fb-cpp 0.0.2
A modern C++ wrapper for the Firebird database API
Loading...
Searching...
No Matches
Statement.cpp
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Adriano dos Santos Fernandes
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25#include "Statement.h"
26#include "Transaction.h"
27#include "Attachment.h"
28#include "Client.h"
29
30using namespace fbcpp;
31using namespace fbcpp::impl;
32
33
35 Attachment& attachment, Transaction& transaction, std::string_view sql, const StatementOptions& options)
36 : attachment{&attachment},
37 statusWrapper{attachment.getClient()},
38 calendarConverter{attachment.getClient()},
39 numericConverter{attachment.getClient()}
40{
41 assert(attachment.isValid());
42 assert(transaction.isValid());
43
44 unsigned flags = fb::IStatement::PREPARE_PREFETCH_METADATA;
45
46 if (options.getPrefetchLegacyPlan())
47 flags |= fb::IStatement::PREPARE_PREFETCH_LEGACY_PLAN;
48
49 if (options.getPrefetchPlan())
50 flags |= fb::IStatement::PREPARE_PREFETCH_DETAILED_PLAN;
51
52 statementHandle.reset(attachment.getHandle()->prepare(&statusWrapper, transaction.getHandle().get(),
53 static_cast<unsigned>(sql.length()), sql.data(), options.getDialect(), flags));
54
55 if (options.getCursorName().has_value())
56 statementHandle->setCursorName(&statusWrapper, options.getCursorName()->c_str());
57
59 cursorFlags = fb::IStatement::CURSOR_TYPE_SCROLLABLE;
60
61 type = static_cast<StatementType>(statementHandle->getType(&statusWrapper));
62
63 switch (type)
64 {
66 free();
67 throw FbCppException("Cannot use SET TRANSACTION command with Statement class. Use Transaction class");
68
70 free();
71 throw FbCppException(
72 "Cannot use COMMIT command with Statement class. Use the commit method from the Transaction class");
73
75 free();
76 throw FbCppException(
77 "Cannot use ROLLBACK command with Statement class. Use the rollback method from the Transaction class");
78
81 throw FbCppException("Unsupported statement type: BLOB segment operations");
82
83 default:
84 break;
85 }
86
87 const auto processMetadata = [&](FbRef<fb::IMessageMetadata>& metadata, std::vector<Descriptor>& descriptors,
88 std::vector<std::byte>& message)
89 {
90 if (!metadata)
91 return;
92
93 message.resize(metadata->getMessageLength(&statusWrapper));
94
96
97 const auto count = metadata->getCount(&statusWrapper);
98 descriptors.reserve(count);
99
100 for (unsigned index = 0u; index < count; ++index)
101 {
102 Descriptor descriptor{
103 .originalType = static_cast<DescriptorOriginalType>(metadata->getType(&statusWrapper, index)),
104 .adjustedType = static_cast<DescriptorAdjustedType>(metadata->getType(&statusWrapper, index)),
105 .scale = metadata->getScale(&statusWrapper, index),
106 .length = metadata->getLength(&statusWrapper, index),
107 .offset = 0,
108 .nullOffset = 0,
109 .isNullable = static_cast<bool>(metadata->isNullable(&statusWrapper, index)),
110 .name = metadata->getField(&statusWrapper, index),
111 .relation = metadata->getRelation(&statusWrapper, index),
112 .alias = metadata->getAlias(&statusWrapper, index),
113 .owner = metadata->getOwner(&statusWrapper, index),
114 .charSetId = metadata->getCharSet(&statusWrapper, index),
115 .subType = metadata->getSubType(&statusWrapper, index),
116 };
117
118 switch (descriptor.originalType)
119 {
121 if (!builder)
122 builder.reset(metadata->getBuilder(&statusWrapper));
123
124 builder->setType(&statusWrapper, index, SQL_VARYING);
125 descriptor.adjustedType = DescriptorAdjustedType::STRING;
126 break;
127
129 if (!builder)
130 builder.reset(metadata->getBuilder(&statusWrapper));
131
132 builder->setType(&statusWrapper, index, SQL_TIME_TZ);
133 descriptor.adjustedType = DescriptorAdjustedType::TIME_TZ;
134 break;
135
137 if (!builder)
138 builder.reset(metadata->getBuilder(&statusWrapper));
139
140 builder->setType(&statusWrapper, index, SQL_TIMESTAMP_TZ);
141 descriptor.adjustedType = DescriptorAdjustedType::TIMESTAMP_TZ;
142 break;
143
144 default:
145 break;
146 }
147
148 if (!builder)
149 {
150 descriptor.offset = metadata->getOffset(&statusWrapper, index);
151 descriptor.nullOffset = metadata->getNullOffset(&statusWrapper, index);
152
153 *reinterpret_cast<std::int16_t*>(&message[descriptor.nullOffset]) = FB_TRUE;
154 }
155
156 descriptors.push_back(descriptor);
157 }
158
159 if (builder)
160 {
161 metadata.reset(builder->getMetadata(&statusWrapper));
162 message.resize(metadata->getMessageLength(&statusWrapper));
163
164 for (unsigned index = 0u; index < count; ++index)
165 {
166 auto& descriptor = descriptors[index];
167 descriptor.offset = metadata->getOffset(&statusWrapper, index);
168 descriptor.nullOffset = metadata->getNullOffset(&statusWrapper, index);
169
170 *reinterpret_cast<std::int16_t*>(&message[descriptor.nullOffset]) = FB_TRUE;
171 }
172 }
173 };
174
175 inMetadata.reset(statementHandle->getInputMetadata(&statusWrapper));
176 processMetadata(inMetadata, inDescriptors, inMessage);
177
178 outMetadata.reset(statementHandle->getOutputMetadata(&statusWrapper));
179 processMetadata(outMetadata, outDescriptors, outMessage);
180
181 outRow = std::make_unique<Row>(attachment.getClient(), outDescriptors, std::span{outMessage});
182}
183
185 : attachment{o.attachment},
186 statusWrapper{std::move(o.statusWrapper)},
187 calendarConverter{std::move(o.calendarConverter)},
188 numericConverter{std::move(o.numericConverter)},
189 statementHandle{std::move(o.statementHandle)},
190 resultSetHandle{std::move(o.resultSetHandle)},
191 inMetadata{std::move(o.inMetadata)},
192 inDescriptors{std::move(o.inDescriptors)},
193 inMessage{std::move(o.inMessage)},
194 outMetadata{std::move(o.outMetadata)},
195 outDescriptors{std::move(o.outDescriptors)},
196 outMessage{std::move(o.outMessage)},
197 outRow{std::make_unique<Row>(attachment->getClient(), outDescriptors, std::span{outMessage})},
198 type{o.type},
199 cursorFlags{o.cursorFlags},
200 currentRow{o.currentRow}
201{
202 o.outRow.reset();
203 o.currentRow = false;
204}
205
207{
208 if (this != &o)
209 {
210 attachment = o.attachment;
211 statusWrapper = std::move(o.statusWrapper);
212 calendarConverter = std::move(o.calendarConverter);
213 numericConverter = std::move(o.numericConverter);
214 statementHandle = std::move(o.statementHandle);
215 resultSetHandle = std::move(o.resultSetHandle);
216 inMetadata = std::move(o.inMetadata);
217 inDescriptors = std::move(o.inDescriptors);
218 inMessage = std::move(o.inMessage);
219 outMetadata = std::move(o.outMetadata);
220 outDescriptors = std::move(o.outDescriptors);
221 outMessage = std::move(o.outMessage);
222 outRow = std::make_unique<Row>(attachment->getClient(), outDescriptors, std::span{outMessage});
223 type = o.type;
224 cursorFlags = o.cursorFlags;
225 currentRow = o.currentRow;
226
227 o.outRow.reset();
228 o.currentRow = false;
229 }
230
231 return *this;
232}
233
235{
236 assert(isValid());
237
238 if (resultSetHandle)
239 {
240 resultSetHandle->close(&statusWrapper);
241 resultSetHandle.reset();
242 }
243
244 currentRow = false;
245 statementHandle->free(&statusWrapper);
246 statementHandle.reset();
247}
248
250{
251 assert(isValid());
252
253 return statementHandle->getPlan(&statusWrapper, false);
254}
255
257{
258 assert(isValid());
259
260 return statementHandle->getPlan(&statusWrapper, true);
261}
262
264{
265 assert(isValid());
266 assert(transaction.isValid());
267
268 if (resultSetHandle)
269 {
270 resultSetHandle->close(&statusWrapper);
271 resultSetHandle.reset();
272 }
273
274 currentRow = false;
275
276 const auto outMessageData = outMessage.data();
277
278 if (outMessageData)
279 {
280 for (const auto& descriptor : outDescriptors)
281 *reinterpret_cast<std::int16_t*>(&outMessageData[descriptor.nullOffset]) = FB_TRUE;
282 }
283
284 switch (type)
285 {
288 resultSetHandle.reset(statementHandle->openCursor(&statusWrapper, transaction.getHandle().get(),
289 inMetadata.get(), inMessage.data(), outMetadata.get(), cursorFlags));
290 currentRow = resultSetHandle->fetchNext(&statusWrapper, outMessageData) == fb::IStatus::RESULT_OK;
291 return currentRow;
292
293 default:
294 statementHandle->execute(&statusWrapper, transaction.getHandle().get(), inMetadata.get(), inMessage.data(),
295 outMetadata.get(), outMessageData);
296 currentRow = !outDescriptors.empty();
297 return true;
298 }
299}
300
301Client& Statement::getClient() noexcept
302{
303 return attachment->getClient();
304}
305
307{
308 assert(isValid());
309
310 currentRow =
311 resultSetHandle && resultSetHandle->fetchNext(&statusWrapper, outMessage.data()) == fb::IStatus::RESULT_OK;
312 return currentRow;
313}
314
316{
317 assert(isValid());
318
319 currentRow =
320 resultSetHandle && resultSetHandle->fetchPrior(&statusWrapper, outMessage.data()) == fb::IStatus::RESULT_OK;
321 return currentRow;
322}
323
325{
326 assert(isValid());
327
328 currentRow =
329 resultSetHandle && resultSetHandle->fetchFirst(&statusWrapper, outMessage.data()) == fb::IStatus::RESULT_OK;
330 return currentRow;
331}
332
334{
335 assert(isValid());
336
337 currentRow =
338 resultSetHandle && resultSetHandle->fetchLast(&statusWrapper, outMessage.data()) == fb::IStatus::RESULT_OK;
339 return currentRow;
340}
341
342bool Statement::fetchAbsolute(unsigned position)
343{
344 assert(isValid());
345
346 currentRow = resultSetHandle &&
347 resultSetHandle->fetchAbsolute(&statusWrapper, static_cast<int>(position), outMessage.data()) ==
348 fb::IStatus::RESULT_OK;
349 return currentRow;
350}
351
353{
354 assert(isValid());
355
356 currentRow = resultSetHandle &&
357 resultSetHandle->fetchRelative(&statusWrapper, offset, outMessage.data()) == fb::IStatus::RESULT_OK;
358 return currentRow;
359}
Represents a connection to a Firebird database.
Definition Attachment.h:220
Client & getClient() noexcept
Returns the Client object reference used to create this Attachment object.
Definition Attachment.h:288
FbRef< fb::IAttachment > getHandle() noexcept
Returns the internal Firebird IAttachment handle.
Definition Attachment.h:296
bool isValid() noexcept
Returns whether the Attachment object is valid.
Definition Attachment.h:280
Represents a Firebird client library instance.
Definition Client.h:54
Base exception class for all fb-cpp exceptions.
Definition Exception.h:231
Reference-counted smart pointer for Firebird objects using addRef/release semantics.
Definition SmartPtrs.h:70
Represents options used when preparing a Statement.
const std::optional< std::string > & getCursorName() const
Returns the cursor name to be set for the statement.
unsigned getDialect() const
Returns the SQL dialect used when preparing the statement.
bool getPrefetchPlan() const
Reports whether the structured plan should be prefetched during prepare.
bool getPrefetchLegacyPlan() const
Reports whether the legacy textual plan should be prefetched during prepare.
CursorType getCursorType() const
Returns the cursor type to be used when opening a result set.
Prepares, executes, and fetches SQL statements against a Firebird attachment.
Definition Statement.h:146
bool fetchAbsolute(unsigned position)
Positions the cursor on the given absolute row number.
bool fetchRelative(int offset)
Moves the cursor by the requested relative offset.
std::string getLegacyPlan()
Retrieves the textual legacy plan if the server produced one.
void free()
Releases the prepared handle and any associated result set.
bool isValid() noexcept
Returns whether the Statement object is valid.
Definition Statement.h:213
Statement & operator=(Statement &&o) noexcept
Transfers ownership of another prepared statement into this one.
std::string getPlan()
Retrieves the structured textual plan if the server produced one.
bool fetchNext()
Fetches the next row in the current result set.
bool fetchLast()
Positions the cursor on the last row.
bool execute(Transaction &transaction)
Executes a prepared statement using the supplied transaction.
Statement(Attachment &attachment, Transaction &transaction, std::string_view sql, const StatementOptions &options={})
Prepares an SQL statement.
Definition Statement.cpp:34
bool fetchPrior()
Fetches the previous row in the current result set.
bool fetchFirst()
Positions the cursor on the first row.
Represents a transaction in one or more Firebird databases.
bool isValid() noexcept
Returns whether the Transaction object is valid.
FbRef< fb::ITransaction > getHandle() noexcept
Returns the internal Firebird ITransaction handle.
fb-cpp namespace.
Definition Array.h:43
@ SCROLLABLE
Allows bidirectional traversal and absolute/relative positioning.
DescriptorAdjustedType
Descriptor adjusted type.
Definition Descriptor.h:161
@ TIME_TZ
Time of day with time zone.
@ STRING
String type (variable-length).
@ TIMESTAMP_TZ
Timestamp with time zone.
StatementType
Distinguishes the semantic category of the prepared SQL statement.
Definition Statement.h:83
@ PUT_SEGMENT
Statement writes a blob segment - legacy feature.
@ COMMIT
Statement commits a transaction.
@ ROLLBACK
Statement rolls back a transaction.
@ GET_SEGMENT
Statement reads a blob segment - legacy feature.
@ SELECT
Server classified the statement as a SELECT.
@ START_TRANSACTION
Statement starts a new transaction.
@ SELECT_FOR_UPDATE
Cursor-based SELECT that allows updates.
DescriptorOriginalType
Descriptor original type.
Definition Descriptor.h:50
@ TIMESTAMP_TZ_EX
Extended timestamp with time zone.
@ TEXT
Fixed-length text.
@ TIME_TZ_EX
Extended time of day with time zone.
Describes a parameter or column.
Definition Descriptor.h:267
DescriptorOriginalType originalType
Original SQL type as reported by Firebird.
Definition Descriptor.h:271