diff --git a/src/fb-cpp/Statement.cpp b/src/fb-cpp/Statement.cpp index 2cd830f..a42a5b1 100644 --- a/src/fb-cpp/Statement.cpp +++ b/src/fb-cpp/Statement.cpp @@ -56,6 +56,9 @@ Statement::Statement( if (options.getCursorName().has_value()) statementHandle->setCursorName(&statusWrapper, options.getCursorName()->c_str()); + if (options.getCursorType() == CursorType::SCROLLABLE) + cursorFlags = fb::IStatement::CURSOR_TYPE_SCROLLABLE; + type = static_cast(statementHandle->getType(&statusWrapper)); switch (type) @@ -223,7 +226,7 @@ bool Statement::execute(Transaction& transaction) case StatementType::SELECT: case StatementType::SELECT_FOR_UPDATE: resultSetHandle.reset(statementHandle->openCursor(&statusWrapper, transaction.getHandle().get(), - inMetadata.get(), inMessage.data(), outMetadata.get(), 0)); + inMetadata.get(), inMessage.data(), outMetadata.get(), cursorFlags)); return resultSetHandle->fetchNext(&statusWrapper, outMessageData) == fb::IStatus::RESULT_OK; default: diff --git a/src/fb-cpp/Statement.h b/src/fb-cpp/Statement.h index 93e81cf..3d1e30c 100644 --- a/src/fb-cpp/Statement.h +++ b/src/fb-cpp/Statement.h @@ -66,6 +66,22 @@ namespace fbcpp { class Transaction; + /// + /// @brief Selects the cursor type for a SELECT statement. + /// + enum class CursorType + { + /// + /// Forward-only traversal (default, more efficient for streaming). + /// + FORWARD_ONLY, + + /// + /// Allows bidirectional traversal and absolute/relative positioning. + /// + SCROLLABLE, + }; + /// /// Represents options used when preparing a Statement. /// @@ -129,10 +145,30 @@ namespace fbcpp return *this; } + /// + /// @brief Returns the cursor type to be used when opening a result set. + /// + CursorType getCursorType() const + { + return cursorType; + } + + /// + /// @brief Sets the cursor type used when opening a result set. + /// @param value `FORWARD_ONLY` for streaming access, `SCROLLABLE` for bidirectional navigation. + /// @return Reference to this instance for fluent configuration. + /// + StatementOptions& setCursorType(CursorType value) + { + cursorType = value; + return *this; + } + private: bool prefetchLegacyPlan = false; bool prefetchPlan = false; std::optional cursorName; + CursorType cursorType = CursorType::FORWARD_ONLY; }; /// @@ -231,7 +267,8 @@ namespace fbcpp outMetadata{std::move(o.outMetadata)}, outDescriptors{std::move(o.outDescriptors)}, outMessage{std::move(o.outMessage)}, - type{o.type} + type{o.type}, + cursorFlags{o.cursorFlags} { } @@ -2736,6 +2773,7 @@ namespace fbcpp std::vector outDescriptors; std::vector outMessage; StatementType type; + unsigned cursorFlags = 0; }; /// diff --git a/src/test/ScrollableCursor.cpp b/src/test/ScrollableCursor.cpp new file mode 100644 index 0000000..b08eb7b --- /dev/null +++ b/src/test/ScrollableCursor.cpp @@ -0,0 +1,91 @@ +/* + * MIT License + * + * Copyright (c) 2026 F.D.Castel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "TestUtil.h" +#include "fb-cpp/Statement.h" +#include "fb-cpp/Transaction.h" + + +BOOST_AUTO_TEST_SUITE(ScrollableCursorSuite) + +BOOST_AUTO_TEST_CASE(defaultCursorTypeIsForwardOnly) +{ + StatementOptions options; + BOOST_CHECK(options.getCursorType() == CursorType::FORWARD_ONLY); +} + +BOOST_AUTO_TEST_CASE(scrollableCursorSupportsFetchMethods) +{ + const auto database = getTempFile("ScrollableCursor-fetchMethods.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + Statement ddl{attachment, transaction, "create table t (col integer)"}; + ddl.execute(transaction); + transaction.commitRetaining(); + + Statement insert{attachment, transaction, "insert into t (col) values (?)"}; + for (int i = 1; i <= 5; ++i) + { + insert.setInt32(0, i); + insert.execute(transaction); + } + + Statement select{attachment, transaction, "select col from t order by col", + StatementOptions().setCursorType(CursorType::SCROLLABLE)}; + BOOST_REQUIRE(select.execute(transaction)); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 1); + + // fetchNext + BOOST_REQUIRE(select.fetchNext()); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 2); + + // fetchFirst + BOOST_REQUIRE(select.fetchFirst()); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 1); + + // fetchLast + BOOST_REQUIRE(select.fetchLast()); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 5); + + // fetchPrior + BOOST_REQUIRE(select.fetchPrior()); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 4); + + // fetchAbsolute + BOOST_REQUIRE(select.fetchAbsolute(3)); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 3); + + // fetchRelative + BOOST_REQUIRE(select.fetchRelative(2)); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 5); + + BOOST_REQUIRE(select.fetchRelative(-1)); + BOOST_CHECK_EQUAL(select.getInt32(0).value(), 4); +} + +BOOST_AUTO_TEST_SUITE_END()