Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/fb-cpp/Statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<StatementType>(statementHandle->getType(&statusWrapper));

switch (type)
Expand Down Expand Up @@ -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:
Expand Down
40 changes: 39 additions & 1 deletion src/fb-cpp/Statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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<std::string> cursorName;
CursorType cursorType = CursorType::FORWARD_ONLY;
};

///
Expand Down Expand Up @@ -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}
{
}

Expand Down Expand Up @@ -2736,6 +2773,7 @@ namespace fbcpp
std::vector<Descriptor> outDescriptors;
std::vector<std::byte> outMessage;
StatementType type;
unsigned cursorFlags = 0;
};

///
Expand Down
230 changes: 230 additions & 0 deletions src/test/ScrollableCursor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* MIT License
*
* Copyright (c) 2025 Adriano dos Santos Fernandes
Comment thread
fdcastel marked this conversation as resolved.
Outdated
*
* 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(scrollableCursorSupportsFetchFirst)
Comment thread
fdcastel marked this conversation as resolved.
Outdated
{
const auto database = getTempFile("ScrollableCursor-fetchFirst.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 <= 3; ++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);

BOOST_REQUIRE(select.fetchNext());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 2);

BOOST_REQUIRE(select.fetchFirst());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 1);
}

BOOST_AUTO_TEST_CASE(scrollableCursorSupportsFetchLast)
{
const auto database = getTempFile("ScrollableCursor-fetchLast.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 <= 3; ++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_REQUIRE(select.fetchLast());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 3);
}

BOOST_AUTO_TEST_CASE(scrollableCursorSupportsFetchPrior)
{
const auto database = getTempFile("ScrollableCursor-fetchPrior.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 <= 3; ++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);

BOOST_REQUIRE(select.fetchNext());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 2);

BOOST_REQUIRE(select.fetchPrior());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 1);
}

BOOST_AUTO_TEST_CASE(scrollableCursorSupportsFetchAbsolute)
{
const auto database = getTempFile("ScrollableCursor-fetchAbsolute.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_REQUIRE(select.fetchAbsolute(3));
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 3);

BOOST_REQUIRE(select.fetchAbsolute(1));
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 1);

BOOST_REQUIRE(select.fetchAbsolute(5));
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 5);
}

BOOST_AUTO_TEST_CASE(scrollableCursorSupportsFetchRelative)
{
const auto database = getTempFile("ScrollableCursor-fetchRelative.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);

BOOST_REQUIRE(select.fetchRelative(2));
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 3);

BOOST_REQUIRE(select.fetchRelative(-1));
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 2);
}

BOOST_AUTO_TEST_CASE(forwardOnlyIsDefault)
Comment thread
fdcastel marked this conversation as resolved.
Outdated
{
const auto database = getTempFile("ScrollableCursor-forwardOnlyIsDefault.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 <= 3; ++i)
{
insert.setInt32(0, i);
insert.execute(transaction);
}

// Default options — forward-only cursor
Statement select{attachment, transaction, "select col from t order by col"};
BOOST_REQUIRE(select.execute(transaction));
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 1);

BOOST_REQUIRE(select.fetchNext());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 2);

BOOST_REQUIRE(select.fetchNext());
BOOST_CHECK_EQUAL(select.getInt32(0).value(), 3);

BOOST_CHECK_EQUAL(select.fetchNext(), false);
}

BOOST_AUTO_TEST_SUITE_END()