Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsql_firebird_array.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// Copyright (C) 2026 Andreas Bacher
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
6
8
9#include <QtCore/qvarlengtharray.h>
10
11#include <firebird/impl/blr.h> // BLR type constants for array element types
12
13#include <algorithm>
14#include <cstring>
15#include <limits>
16
17QT_BEGIN_NAMESPACE
18
19// Firebird OO API types are used throughout; avoid full Firebird:: qualification.
20using namespace Firebird;
21
22using namespace Qt::StringLiterals;
23
24// Query Firebird system tables to populate ISC_ARRAY_DESC using OO API directly
25static bool lookupArrayDesc(IAttachment *att, ITransaction *tra, IStatus *iStatus,
26 IMaster *master, const QByteArray &relName,
27 const QByteArray &fldName, ISC_ARRAY_DESC *desc)
28{
29 std::memset(desc, 0, sizeof(ISC_ARRAY_DESC));
30
31 // ISC_ARRAY_DESC expects space-padded names (31 chars)
32 const QByteArray relPadded = relName.leftJustified(31, ' ');
33 const QByteArray fldPadded = fldName.leftJustified(31, ' ');
34 std::memcpy(desc->array_desc_relation_name, relPadded.constData(), 31);
35 std::memcpy(desc->array_desc_field_name, fldPadded.constData(), 31);
36
37 try {
38 ThrowStatusWrapper st(iStatus);
39
40 /* Run a metadata SELECT with relName/fldName bound as the two text
41 parameters — identifiers may legally contain quotes or non-ASCII, so
42 they are never spliced into the SQL text. rowFn(buf, meta) is invoked
43 for each fetched row until it returns false or the cursor is
44 exhausted. The statement, metadata and cursor handles are freed by
45 FbGuard on every path (close on the happy path, release on a throw)
46 — no manual unwinding needed. */
47 auto runQuery = [&](const QByteArray &sql, auto rowFn) {
48 FbGuard<IStatement> stmtG(
49 att->prepare(&st, tra, 0, sql.constData(), SQL_DIALECT_V6,
50 IStatement::PREPARE_PREFETCH_METADATA),
51 fbRelease<IStatement>);
52
53 FbGuard<IMessageMetadata> inMetaG(stmtG->getInputMetadata(&st),
54 fbRelease<IMessageMetadata>);
55 IMessageMetadata *inMeta = inMetaG.get();
56 QByteArray inBuf(inMeta->getMessageLength(&st), '\0');
57 const QByteArray *binds[2] = { &relName, &fldName };
58 const unsigned paramCount = std::min(inMeta->getCount(&st), 2u);
59 for (unsigned i = 0; i < paramCount; ++i) {
60 char *data = inBuf.data() + inMeta->getOffset(&st, i);
61 *reinterpret_cast<short *>(inBuf.data()
62 + inMeta->getNullOffset(&st, i)) = 0;
63 encodeTextValue(data, static_cast<int>(inMeta->getType(&st, i)),
64 inMeta->getLength(&st, i), *binds[i]);
65 }
66
67 FbGuard<IMessageMetadata> metaG(stmtG->getOutputMetadata(&st),
68 fbRelease<IMessageMetadata>);
69 IMessageMetadata *meta = metaG.get();
70 QByteArray outBuf(meta->getMessageLength(&st), '\0');
71 FbGuard<IResultSet> rsG(
72 stmtG->openCursor(&st, tra, inMeta,
73 reinterpret_cast<unsigned char *>(inBuf.data()),
74 meta, 0),
75 fbRelease<IResultSet>);
76 while (rsG->fetchNext(&st, outBuf.data()) == IStatus::RESULT_OK) {
77 if (!rowFn(outBuf, meta))
78 break;
79 }
80 rsG.closeWith([&](IResultSet *r) { r->close(&st); });
81 stmtG.closeWith([&](IStatement *s) { s->free(&st); });
82 };
83
84 /* Query 1: field type/scale/length and dimension count. TRIM for
85 matching — preserves original case for quoted identifiers. */
86 const QByteArray sql1 = QByteArrayLiteral(
87 "SELECT f.RDB$FIELD_TYPE, f.RDB$FIELD_SCALE, f.RDB$FIELD_LENGTH, "
88 "f.RDB$DIMENSIONS "
89 "FROM RDB$RELATION_FIELDS rf "
90 "JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE "
91 "WHERE TRIM(rf.RDB$RELATION_NAME) = ? AND TRIM(rf.RDB$FIELD_NAME) = ?");
92
93 bool found = false;
94 runQuery(sql1, [&](const QByteArray &buf, IMessageMetadata *meta) {
95 const unsigned o0 = meta->getOffset(&st, 0);
96 const unsigned o1 = meta->getOffset(&st, 1);
97 const unsigned o2 = meta->getOffset(&st, 2);
98 const unsigned o3 = meta->getOffset(&st, 3);
99 desc->array_desc_dtype = static_cast<ISC_UCHAR>(
100 *reinterpret_cast<const short *>(buf.constData() + o0));
101 desc->array_desc_scale = static_cast<ISC_SCHAR>(
102 *reinterpret_cast<const short *>(buf.constData() + o1));
103 desc->array_desc_length = static_cast<unsigned short>(
104 *reinterpret_cast<const short *>(buf.constData() + o2));
105 desc->array_desc_dimensions = static_cast<short>(
106 *reinterpret_cast<const short *>(buf.constData() + o3));
107 found = true;
108 return false; // only the first row is needed
109 });
110
111 /* Reject dimension counts the array_desc_bounds array cannot hold
112 (Firebird DDL enforces the same maximum; more means a corrupt or
113 hostile database). */
114 if (!found || desc->array_desc_dimensions <= 0
115 || desc->array_desc_dimensions
116 > static_cast<int>(std::size(desc->array_desc_bounds)))
117 return false;
118
119 // Query 2: dimension bounds.
120 const QByteArray sql2 = QByteArrayLiteral(
121 "SELECT fd.RDB$DIMENSION, fd.RDB$LOWER_BOUND, fd.RDB$UPPER_BOUND "
122 "FROM RDB$RELATION_FIELDS rf "
123 "JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE "
124 "JOIN RDB$FIELD_DIMENSIONS fd ON fd.RDB$FIELD_NAME = f.RDB$FIELD_NAME "
125 "WHERE TRIM(rf.RDB$RELATION_NAME) = ? AND TRIM(rf.RDB$FIELD_NAME) = ? "
126 "ORDER BY fd.RDB$DIMENSION");
127
128 runQuery(sql2, [&](const QByteArray &buf, IMessageMetadata *meta) {
129 const unsigned d0 = meta->getOffset(&st, 0);
130 const unsigned d1 = meta->getOffset(&st, 1);
131 const unsigned d2 = meta->getOffset(&st, 2);
132 const int dim = *reinterpret_cast<const short *>(buf.constData() + d0);
133 if (dim >= 0 && dim < 16) {
134 desc->array_desc_bounds[dim].array_bound_lower =
135 *reinterpret_cast<const short *>(buf.constData() + d1);
136 desc->array_desc_bounds[dim].array_bound_upper =
137 *reinterpret_cast<const short *>(buf.constData() + d2);
138 }
139 return true; // read every row
140 });
141
142 } catch (const FbException &e) {
143 const auto lerr = fbError(master, e.getStatus(), QSqlError::StatementError);
144 qCWarning(lcFirebird) << "lookupArrayDesc:" << fbErrorLog(lerr);
145 iStatus->init();
146 return false;
147 }
148
149 return true;
150}
151
152/*! \internal
153 A column's array descriptor is immutable while the statement lives (it
154 mirrors the column's DDL), so resolve it through a per-result cache:
155 lookupArrayDesc costs two server-side metadata SELECTs per call, which would
156 otherwise be paid for every row fetched and every parameter bound.
157*/
158static bool cachedArrayDesc(IAttachment *att, ITransaction *tra, IStatus *iStatus,
159 IMaster *master,
160 ArrayDescCache &descCache,
161 const QByteArray &relName, const QByteArray &fldName,
162 ISC_ARRAY_DESC *desc)
163{
164 const ArrayFieldKey key = { relName, fldName };
165 const auto it = descCache.constFind(key);
166 if (it != descCache.constEnd()) {
167 *desc = it.value();
168 return true;
169 }
170 if (!lookupArrayDesc(att, tra, iStatus, master, relName, fldName, desc))
171 return false;
172 descCache.insert(key, *desc);
173 return true;
174}
175
176/*! \internal
177 Validate the descriptor's dimension bounds and compute the per-dimension
178 element counts and the total slice byte length in 64-bit: bounds come from
179 system tables, and a wrapped or non-positive total would otherwise allocate
180 a short buffer that the read/write helpers overrun. get/putSlice take an
181 int length, so INT_MAX is the hard upper limit. Shared by fetchArray and
182 writeArray so the safety checks cannot drift apart.
183*/
184static bool arraySliceSize(const ISC_ARRAY_DESC &desc, const char *who,
185 const QByteArray &relName, const QByteArray &fldName,
186 QVarLengthArray<int> *numElements, qint64 *bufLen)
187{
188 qint64 arraySize = 1;
189 const short dimensions = desc.array_desc_dimensions;
190 if (numElements)
191 numElements->resize(dimensions);
192 for (short i = 0; i < dimensions; ++i) {
193 const int sub = desc.array_desc_bounds[i].array_bound_upper -
194 desc.array_desc_bounds[i].array_bound_lower + 1;
195 if (sub <= 0) {
196 qCWarning(lcFirebird, "%s: invalid bounds for dimension %d of %s.%s",
197 who, i, relName.constData(), fldName.constData());
198 return false;
199 }
200 if (numElements)
201 (*numElements)[i] = sub;
202 arraySize *= sub;
203 }
204 *bufLen = qint64(desc.array_desc_length) * arraySize;
205 if (*bufLen <= 0 || *bufLen > (std::numeric_limits<int>::max)()) {
206 qCWarning(lcFirebird, "%s: array %s.%s too large (%lld bytes)",
207 who, relName.constData(), fldName.constData(),
208 static_cast<long long>(*bufLen));
209 return false;
210 }
211 return true;
212}
213
214// Recursive: parse flat buffer into QVariantList according to array dimensions
215static const char *readArrayBuffer(QList<QVariant> &list, const char *buffer,
216 short curDim, const int *numElements,
217 ISC_ARRAY_DESC *desc, IMaster *master,
218 IStatus *iStatus)
219{
220 const short dims = desc->array_desc_dimensions;
221 const unsigned char dtype = desc->array_desc_dtype;
222 unsigned short strLen = desc->array_desc_length;
223
224 if (curDim < dims - 1) {
225 // Non-leaf dimension: build sublists
226 for (int i = 0; i < numElements[curDim]; ++i) {
227 QList<QVariant> subList;
228 buffer = readArrayBuffer(subList, buffer, curDim + 1, numElements, desc, master,
229 iStatus);
230 list.append(QVariant(subList));
231 }
232 } else {
233 // Leaf dimension: read actual values directly into list
234 switch (dtype) {
235 case blr_varying:
236 case blr_text: {
237 for (int i = 0; i < numElements[curDim]; ++i) {
238 // Trim trailing spaces and nulls (text/varying delivered as blr_text via SDL)
239 int o = strLen;
240 while (o > 0 && (buffer[o - 1] == ' ' || buffer[o - 1] == '\0'))
241 --o;
242 list.append(QString::fromUtf8(buffer, o));
243 buffer += strLen;
244 }
245 break;
246 }
247 case blr_short:
248 for (int i = 0; i < numElements[curDim]; ++i) {
249 list.append(static_cast<int>(*reinterpret_cast<const short *>(buffer)));
250 buffer += sizeof(short);
251 }
252 break;
253 case blr_long:
254 for (int i = 0; i < numElements[curDim]; ++i) {
255 list.append(*reinterpret_cast<const int *>(buffer));
256 buffer += sizeof(int);
257 }
258 break;
259 case blr_int64:
260 for (int i = 0; i < numElements[curDim]; ++i) {
261 list.append(*reinterpret_cast<const qint64 *>(buffer));
262 buffer += sizeof(qint64);
263 }
264 break;
265 case blr_float:
266 for (int i = 0; i < numElements[curDim]; ++i) {
267 list.append(static_cast<double>(*reinterpret_cast<const float *>(buffer)));
268 buffer += sizeof(float);
269 }
270 break;
271 case blr_double:
272 case blr_d_float:
273 for (int i = 0; i < numElements[curDim]; ++i) {
274 list.append(*reinterpret_cast<const double *>(buffer));
275 buffer += sizeof(double);
276 }
277 break;
278 case blr_sql_date:
279 for (int i = 0; i < numElements[curDim]; ++i) {
280 list.append(decodeFirebirdDate(master->getUtilInterface(),
281 *reinterpret_cast<const ISC_DATE *>(buffer)));
282 buffer += sizeof(ISC_DATE);
283 }
284 break;
285 case blr_sql_time:
286 for (int i = 0; i < numElements[curDim]; ++i) {
287 list.append(decodeFirebirdTime(master->getUtilInterface(),
288 *reinterpret_cast<const ISC_TIME *>(buffer)));
289 buffer += sizeof(ISC_TIME);
290 }
291 break;
292 case blr_timestamp:
293 for (int i = 0; i < numElements[curDim]; ++i) {
294 list.append(decodeFirebirdTimestamp(master->getUtilInterface(),
295 *reinterpret_cast<const ISC_TIMESTAMP *>(buffer)));
296 buffer += sizeof(ISC_TIMESTAMP);
297 }
298 break;
299 case blr_timestamp_tz:
300 for (int i = 0; i < numElements[curDim]; ++i) {
301 list.append(decodeFirebirdTimestampTz(iStatus, master->getUtilInterface(),
302 *reinterpret_cast<const ISC_TIMESTAMP_TZ *>(buffer)));
303 buffer += sizeof(ISC_TIMESTAMP_TZ);
304 }
305 break;
306 case blr_bool:
307 for (int i = 0; i < numElements[curDim]; ++i) {
308 list.append(*reinterpret_cast<const FB_BOOLEAN *>(buffer) != FB_FALSE);
309 buffer += sizeof(FB_BOOLEAN);
310 }
311 break;
312 default:
313 qCWarning(lcFirebird, "readArrayBuffer: unsupported BLR type %d", dtype);
314 buffer += strLen * numElements[curDim];
315 break;
316 }
317 }
318 return buffer;
319}
320
321// Recursive: serialize QVariantList into flat buffer
322static char *createArrayBuffer(char *buffer, const QList<QVariant> &list,
323 QMetaType::Type type, short curDim,
324 ISC_ARRAY_DESC *desc, IMaster *master,
325 IStatus *iStatus, QString &error)
326{
327 ISC_ARRAY_BOUND *bounds = desc->array_desc_bounds;
328 short dim = desc->array_desc_dimensions - 1;
329
330 const qsizetype elements = bounds[curDim].array_bound_upper
331 - bounds[curDim].array_bound_lower + 1;
332 if (list.size() != elements) {
333 error = QString::fromLatin1("Array size mismatch: expected %1, got %2")
334 .arg(elements).arg(list.size());
335 return nullptr;
336 }
337
338 if (curDim != dim) {
339 for (const auto &elem : list) {
340 if (elem.typeId() != QMetaType::QVariantList) {
341 error = u"Array dimensions mismatch"_s;
342 return nullptr;
343 }
344 buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1, desc, master,
345 iStatus, error);
346 if (!buffer)
347 return nullptr;
348 }
349 } else {
350 switch (type) {
351 case QMetaType::Short:
352 case QMetaType::UShort:
353 case QMetaType::Int:
354 case QMetaType::UInt:
355 if (desc->array_desc_dtype == blr_short) {
356 for (const auto &v : list) {
357 *reinterpret_cast<short *>(buffer) = static_cast<short>(v.toInt());
358 buffer += sizeof(short);
359 }
360 } else {
361 for (const auto &v : list) {
362 *reinterpret_cast<int *>(buffer) = v.toInt();
363 buffer += sizeof(int);
364 }
365 }
366 break;
367 case QMetaType::Float:
368 case QMetaType::Double:
369 if (desc->array_desc_dtype == blr_float) {
370 for (const auto &v : list) {
371 *reinterpret_cast<float *>(buffer) = static_cast<float>(v.toDouble());
372 buffer += sizeof(float);
373 }
374 } else {
375 for (const auto &v : list) {
376 *reinterpret_cast<double *>(buffer) = v.toDouble();
377 buffer += sizeof(double);
378 }
379 }
380 break;
381 case QMetaType::LongLong:
382 for (const auto &v : list) {
383 *reinterpret_cast<qint64 *>(buffer) = v.toLongLong();
384 buffer += sizeof(qint64);
385 }
386 break;
387 case QMetaType::QString:
388 for (const auto &v : list) {
389 const QByteArray utf8 = v.toString().toUtf8();
390 unsigned short len = desc->array_desc_length;
391 if (desc->array_desc_dtype == blr_varying || desc->array_desc_dtype == blr_text) {
392 // Both varying and text use blr_text format in SDL: raw data, space-padded
393 int copyLen = std::min(static_cast<int>(utf8.size()), static_cast<int>(len));
394 std::memcpy(buffer, utf8.constData(), copyLen);
395 if (copyLen < static_cast<int>(len))
396 std::memset(buffer + copyLen, ' ', len - copyLen);
397 buffer += len;
398 }
399 }
400 break;
401 case QMetaType::QDate:
402 for (const auto &v : list) {
403 *reinterpret_cast<ISC_DATE *>(buffer) =
404 encodeQDate(master->getUtilInterface(), v.toDate());
405 buffer += sizeof(ISC_DATE);
406 }
407 break;
408 case QMetaType::QTime:
409 for (const auto &v : list) {
410 *reinterpret_cast<ISC_TIME *>(buffer) =
411 encodeQTime(master->getUtilInterface(), v.toTime());
412 buffer += sizeof(ISC_TIME);
413 }
414 break;
415 case QMetaType::QDateTime:
416 for (const auto &v : list) {
417 QDateTime dt = v.toDateTime();
418 if (desc->array_desc_dtype == blr_timestamp_tz) {
419 *reinterpret_cast<ISC_TIMESTAMP_TZ *>(buffer) =
420 encodeQDateTimeTz(iStatus, master->getUtilInterface(), dt);
421 buffer += sizeof(ISC_TIMESTAMP_TZ);
422 } else {
423 *reinterpret_cast<ISC_TIMESTAMP *>(buffer) =
424 encodeQDateTime(master->getUtilInterface(), dt);
425 buffer += sizeof(ISC_TIMESTAMP);
426 }
427 }
428 break;
429 case QMetaType::Bool:
430 for (const auto &v : list) {
431 *reinterpret_cast<FB_BOOLEAN *>(buffer) = v.toBool() ? FB_TRUE : FB_FALSE;
432 buffer += sizeof(FB_BOOLEAN);
433 }
434 break;
435 default:
436 error = QString::fromLatin1("Unsupported array element type %1").arg(static_cast<int>(type));
437 return nullptr;
438 }
439 }
440 return buffer;
441}
442
443/*! \internal
444 Build SDL (Slice Description Language) binary for array get/putSlice.
445 Supports identifiers up to 255 chars (unlike isc_array_gen_sdl which is limited to 31).
446*/
447static QByteArray buildArraySdl(const ISC_ARRAY_DESC &desc,
448 const QByteArray &relationName,
449 const QByteArray &fieldName)
450{
451 QByteArray sdl;
452 sdl.append(char(isc_sdl_version1));
453 // struct with 1 element type descriptor
454 sdl.append(char(isc_sdl_struct));
455 sdl.append(char(1));
456 // BLR-style element type encoding (varies by type)
457 unsigned char dtype = desc.array_desc_dtype;
458 // For varying columns, request text format in SDL to avoid ISC_VARYING prefix complexity
459 unsigned char sdlDtype = (dtype == blr_varying) ? blr_text : dtype;
460 sdl.append(char(sdlDtype));
461 switch (sdlDtype) {
462 case blr_short: // 7: type + scale
463 case blr_long: // 8: type + scale
464 case blr_int64: // 16: type + scale
465 case blr_int128: // 26: type + scale
466 sdl.append(char(desc.array_desc_scale));
467 break;
468 case blr_text: // 14: type + length(2 LE)
469 case blr_varying: // 37: type + length(2 LE)
470 case blr_cstring: // 40: type + length(2 LE)
471 sdl.append(char(desc.array_desc_length & 0xFF));
472 sdl.append(char((desc.array_desc_length >> 8) & 0xFF));
473 break;
474 // Types with no extra bytes: float, double, date, time, timestamp, bool, etc.
475 default:
476 break;
477 }
478 // relation name
479 sdl.append(char(isc_sdl_relation));
480 sdl.append(char(relationName.size()));
481 sdl.append(relationName);
482 // field name
483 sdl.append(char(isc_sdl_field));
484 sdl.append(char(fieldName.size()));
485 sdl.append(fieldName);
486 /* dimension loops (nested: outermost first, each body is next instruction)
487 Use do1 (lower=1) or do2 (explicit lower), NOT do3 (which adds increment) */
488 short dims = desc.array_desc_dimensions;
489 for (short d = 0; d < dims; ++d) {
490 short lo = desc.array_desc_bounds[d].array_bound_lower;
491 short hi = desc.array_desc_bounds[d].array_bound_upper;
492 if (lo == 1) {
493 // do1: only upper bound, lower defaults to 1, increment defaults to 1
494 sdl.append(char(isc_sdl_do1));
495 sdl.append(char(d));
496 } else {
497 // do2: lower + upper bounds, increment defaults to 1
498 sdl.append(char(isc_sdl_do2));
499 sdl.append(char(d));
500 // lower bound
501 if (lo >= -128 && lo <= 127) {
502 sdl.append(char(isc_sdl_tiny_integer));
503 sdl.append(char(static_cast<signed char>(lo)));
504 } else {
505 sdl.append(char(isc_sdl_short_integer));
506 sdl.append(char(lo & 0xFF));
507 sdl.append(char((lo >> 8) & 0xFF));
508 }
509 }
510 // upper bound
511 if (hi >= -128 && hi <= 127) {
512 sdl.append(char(isc_sdl_tiny_integer));
513 sdl.append(char(static_cast<signed char>(hi)));
514 } else {
515 sdl.append(char(isc_sdl_short_integer));
516 sdl.append(char(hi & 0xFF));
517 sdl.append(char((hi >> 8) & 0xFF));
518 }
519 }
520 // element accessor: element 1, scalar 0 ndim, variable per dim
521 sdl.append(char(isc_sdl_element));
522 sdl.append(char(1));
523 sdl.append(char(isc_sdl_scalar));
524 sdl.append(char(0));
525 sdl.append(char(dims));
526 for (short d = 0; d < dims; ++d) {
527 sdl.append(char(isc_sdl_variable));
528 sdl.append(char(d));
529 }
530 sdl.append(char(isc_sdl_eoc));
531 return sdl;
532}
533
534// Fetch array data via OO API (IAttachment::getSlice)
535QVariant fetchArray(IAttachment *att, ITransaction *tra, IStatus *iStatus,
536 IMaster *master,
537 ArrayDescCache &descCache,
538 const ISC_QUAD &arrayId, const QString &relation,
539 const QString &field)
540{
541 QList<QVariant> list;
542 if (arrayId.gds_quad_high == 0 && arrayId.gds_quad_low == 0)
543 return QVariant(list);
544
545 QByteArray relBytes = relation.trimmed().toUtf8();
546 QByteArray fldBytes = field.trimmed().toUtf8();
547
548 ISC_ARRAY_DESC desc;
549 if (!cachedArrayDesc(att, tra, iStatus, master, descCache, relBytes, fldBytes, &desc)) {
550 qCWarning(lcFirebird, "fetchArray: could not look up array descriptor for %s.%s",
551 relBytes.constData(), fldBytes.constData());
552 return QVariant(list);
553 }
554
555 // With blr_text SDL for varying, no +2 prefix needed
556 QVarLengthArray<int> numElements;
557 qint64 bufLen = 0;
558 if (!arraySliceSize(desc, "fetchArray", relBytes, fldBytes, &numElements, &bufLen))
559 return QVariant(list);
560
561 // Generate SDL with original desc_length
562 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
563
564 // Read array data via OO API
565 QByteArray ba(bufLen, '\0');
566 ISC_QUAD id = arrayId;
567 try {
568 ThrowStatusWrapper st(iStatus);
569 int bytesRead = att->getSlice(&st, tra, &id,
570 static_cast<unsigned>(sdlBuf.size()),
571 reinterpret_cast<const unsigned char *>(sdlBuf.constData()),
572 0, nullptr,
573 static_cast<int>(bufLen),
574 reinterpret_cast<unsigned char *>(ba.data()));
575 Q_UNUSED(bytesRead);
576 } catch (const FbException &e) {
577 /* Surface to the caller (QFirebirdResult::data) rather than returning a
578 silently empty array on a read failure. */
579 qCWarning(lcFirebird) << "fetchArray: getSlice:"
580 << fbErrorLog(fbError(master, e.getStatus(), QSqlError::StatementError));
581 throw;
582 }
583
584 readArrayBuffer(list, ba.constData(), 0, numElements.constData(), &desc, master, iStatus);
585 return QVariant(list);
586}
587
588// Write array data via OO API (IAttachment::putSlice)
589bool writeArray(IAttachment *att, ITransaction *tra, IStatus *iStatus,
590 IMaster *master,
591 ArrayDescCache &descCache,
592 ISC_QUAD *arrayId, const QString &relation,
593 const QString &field, const QList<QVariant> &list)
594{
595 QByteArray relBytes = relation.trimmed().toUtf8();
596 QByteArray fldBytes = field.trimmed().toUtf8();
597
598 ISC_ARRAY_DESC desc;
599 if (!cachedArrayDesc(att, tra, iStatus, master, descCache, relBytes, fldBytes, &desc)) {
600 qCWarning(lcFirebird, "writeArray: could not look up array descriptor for %s.%s",
601 relBytes.constData(), fldBytes.constData());
602 return false;
603 }
604
605 // With blr_text SDL for varying, no +2 prefix needed
606 qint64 bufLen = 0;
607 if (!arraySliceSize(desc, "writeArray", relBytes, fldBytes, nullptr, &bufLen))
608 return false;
609
610 // Generate SDL with original desc_length
611 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
612
613 QByteArray ba(bufLen, '\0');
614
615 QMetaType::Type elType = blrTypeToQt(desc.array_desc_dtype, desc.array_desc_scale < 0);
616 QString error;
617 if (!createArrayBuffer(ba.data(), list, elType, 0, &desc, master, iStatus, error)) {
618 qCWarning(lcFirebird) << "writeArray:" << error;
619 return false;
620 }
621
622 try {
623 ThrowStatusWrapper st(iStatus);
624 att->putSlice(&st, tra, arrayId,
625 static_cast<unsigned>(sdlBuf.size()),
626 reinterpret_cast<const unsigned char *>(sdlBuf.constData()),
627 0, nullptr,
628 static_cast<int>(bufLen),
629 reinterpret_cast<unsigned char *>(ba.data()));
630 } catch (const FbException &e) {
631 const auto werr = fbError(master, e.getStatus(), QSqlError::StatementError);
632 qCWarning(lcFirebird) << "writeArray: putSlice:" << fbErrorLog(werr);
633 return false;
634 }
635
636 return true;
637}
638
639QT_END_NAMESPACE
bool writeArray(IAttachment *att, ITransaction *tra, IStatus *iStatus, IMaster *master, ArrayDescCache &descCache, ISC_QUAD *arrayId, const QString &relation, const QString &field, const QList< QVariant > &list)
QVariant fetchArray(IAttachment *att, ITransaction *tra, IStatus *iStatus, IMaster *master, ArrayDescCache &descCache, const ISC_QUAD &arrayId, const QString &relation, const QString &field)
static const char * readArrayBuffer(QList< QVariant > &list, const char *buffer, short curDim, const int *numElements, ISC_ARRAY_DESC *desc, IMaster *master, IStatus *iStatus)
static bool arraySliceSize(const ISC_ARRAY_DESC &desc, const char *who, const QByteArray &relName, const QByteArray &fldName, QVarLengthArray< int > *numElements, qint64 *bufLen)
static QByteArray buildArraySdl(const ISC_ARRAY_DESC &desc, const QByteArray &relationName, const QByteArray &fieldName)
static bool lookupArrayDesc(IAttachment *att, ITransaction *tra, IStatus *iStatus, IMaster *master, const QByteArray &relName, const QByteArray &fldName, ISC_ARRAY_DESC *desc)
static char * createArrayBuffer(char *buffer, const QList< QVariant > &list, QMetaType::Type type, short curDim, ISC_ARRAY_DESC *desc, IMaster *master, IStatus *iStatus, QString &error)
static bool cachedArrayDesc(IAttachment *att, ITransaction *tra, IStatus *iStatus, IMaster *master, ArrayDescCache &descCache, const QByteArray &relName, const QByteArray &fldName, ISC_ARRAY_DESC *desc)