Skip to content

Commit 34c01d2

Browse files
committed
Add Attachment::queryRowSet methods
1 parent dd8d8dc commit 34c01d2

5 files changed

Lines changed: 210 additions & 2 deletions

File tree

src/fb-cpp/Attachment.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "Attachment.h"
2626
#include "Client.h"
2727
#include "Exception.h"
28+
#include "RowSet.h"
2829
#include "Statement.h"
2930
#include "Transaction.h"
3031

@@ -106,3 +107,33 @@ bool Attachment::execute(Transaction& transaction, std::string_view sql, const S
106107
Statement statement{*this, transaction, sql, options};
107108
return statement.execute(transaction);
108109
}
110+
111+
RowSet Attachment::queryRowSet(Transaction& transaction, std::string_view sql, unsigned maxRows)
112+
{
113+
return queryRowSet(transaction, sql, maxRows, StatementOptions{});
114+
}
115+
116+
RowSet Attachment::queryRowSet(
117+
Transaction& transaction, std::string_view sql, unsigned maxRows, const StatementOptions& options)
118+
{
119+
Statement statement{*this, transaction, sql, options};
120+
121+
switch (statement.getType())
122+
{
123+
case StatementType::SELECT:
124+
case StatementType::SELECT_FOR_UPDATE:
125+
break;
126+
127+
case StatementType::EXEC_PROCEDURE:
128+
if (!statement.getOutputDescriptors().empty())
129+
break;
130+
131+
throw FbCppException("Cannot use procedure without output columns with Attachment::queryRowSet");
132+
133+
default:
134+
throw FbCppException("Cannot use non-query SQL with Attachment::queryRowSet");
135+
}
136+
137+
const auto hasRow = statement.execute(transaction);
138+
return RowSet{statement, hasRow ? maxRows : 0u, hasRow};
139+
}

src/fb-cpp/Attachment.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
namespace fbcpp
4343
{
4444
class Client;
45+
class RowSet;
4546
class StatementOptions;
4647
class Transaction;
4748

@@ -314,6 +315,18 @@ namespace fbcpp
314315
///
315316
bool execute(Transaction& transaction, std::string_view sql, const StatementOptions& options);
316317

318+
///
319+
/// Prepares and executes a query using the supplied transaction and returns up to maxRows rows.
320+
///
321+
RowSet queryRowSet(Transaction& transaction, std::string_view sql, unsigned maxRows);
322+
323+
///
324+
/// Prepares and executes a query using the supplied transaction and statement options and returns up to maxRows
325+
/// rows.
326+
///
327+
RowSet queryRowSet(
328+
Transaction& transaction, std::string_view sql, unsigned maxRows, const StatementOptions& options);
329+
317330
private:
318331
void disconnectOrDrop(bool drop);
319332

src/fb-cpp/RowSet.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,25 @@
2525
#include "RowSet.h"
2626
#include "Client.h"
2727
#include "Statement.h"
28+
#include <algorithm>
2829

2930
using namespace fbcpp;
3031
using namespace fbcpp::impl;
3132

3233

3334
RowSet::RowSet(Statement& statement, unsigned maxRows)
35+
: RowSet{statement, maxRows, false}
36+
{
37+
}
38+
39+
RowSet::RowSet(Statement& statement, unsigned maxRows, bool includeCurrentRow)
3440
: client{&statement.getAttachment().getClient()},
3541
statusWrapper{statement.getAttachment().getClient()},
3642
numericConverter{statement.getAttachment().getClient()},
3743
calendarConverter{statement.getAttachment().getClient()}
3844
{
3945
assert(statement.isValid());
40-
assert(statement.getResultSetHandle());
46+
assert(includeCurrentRow || statement.getResultSetHandle());
4147

4248
descriptors = statement.getOutputDescriptors();
4349

@@ -49,7 +55,22 @@ RowSet::RowSet(Statement& statement, unsigned maxRows)
4955
auto resultSet = statement.getResultSetHandle();
5056
auto* dest = buffer.data();
5157

52-
for (unsigned i = 0; i < maxRows; ++i)
58+
if (includeCurrentRow && maxRows > 0u)
59+
{
60+
auto& currentRow = statement.getOutputMessage();
61+
assert(currentRow.size() == messageLength);
62+
std::copy(currentRow.begin(), currentRow.end(), dest);
63+
dest += messageLength;
64+
++count;
65+
}
66+
67+
if (!resultSet)
68+
{
69+
buffer.resize(static_cast<std::size_t>(dest - buffer.data()));
70+
return;
71+
}
72+
73+
for (unsigned i = count; i < maxRows; ++i)
5374
{
5475
if (resultSet->fetchNext(&statusWrapper, dest) != fb::IStatus::RESULT_OK)
5576
break;

src/fb-cpp/RowSet.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ namespace fbcpp
6969
///
7070
explicit RowSet(Statement& statement, unsigned maxRows);
7171

72+
///
73+
/// @brief Fetches up to `maxRows` rows from the current result set of `statement`.
74+
///
75+
/// When `includeCurrentRow` is true, the current output message already fetched by `statement.execute()` is
76+
/// copied as the first row before fetching the remaining rows from the result set.
77+
///
78+
explicit RowSet(Statement& statement, unsigned maxRows, bool includeCurrentRow);
79+
7280
RowSet(RowSet&& o) noexcept
7381
: client{o.client},
7482
count{o.count},

src/test/Attachment.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "TestUtil.h"
2626
#include "fb-cpp/Attachment.h"
2727
#include "fb-cpp/Exception.h"
28+
#include "fb-cpp/RowSet.h"
2829
#include "fb-cpp/Statement.h"
2930
#include "fb-cpp/Transaction.h"
3031
#include <exception>
@@ -109,6 +110,140 @@ BOOST_AUTO_TEST_CASE(executePreparesAndExecutesStatement)
109110
transaction.commit();
110111
}
111112

113+
BOOST_AUTO_TEST_CASE(queryReturnsRowsIncludingFirstRow)
114+
{
115+
const auto database = getTempFile("Attachment-queryReturnsRowsIncludingFirstRow.fdb");
116+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
117+
FbDropDatabase attachmentDrop{attachment};
118+
119+
Transaction transaction{attachment};
120+
BOOST_REQUIRE(attachment.execute(transaction, "create table t (id integer not null primary key)"));
121+
transaction.commitRetaining();
122+
123+
for (int i = 1; i <= 3; ++i)
124+
{
125+
Statement insert{attachment, transaction, "insert into t (id) values (?)"};
126+
insert.setInt32(0, i);
127+
BOOST_REQUIRE(insert.execute(transaction));
128+
}
129+
130+
auto rowSet = attachment.queryRowSet(transaction, "select id from t order by id", 10u);
131+
132+
BOOST_REQUIRE_EQUAL(rowSet.getCount(), 3u);
133+
BOOST_CHECK_EQUAL(rowSet.getRow(0).getInt32(0).value(), 1);
134+
BOOST_CHECK_EQUAL(rowSet.getRow(1).getInt32(0).value(), 2);
135+
BOOST_CHECK_EQUAL(rowSet.getRow(2).getInt32(0).value(), 3);
136+
137+
transaction.commit();
138+
}
139+
140+
BOOST_AUTO_TEST_CASE(queryHonorsMaxRows)
141+
{
142+
const auto database = getTempFile("Attachment-queryHonorsMaxRows.fdb");
143+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
144+
FbDropDatabase attachmentDrop{attachment};
145+
146+
Transaction transaction{attachment};
147+
BOOST_REQUIRE(attachment.execute(transaction, "create table t (id integer not null primary key)"));
148+
transaction.commitRetaining();
149+
150+
for (int i = 1; i <= 5; ++i)
151+
{
152+
Statement insert{attachment, transaction, "insert into t (id) values (?)"};
153+
insert.setInt32(0, i);
154+
BOOST_REQUIRE(insert.execute(transaction));
155+
}
156+
157+
auto rowSet = attachment.queryRowSet(transaction, "select id from t order by id", 2u);
158+
159+
BOOST_REQUIRE_EQUAL(rowSet.getCount(), 2u);
160+
BOOST_CHECK_EQUAL(rowSet.getRow(0).getInt32(0).value(), 1);
161+
BOOST_CHECK_EQUAL(rowSet.getRow(1).getInt32(0).value(), 2);
162+
163+
transaction.commit();
164+
}
165+
166+
BOOST_AUTO_TEST_CASE(queryReturnsEmptyRowSetForNoRows)
167+
{
168+
const auto database = getTempFile("Attachment-queryReturnsEmptyRowSetForNoRows.fdb");
169+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
170+
FbDropDatabase attachmentDrop{attachment};
171+
172+
Transaction transaction{attachment};
173+
BOOST_REQUIRE(attachment.execute(transaction, "create table t (id integer not null primary key)"));
174+
transaction.commitRetaining();
175+
176+
auto rowSet = attachment.queryRowSet(transaction, "select id from t", 10u);
177+
178+
BOOST_CHECK_EQUAL(rowSet.getCount(), 0u);
179+
BOOST_CHECK(rowSet.getRawBuffer().empty());
180+
181+
transaction.commit();
182+
}
183+
184+
BOOST_AUTO_TEST_CASE(querySupportsStatementOptions)
185+
{
186+
const auto database = getTempFile("Attachment-querySupportsStatementOptions.fdb");
187+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
188+
FbDropDatabase attachmentDrop{attachment};
189+
190+
Transaction transaction{attachment};
191+
auto rowSet =
192+
attachment.queryRowSet(transaction, "select 1 from rdb$database", 1u, StatementOptions().setDialect(3u));
193+
194+
BOOST_REQUIRE_EQUAL(rowSet.getCount(), 1u);
195+
BOOST_CHECK_EQUAL(rowSet.getRow(0).getInt32(0).value(), 1);
196+
197+
transaction.commit();
198+
}
199+
200+
BOOST_AUTO_TEST_CASE(queryThrowsForNonQueryStatement)
201+
{
202+
const auto database = getTempFile("Attachment-queryThrowsForNonQueryStatement.fdb");
203+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
204+
FbDropDatabase attachmentDrop{attachment};
205+
206+
Transaction transaction{attachment};
207+
BOOST_CHECK_THROW(attachment.queryRowSet(transaction, "create table t (id integer)", 10u), FbCppException);
208+
209+
transaction.commit();
210+
}
211+
212+
BOOST_AUTO_TEST_CASE(queryRowSetSupportsProcedureWithOutputColumns)
213+
{
214+
const auto database = getTempFile("Attachment-queryRowSetSupportsProcedureWithOutputColumns.fdb");
215+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
216+
FbDropDatabase attachmentDrop{attachment};
217+
218+
Transaction transaction{attachment};
219+
BOOST_REQUIRE(attachment.execute(transaction,
220+
"create procedure p returns (id integer, name varchar(20)) as begin id = 42; name = 'answer'; suspend; end"));
221+
transaction.commitRetaining();
222+
223+
auto rowSet = attachment.queryRowSet(transaction, "execute procedure p", 10u);
224+
225+
BOOST_REQUIRE_EQUAL(rowSet.getCount(), 1u);
226+
BOOST_CHECK_EQUAL(rowSet.getRow(0).getInt32(0).value(), 42);
227+
BOOST_CHECK_EQUAL(rowSet.getRow(0).getString(1).value(), "answer");
228+
229+
transaction.commit();
230+
}
231+
232+
BOOST_AUTO_TEST_CASE(queryRowSetRejectsProcedureWithoutOutputColumns)
233+
{
234+
const auto database = getTempFile("Attachment-queryRowSetRejectsProcedureWithoutOutputColumns.fdb");
235+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setForcedWrites(false)};
236+
FbDropDatabase attachmentDrop{attachment};
237+
238+
Transaction transaction{attachment};
239+
BOOST_REQUIRE(attachment.execute(transaction, "create procedure p as begin end"));
240+
transaction.commitRetaining();
241+
242+
BOOST_CHECK_THROW(attachment.queryRowSet(transaction, "execute procedure p", 10u), FbCppException);
243+
244+
transaction.commit();
245+
}
246+
112247
BOOST_AUTO_TEST_CASE(isNotValidAfterMove)
113248
{
114249
const auto database = getTempFile("Attachment-isNotValidAfterMove.fdb");

0 commit comments

Comments
 (0)