From a861757ac0475b2f55f1003a19ae0f5b7ffe316e Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Wed, 24 Dec 2025 14:28:10 -0300 Subject: [PATCH] Feature #7 - Reading fields or setting parameters with tuples --- src/fb-cpp/Statement.h | 63 +++++++++++++++++++ src/fb-cpp/StructBinding.h | 6 ++ src/test/Statement.cpp | 125 +++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) diff --git a/src/fb-cpp/Statement.h b/src/fb-cpp/Statement.h index 40a296d..8de331d 100644 --- a/src/fb-cpp/Statement.h +++ b/src/fb-cpp/Statement.h @@ -2088,6 +2088,49 @@ namespace fbcpp setStruct(value, std::make_index_sequence{}); } + /// + /// @brief Retrieves all output columns into a tuple-like type. + /// @tparam T A tuple-like type (std::tuple, std::pair) whose elements match the output column count and types. + /// @return The populated tuple with values from the current row. + /// @throws FbCppException if element count mismatches output column count. + /// @throws FbCppException if a NULL value is encountered for a non-optional element. + /// + template + T get() + { + using namespace impl::reflection; + + constexpr std::size_t N = std::tuple_size_v; + + if (N != outDescriptors.size()) + { + throw FbCppException("Tuple element count (" + std::to_string(N) + + ") does not match output column count (" + std::to_string(outDescriptors.size()) + ")"); + } + + return getTuple(std::make_index_sequence{}); + } + + /// + /// @brief Sets all input parameters from elements of a tuple-like type. + /// @tparam T A tuple-like type (std::tuple, std::pair) whose elements match the input parameter count. + /// @param value The tuple containing parameter values. + /// @throws FbCppException if element count mismatches input parameter count. + /// + template + void set(const T& value) + { + constexpr std::size_t N = std::tuple_size_v; + + if (N != inDescriptors.size()) + { + throw FbCppException("Tuple element count (" + std::to_string(N) + + ") does not match input parameter count (" + std::to_string(inDescriptors.size()) + ")"); + } + + setTuple(value, std::make_index_sequence{}); + } + private: /// /// @brief Validates and returns the descriptor for the given input parameter index. @@ -2158,6 +2201,26 @@ namespace fbcpp (set(static_cast(Is), std::get(tuple)), ...); } + /// + /// @brief Helper to retrieve all output columns into a tuple. + /// + template + T getTuple(std::index_sequence) + { + using namespace impl::reflection; + + return T{getStructField>(static_cast(Is))...}; + } + + /// + /// @brief Helper to set all input parameters from a tuple. + /// + template + void setTuple(const T& value, std::index_sequence) + { + (set(static_cast(Is), std::get(value)), ...); + } + /// /// @brief Converts and writes numeric parameter values following descriptor rules. /// diff --git a/src/fb-cpp/StructBinding.h b/src/fb-cpp/StructBinding.h index 7fb3c10..a8c5af8 100644 --- a/src/fb-cpp/StructBinding.h +++ b/src/fb-cpp/StructBinding.h @@ -39,6 +39,12 @@ namespace fbcpp /// template concept Aggregate = std::is_aggregate_v && !std::is_array_v && !std::is_union_v; + + /// + /// Concept constraining types to tuple-like types (std::tuple, std::pair, std::array). + /// + template + concept TupleLike = !Aggregate && requires { typename std::tuple_size::type; }; } // namespace fbcpp namespace fbcpp::impl::reflection diff --git a/src/test/Statement.cpp b/src/test/Statement.cpp index 61d32d4..4dbca80 100644 --- a/src/test/Statement.cpp +++ b/src/test/Statement.cpp @@ -2703,3 +2703,128 @@ BOOST_AUTO_TEST_CASE(setStructWithOptionalNull) } BOOST_AUTO_TEST_SUITE_END() + + +BOOST_AUTO_TEST_SUITE(TupleBindingSuite) + +BOOST_AUTO_TEST_CASE(getTupleRetrievesAllColumns) +{ + using ResultTuple = std::tuple, std::optional, std::optional>; + + const auto database = getTempFile("Statement-getTupleRetrievesAllColumns.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select 42, 'hello', 3.14e0 from rdb$database"}; + BOOST_REQUIRE(stmt.execute(transaction)); + + const auto result = stmt.get(); + BOOST_CHECK(std::get<0>(result).has_value()); + BOOST_CHECK_EQUAL(std::get<0>(result).value(), 42); + BOOST_CHECK(std::get<1>(result).has_value()); + BOOST_CHECK_EQUAL(std::get<1>(result).value(), "hello"); + BOOST_CHECK(std::get<2>(result).has_value()); + BOOST_CHECK_CLOSE(std::get<2>(result).value(), 3.14, 0.001); +} + +BOOST_AUTO_TEST_CASE(setTupleSetsAllParameters) +{ + using ParamTuple = std::tuple; + + const auto database = getTempFile("Statement-setTupleSetsAllParameters.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select cast(? as integer), cast(? as varchar(50)) from rdb$database"}; + + stmt.set(ParamTuple{123, "test"}); + BOOST_REQUIRE(stmt.execute(transaction)); + + BOOST_CHECK_EQUAL(stmt.getInt32(0).value(), 123); + BOOST_CHECK_EQUAL(stmt.getString(1).value(), "test"); +} + +BOOST_AUTO_TEST_CASE(getTupleElementCountMismatchThrows) +{ + using WrongTuple = std::tuple, std::optional>; + + const auto database = getTempFile("Statement-getTupleElementCountMismatchThrows.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select 1, 2, 3 from rdb$database"}; + BOOST_REQUIRE(stmt.execute(transaction)); + + BOOST_CHECK_THROW(stmt.get(), FbCppException); +} + +BOOST_AUTO_TEST_CASE(setTupleElementCountMismatchThrows) +{ + using WrongTuple = std::tuple; + + const auto database = getTempFile("Statement-setTupleElementCountMismatchThrows.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select cast(? as integer) from rdb$database"}; + + BOOST_CHECK_THROW(stmt.set(WrongTuple{1, 2, 3}), FbCppException); +} + +BOOST_AUTO_TEST_CASE(nullForNonOptionalTupleElementThrows) +{ + using NonOptionalTuple = std::tuple; + + const auto database = getTempFile("Statement-nullForNonOptionalTupleElementThrows.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select cast(null as integer) from rdb$database"}; + BOOST_REQUIRE(stmt.execute(transaction)); + + BOOST_CHECK_THROW(stmt.get(), FbCppException); +} + +BOOST_AUTO_TEST_CASE(pairAsResultType) +{ + using ResultPair = std::pair, std::optional>; + + const auto database = getTempFile("Statement-pairAsResultType.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select 100, 'pair test' from rdb$database"}; + BOOST_REQUIRE(stmt.execute(transaction)); + + const auto result = stmt.get(); + BOOST_CHECK(result.first.has_value()); + BOOST_CHECK_EQUAL(result.first.value(), 100); + BOOST_CHECK(result.second.has_value()); + BOOST_CHECK_EQUAL(result.second.value(), "pair test"); +} + +BOOST_AUTO_TEST_CASE(setTupleWithOptionalNull) +{ + using ParamTuple = std::tuple>; + + const auto database = getTempFile("Statement-setTupleWithOptionalNull.fdb"); + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt{attachment, transaction, "select cast(? as integer), cast(? as varchar(50)) from rdb$database"}; + + stmt.set(ParamTuple{999, std::nullopt}); + BOOST_REQUIRE(stmt.execute(transaction)); + + BOOST_CHECK_EQUAL(stmt.getInt32(0).value(), 999); + BOOST_CHECK(!stmt.getString(1).has_value()); +} + +BOOST_AUTO_TEST_SUITE_END()