Skip to content

Commit 840a686

Browse files
authored
Add Batch Execution support (IBatch wrapper) (asfernandes#26) (asfernandes#40)
1 parent 46f926e commit 840a686

6 files changed

Lines changed: 1296 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
}
1717
```
1818
- When adding new source files, copy the header from the existing files, but update the Copyright line to reflect the current year and use `$(git config --get user.name)` as the original author.
19+
- Use lowercase suffixes for numeric literals (e.g. `1u`, `3u`, not `1U`, `3U`).
20+
- Do not use string literals in `assert()` expressions (e.g. `assert(ptr && "msg")` is not idiomatic C++).
21+
- Do not explicitly define a destructor as `= default` unless it is necessary (e.g. to make it virtual or to
22+
control the definition translation unit).
23+
- Prefer constructing `std::vector` directly from iterator ranges (constructor 5) instead of allocating and copying
24+
with `std::memcpy`.
1925
2026
### Build
2127
- If .cpp files are added, it's necessary to run `cmake --preset default` from the repo root.

src/fb-cpp/Batch.cpp

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2026 F.D.Castel
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 all
14+
* 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 "Batch.h"
26+
#include "Attachment.h"
27+
#include "Client.h"
28+
#include "Statement.h"
29+
#include "Transaction.h"
30+
31+
using namespace fbcpp;
32+
using namespace fbcpp::impl;
33+
34+
35+
// --- BatchCompletionState ---
36+
37+
BatchCompletionState::BatchCompletionState(Client& client, FbUniquePtr<fb::IBatchCompletionState> handle) noexcept
38+
: client{&client},
39+
status{client.newStatus()},
40+
statusWrapper{client, status.get()},
41+
handle{std::move(handle)}
42+
{
43+
}
44+
45+
BatchCompletionState::BatchCompletionState(BatchCompletionState&& o) noexcept
46+
: client{o.client},
47+
status{std::move(o.status)},
48+
statusWrapper{std::move(o.statusWrapper)},
49+
handle{std::move(o.handle)}
50+
{
51+
}
52+
53+
unsigned BatchCompletionState::getSize()
54+
{
55+
return handle->getSize(&statusWrapper);
56+
}
57+
58+
int BatchCompletionState::getState(unsigned pos)
59+
{
60+
return handle->getState(&statusWrapper, pos);
61+
}
62+
63+
std::optional<unsigned> BatchCompletionState::findError(unsigned pos)
64+
{
65+
const auto result = handle->findError(&statusWrapper, pos);
66+
67+
if (result == fb::IBatchCompletionState::NO_MORE_ERRORS)
68+
return std::nullopt;
69+
70+
return result;
71+
}
72+
73+
std::vector<std::intptr_t> BatchCompletionState::getStatus(unsigned pos)
74+
{
75+
auto tempStatus = client->newStatus();
76+
77+
handle->getStatus(&statusWrapper, tempStatus.get(), pos);
78+
79+
std::vector<std::intptr_t> result;
80+
const auto* errors = tempStatus->getErrors();
81+
82+
if (errors)
83+
{
84+
const auto* p = errors;
85+
86+
while (*p != isc_arg_end)
87+
{
88+
result.push_back(*p++);
89+
result.push_back(*p++);
90+
}
91+
92+
result.push_back(isc_arg_end);
93+
}
94+
95+
return result;
96+
}
97+
98+
99+
// --- Batch ---
100+
101+
Batch::Batch(Statement& statement, Transaction& transaction, const BatchOptions& options)
102+
: client{&statement.getAttachment().getClient()},
103+
transaction{&transaction},
104+
statement{&statement},
105+
status{client->newStatus()},
106+
statusWrapper{*client, status.get()}
107+
{
108+
assert(statement.isValid());
109+
assert(transaction.isValid());
110+
111+
const auto parBlock = buildParametersBlock(*client, options);
112+
113+
handle.reset(statement.getStatementHandle()->createBatch(
114+
&statusWrapper, statement.getInputMetadata().get(), static_cast<unsigned>(parBlock.size()), parBlock.data()));
115+
}
116+
117+
Batch::Batch(Attachment& attachment, Transaction& transaction, std::string_view sql, unsigned dialect,
118+
const BatchOptions& options)
119+
: client{&attachment.getClient()},
120+
transaction{&transaction},
121+
status{client->newStatus()},
122+
statusWrapper{*client, status.get()}
123+
{
124+
assert(attachment.isValid());
125+
assert(transaction.isValid());
126+
127+
const auto parBlock = buildParametersBlock(*client, options);
128+
129+
handle.reset(attachment.getHandle()->createBatch(&statusWrapper, transaction.getHandle().get(),
130+
static_cast<unsigned>(sql.length()), sql.data(), dialect, nullptr, static_cast<unsigned>(parBlock.size()),
131+
parBlock.data()));
132+
}
133+
134+
Batch::Batch(Batch&& o) noexcept
135+
: client{o.client},
136+
transaction{o.transaction},
137+
statement{o.statement},
138+
status{std::move(o.status)},
139+
statusWrapper{std::move(o.statusWrapper)},
140+
handle{std::move(o.handle)}
141+
{
142+
}
143+
144+
145+
// --- Adding messages ---
146+
147+
void Batch::add(unsigned count, const void* inBuffer)
148+
{
149+
assert(isValid());
150+
handle->add(&statusWrapper, count, inBuffer);
151+
}
152+
153+
void Batch::addMessage()
154+
{
155+
assert(isValid());
156+
assert(statement);
157+
handle->add(&statusWrapper, 1, statement->getInputMessage().data());
158+
}
159+
160+
161+
// --- Blob support ---
162+
163+
BlobId Batch::addBlob(std::span<const std::byte> data, const BlobOptions& bpb)
164+
{
165+
assert(isValid());
166+
167+
const auto preparedBpb = prepareBpb(*client, bpb);
168+
169+
BlobId blobId;
170+
handle->addBlob(&statusWrapper, static_cast<unsigned>(data.size()), data.data(), &blobId.id,
171+
static_cast<unsigned>(preparedBpb.size()), preparedBpb.data());
172+
173+
return blobId;
174+
}
175+
176+
void Batch::appendBlobData(std::span<const std::byte> data)
177+
{
178+
assert(isValid());
179+
handle->appendBlobData(&statusWrapper, static_cast<unsigned>(data.size()), data.data());
180+
}
181+
182+
void Batch::addBlobStream(std::span<const std::byte> data)
183+
{
184+
assert(isValid());
185+
handle->addBlobStream(&statusWrapper, static_cast<unsigned>(data.size()), data.data());
186+
}
187+
188+
BlobId Batch::registerBlob(const BlobId& existingBlob)
189+
{
190+
assert(isValid());
191+
192+
BlobId batchId;
193+
handle->registerBlob(&statusWrapper, &existingBlob.id, &batchId.id);
194+
195+
return batchId;
196+
}
197+
198+
void Batch::setDefaultBpb(const BlobOptions& bpb)
199+
{
200+
assert(isValid());
201+
202+
const auto preparedBpb = prepareBpb(*client, bpb);
203+
handle->setDefaultBpb(&statusWrapper, static_cast<unsigned>(preparedBpb.size()), preparedBpb.data());
204+
}
205+
206+
unsigned Batch::getBlobAlignment()
207+
{
208+
assert(isValid());
209+
return handle->getBlobAlignment(&statusWrapper);
210+
}
211+
212+
213+
// --- Execution ---
214+
215+
BatchCompletionState Batch::execute()
216+
{
217+
assert(isValid());
218+
219+
auto completionState = fbUnique(handle->execute(&statusWrapper, transaction->getHandle().get()));
220+
221+
return BatchCompletionState{*client, std::move(completionState)};
222+
}
223+
224+
void Batch::cancel()
225+
{
226+
assert(isValid());
227+
handle->cancel(&statusWrapper);
228+
handle.reset();
229+
}
230+
231+
void Batch::close()
232+
{
233+
assert(isValid());
234+
handle->close(&statusWrapper);
235+
handle.reset();
236+
}
237+
238+
FbRef<fb::IMessageMetadata> Batch::getInputMetadata()
239+
{
240+
assert(isValid());
241+
242+
FbRef<fb::IMessageMetadata> metadata;
243+
metadata.reset(handle->getMetadata(&statusWrapper));
244+
245+
return metadata;
246+
}
247+
248+
const std::vector<Descriptor>& Batch::getInputDescriptors()
249+
{
250+
assert(isValid());
251+
252+
if (inputDescriptors.empty())
253+
buildInputDescriptors();
254+
255+
return inputDescriptors;
256+
}
257+
258+
259+
// --- Internal helpers ---
260+
261+
std::vector<std::uint8_t> Batch::buildParametersBlock(Client& client, const BatchOptions& options)
262+
{
263+
auto builder = fbUnique(client.getUtil()->getXpbBuilder(&statusWrapper, fb::IXpbBuilder::BATCH, nullptr, 0));
264+
265+
if (options.getMultiError())
266+
builder->insertInt(&statusWrapper, fb::IBatch::TAG_MULTIERROR, 1);
267+
268+
if (options.getRecordCounts())
269+
builder->insertInt(&statusWrapper, fb::IBatch::TAG_RECORD_COUNTS, 1);
270+
271+
if (const auto bufferSize = options.getBufferBytesSize(); bufferSize.has_value())
272+
builder->insertInt(&statusWrapper, fb::IBatch::TAG_BUFFER_BYTES_SIZE, static_cast<int>(bufferSize.value()));
273+
274+
if (options.getBlobPolicy() != BlobPolicy::NONE)
275+
builder->insertInt(&statusWrapper, fb::IBatch::TAG_BLOB_POLICY, static_cast<int>(options.getBlobPolicy()));
276+
277+
if (options.getDetailedErrors() != 64)
278+
{
279+
builder->insertInt(
280+
&statusWrapper, fb::IBatch::TAG_DETAILED_ERRORS, static_cast<int>(options.getDetailedErrors()));
281+
}
282+
283+
const auto buffer = builder->getBuffer(&statusWrapper);
284+
const auto length = builder->getBufferLength(&statusWrapper);
285+
286+
return {buffer, buffer + length};
287+
}
288+
289+
std::vector<std::uint8_t> Batch::prepareBpb(Client& client, const BlobOptions& bpb)
290+
{
291+
auto builder = fbUnique(client.getUtil()->getXpbBuilder(&statusWrapper, fb::IXpbBuilder::BPB,
292+
reinterpret_cast<const std::uint8_t*>(bpb.getBpb().data()), static_cast<unsigned>(bpb.getBpb().size())));
293+
294+
if (const auto type = bpb.getType(); type.has_value())
295+
builder->insertInt(&statusWrapper, isc_bpb_type, static_cast<int>(type.value()));
296+
297+
if (const auto storage = bpb.getStorage(); storage.has_value())
298+
builder->insertInt(&statusWrapper, isc_bpb_storage, static_cast<int>(storage.value()));
299+
300+
const auto buffer = builder->getBuffer(&statusWrapper);
301+
const auto length = builder->getBufferLength(&statusWrapper);
302+
303+
return {buffer, buffer + length};
304+
}
305+
306+
void Batch::buildInputDescriptors()
307+
{
308+
auto metadata = getInputMetadata();
309+
const auto count = metadata->getCount(&statusWrapper);
310+
311+
inputDescriptors.reserve(count);
312+
313+
for (unsigned index = 0u; index < count; ++index)
314+
{
315+
inputDescriptors.push_back(Descriptor{
316+
.originalType = static_cast<DescriptorOriginalType>(metadata->getType(&statusWrapper, index)),
317+
.adjustedType = static_cast<DescriptorAdjustedType>(metadata->getType(&statusWrapper, index)),
318+
.scale = metadata->getScale(&statusWrapper, index),
319+
.length = metadata->getLength(&statusWrapper, index),
320+
.offset = metadata->getOffset(&statusWrapper, index),
321+
.nullOffset = metadata->getNullOffset(&statusWrapper, index),
322+
.isNullable = static_cast<bool>(metadata->isNullable(&statusWrapper, index)),
323+
.name = metadata->getField(&statusWrapper, index),
324+
.relation = metadata->getRelation(&statusWrapper, index),
325+
.alias = metadata->getAlias(&statusWrapper, index),
326+
.owner = metadata->getOwner(&statusWrapper, index),
327+
.charSetId = metadata->getCharSet(&statusWrapper, index),
328+
.subType = metadata->getSubType(&statusWrapper, index),
329+
});
330+
}
331+
}

0 commit comments

Comments
 (0)