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 Byte width the array codecs read and write per element of a fixed-width
178 BLR type; 0 for the text types, whose stride is the descriptor's own
179 length, and for types the codecs do not handle.
180*/
181static unsigned fixedElementSize(unsigned char dtype)
182{
183 switch (dtype) {
184 case blr_short: return sizeof(qint16);
185 case blr_long: return sizeof(qint32);
186 case blr_int64: return sizeof(qint64);
187 case blr_int128: return sizeof(FB_I128);
188 case blr_float: return sizeof(float);
189 case blr_double:
190 case blr_d_float: return sizeof(double);
191 case blr_sql_date: return sizeof(ISC_DATE);
192 case blr_sql_time: return sizeof(ISC_TIME);
193 case blr_timestamp: return sizeof(ISC_TIMESTAMP);
194 case blr_timestamp_tz: return sizeof(ISC_TIMESTAMP_TZ);
195 case blr_bool: return sizeof(FB_BOOLEAN);
196 default: return 0;
197 }
198}
199
200/*! \internal
201 Validate the descriptor's dimension bounds and compute the per-dimension
202 element counts and the total slice byte length in 64-bit: bounds come from
203 system tables, and a wrapped or non-positive total would otherwise allocate
204 a short buffer that the read/write helpers overrun. get/putSlice take an
205 int length, so INT_MAX is the hard upper limit. Shared by fetchArray and
206 writeArray so the safety checks cannot drift apart.
207*/
208static bool arraySliceSize(const ISC_ARRAY_DESC &desc, const char *who,
209 const QByteArray &relName, const QByteArray &fldName,
210 QVarLengthArray<int> *numElements, qint64 *bufLen)
211{
212 qint64 arraySize = 1;
213 const short dimensions = desc.array_desc_dimensions;
214 if (numElements)
215 numElements->resize(dimensions);
216 for (short i = 0; i < dimensions; ++i) {
217 const int sub = desc.array_desc_bounds[i].array_bound_upper -
218 desc.array_desc_bounds[i].array_bound_lower + 1;
219 if (sub <= 0) {
220 qCWarning(lcFirebird, "%s: invalid bounds for dimension %d of %s.%s",
221 who, i, relName.constData(), fldName.constData());
222 return false;
223 }
224 if (numElements)
225 (*numElements)[i] = sub;
226 arraySize *= sub;
227 }
228 /* The codecs read and write sizeof(<element type>) bytes per element of a
229 fixed-width type, while the buffer is sized from the metadata-supplied
230 element length: reject a descriptor where the two disagree (corrupt or
231 hostile database), or the element loops would overrun the slice buffer. */
232 if (const unsigned fixed = fixedElementSize(desc.array_desc_dtype);
233 fixed != 0 && desc.array_desc_length != fixed) {
234 qCWarning(lcFirebird, "%s: element length %d of %s.%s does not match its type (%u bytes)",
235 who, int(desc.array_desc_length), relName.constData(), fldName.constData(),
236 fixed);
237 return false;
238 }
239 *bufLen = qint64(desc.array_desc_length) * arraySize;
240 if (*bufLen <= 0 || *bufLen > (std::numeric_limits<int>::max)()) {
241 qCWarning(lcFirebird, "%s: array %s.%s too large (%lld bytes)",
242 who, relName.constData(), fldName.constData(),
243 static_cast<long long>(*bufLen));
244 return false;
245 }
246 return true;
247}
248
249// Recursive: parse flat buffer into QVariantList according to array dimensions
250static const char *readArrayBuffer(QList<QVariant> &list, const char *buffer,
251 short curDim, const int *numElements,
252 ISC_ARRAY_DESC *desc, IMaster *master,
253 IStatus *iStatus)
254{
255 const short dims = desc->array_desc_dimensions;
256 const unsigned char dtype = desc->array_desc_dtype;
257 unsigned short strLen = desc->array_desc_length;
258
259 if (curDim < dims - 1) {
260 // Non-leaf dimension: build sublists
261 for (int i = 0; i < numElements[curDim]; ++i) {
262 QList<QVariant> subList;
263 buffer = readArrayBuffer(subList, buffer, curDim + 1, numElements, desc, master,
264 iStatus);
265 list.append(QVariant(subList));
266 }
267 } else {
268 // Leaf dimension: read actual values directly into list
269 switch (dtype) {
270 case blr_varying:
271 case blr_text: {
272 for (int i = 0; i < numElements[curDim]; ++i) {
273 // Trim trailing spaces and nulls (text/varying delivered as blr_text via SDL)
274 int o = strLen;
275 while (o > 0 && (buffer[o - 1] == ' ' || buffer[o - 1] == '\0'))
276 --o;
277 list.append(QString::fromUtf8(buffer, o));
278 buffer += strLen;
279 }
280 break;
281 }
282 /* Integer elements; a scaled one (NUMERIC/DECIMAL array) surfaces as the
283 double blrTypeToQt reports for it, an unscaled one as the integer. */
284 case blr_short:
285 for (int i = 0; i < numElements[curDim]; ++i) {
286 const auto v = *reinterpret_cast<const qint16 *>(buffer);
287 list.append(QtPrivate::sqlApplyScale(v, desc->array_desc_scale,
288 QSql::LowPrecisionDouble));
289 buffer += sizeof(qint16);
290 }
291 break;
292 case blr_long:
293 for (int i = 0; i < numElements[curDim]; ++i) {
294 const auto v = *reinterpret_cast<const qint32 *>(buffer);
295 list.append(QtPrivate::sqlApplyScale(v, desc->array_desc_scale,
296 QSql::LowPrecisionDouble));
297 buffer += sizeof(qint32);
298 }
299 break;
300 case blr_int64:
301 for (int i = 0; i < numElements[curDim]; ++i) {
302 const auto v = *reinterpret_cast<const qint64 *>(buffer);
303 list.append(QtPrivate::sqlApplyScale(v, desc->array_desc_scale,
304 QSql::LowPrecisionDouble));
305 buffer += sizeof(qint64);
306 }
307 break;
308 case blr_int128:
309 // Exact decimal strings, scaled like the column (see INT128 codec)
310 for (int i = 0; i < numElements[curDim]; ++i) {
311 list.append(decodeInt128Slot(buffer, desc->array_desc_scale,
312 QSql::HighPrecision, master, iStatus));
313 buffer += sizeof(FB_I128);
314 }
315 break;
316 case blr_float:
317 for (int i = 0; i < numElements[curDim]; ++i) {
318 list.append(static_cast<double>(*reinterpret_cast<const float *>(buffer)));
319 buffer += sizeof(float);
320 }
321 break;
322 case blr_double:
323 case blr_d_float:
324 for (int i = 0; i < numElements[curDim]; ++i) {
325 list.append(*reinterpret_cast<const double *>(buffer));
326 buffer += sizeof(double);
327 }
328 break;
329 case blr_sql_date:
330 for (int i = 0; i < numElements[curDim]; ++i) {
331 list.append(decodeFirebirdDate(master->getUtilInterface(),
332 *reinterpret_cast<const ISC_DATE *>(buffer)));
333 buffer += sizeof(ISC_DATE);
334 }
335 break;
336 case blr_sql_time:
337 for (int i = 0; i < numElements[curDim]; ++i) {
338 list.append(decodeFirebirdTime(master->getUtilInterface(),
339 *reinterpret_cast<const ISC_TIME *>(buffer)));
340 buffer += sizeof(ISC_TIME);
341 }
342 break;
343 case blr_timestamp:
344 for (int i = 0; i < numElements[curDim]; ++i) {
345 list.append(decodeFirebirdTimestamp(master->getUtilInterface(),
346 *reinterpret_cast<const ISC_TIMESTAMP *>(buffer)));
347 buffer += sizeof(ISC_TIMESTAMP);
348 }
349 break;
350 case blr_timestamp_tz:
351 for (int i = 0; i < numElements[curDim]; ++i) {
352 list.append(decodeFirebirdTimestampTz(iStatus, master->getUtilInterface(),
353 *reinterpret_cast<const ISC_TIMESTAMP_TZ *>(buffer)));
354 buffer += sizeof(ISC_TIMESTAMP_TZ);
355 }
356 break;
357 case blr_bool:
358 for (int i = 0; i < numElements[curDim]; ++i) {
359 list.append(*reinterpret_cast<const FB_BOOLEAN *>(buffer) != FB_FALSE);
360 buffer += sizeof(FB_BOOLEAN);
361 }
362 break;
363 default:
364 qCWarning(lcFirebird, "readArrayBuffer: unsupported BLR type %d", dtype);
365 buffer += strLen * numElements[curDim];
366 break;
367 }
368 }
369 return buffer;
370}
371
372// Recursive: serialize QVariantList into flat buffer
373static char *createArrayBuffer(char *buffer, const QList<QVariant> &list,
374 QMetaType::Type type, short curDim,
375 ISC_ARRAY_DESC *desc, IMaster *master,
376 IStatus *iStatus, QString &error)
377{
378 ISC_ARRAY_BOUND *bounds = desc->array_desc_bounds;
379 short dim = desc->array_desc_dimensions - 1;
380
381 const qsizetype elements = bounds[curDim].array_bound_upper
382 - bounds[curDim].array_bound_lower + 1;
383 if (list.size() != elements) {
384 error = QString::fromLatin1("Array size mismatch: expected %1, got %2")
385 .arg(elements).arg(list.size());
386 return nullptr;
387 }
388
389 if (curDim != dim) {
390 for (const auto &elem : list) {
391 if (elem.typeId() != QMetaType::QVariantList) {
392 error = u"Array dimensions mismatch"_s;
393 return nullptr;
394 }
395 buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1, desc, master,
396 iStatus, error);
397 if (!buffer)
398 return nullptr;
399 }
400 } else {
401 /* Scaled SMALLINT/INTEGER/BIGINT elements (NUMERIC/DECIMAL arrays) map
402 to QMetaType::Double, but the slice holds the backing integer at its
403 own width. Encode each element exactly at that width, like the INT128
404 branch below, so the stride matches the descriptor; the Double case
405 would write 8 bytes into 2- or 4-byte slots. */
406 const unsigned char dtype = desc->array_desc_dtype;
407 const int scale = desc->array_desc_scale;
408 if (scale != 0 && (dtype == blr_short || dtype == blr_long || dtype == blr_int64)) {
409 for (const auto &v : list) {
410 bool ok = false;
411 if (dtype == blr_short) {
412 ok = QtPrivate::sqlScaledFromVariant(v, scale, reinterpret_cast<qint16 *>(buffer),
413 &error);
414 buffer += sizeof(qint16);
415 } else if (dtype == blr_long) {
416 ok = QtPrivate::sqlScaledFromVariant(v, scale, reinterpret_cast<qint32 *>(buffer),
417 &error);
418 buffer += sizeof(qint32);
419 } else {
420 ok = QtPrivate::sqlScaledFromVariant(v, scale, reinterpret_cast<qint64 *>(buffer),
421 &error);
422 buffer += sizeof(qint64);
423 }
424 if (!ok)
425 return nullptr;
426 }
427 return buffer;
428 }
429 switch (type) {
430 case QMetaType::Short:
431 case QMetaType::UShort:
432 case QMetaType::Int:
433 case QMetaType::UInt:
434 if (desc->array_desc_dtype == blr_short) {
435 for (const auto &v : list) {
436 *reinterpret_cast<qint16 *>(buffer) = static_cast<qint16>(v.toInt());
437 buffer += sizeof(qint16);
438 }
439 } else {
440 for (const auto &v : list) {
441 *reinterpret_cast<qint32 *>(buffer) = v.toInt();
442 buffer += sizeof(qint32);
443 }
444 }
445 break;
446 case QMetaType::Float:
447 case QMetaType::Double:
448 if (desc->array_desc_dtype == blr_float) {
449 for (const auto &v : list) {
450 *reinterpret_cast<float *>(buffer) = static_cast<float>(v.toDouble());
451 buffer += sizeof(float);
452 }
453 } else {
454 for (const auto &v : list) {
455 *reinterpret_cast<double *>(buffer) = v.toDouble();
456 buffer += sizeof(double);
457 }
458 }
459 break;
460 case QMetaType::LongLong:
461 for (const auto &v : list) {
462 *reinterpret_cast<qint64 *>(buffer) = v.toLongLong();
463 buffer += sizeof(qint64);
464 }
465 break;
466 case QMetaType::QString:
467 for (const auto &v : list) {
468 if (desc->array_desc_dtype == blr_int128) {
469 // INT128 elements arrive as strings or numbers; encode exactly
470 if (!encodeInt128Slot(v, desc->array_desc_scale, buffer,
471 master, iStatus, error)) {
472 return nullptr;
473 }
474 buffer += sizeof(FB_I128);
475 continue;
476 }
477 if (desc->array_desc_dtype != blr_varying && desc->array_desc_dtype != blr_text) {
478 // Never silently skip: a non-advanced slot misaligns every later element.
479 error = QString::fromLatin1("Unsupported array element type %1 for string values")
480 .arg(static_cast<int>(desc->array_desc_dtype));
481 return nullptr;
482 }
483 const QByteArray utf8 = v.toString().toUtf8();
484 unsigned short len = desc->array_desc_length;
485 // Both varying and text use blr_text format in SDL: raw data, space-padded
486 int copyLen = (std::min)(static_cast<int>(utf8.size()), static_cast<int>(len));
487 std::memcpy(buffer, utf8.constData(), copyLen);
488 if (copyLen < static_cast<int>(len))
489 std::memset(buffer + copyLen, ' ', len - copyLen);
490 buffer += len;
491 }
492 break;
493 case QMetaType::QDate:
494 for (const auto &v : list) {
495 *reinterpret_cast<ISC_DATE *>(buffer) =
496 encodeQDate(master->getUtilInterface(), v.toDate());
497 buffer += sizeof(ISC_DATE);
498 }
499 break;
500 case QMetaType::QTime:
501 for (const auto &v : list) {
502 *reinterpret_cast<ISC_TIME *>(buffer) =
503 encodeQTime(master->getUtilInterface(), v.toTime());
504 buffer += sizeof(ISC_TIME);
505 }
506 break;
507 case QMetaType::QDateTime:
508 for (const auto &v : list) {
509 QDateTime dt = v.toDateTime();
510 if (desc->array_desc_dtype == blr_timestamp_tz) {
511 *reinterpret_cast<ISC_TIMESTAMP_TZ *>(buffer) =
512 encodeQDateTimeTz(iStatus, master->getUtilInterface(), dt);
513 buffer += sizeof(ISC_TIMESTAMP_TZ);
514 } else {
515 *reinterpret_cast<ISC_TIMESTAMP *>(buffer) =
516 encodeQDateTime(master->getUtilInterface(), dt);
517 buffer += sizeof(ISC_TIMESTAMP);
518 }
519 }
520 break;
521 case QMetaType::Bool:
522 for (const auto &v : list) {
523 *reinterpret_cast<FB_BOOLEAN *>(buffer) = v.toBool() ? FB_TRUE : FB_FALSE;
524 buffer += sizeof(FB_BOOLEAN);
525 }
526 break;
527 default:
528 error = QString::fromLatin1("Unsupported array element type %1").arg(static_cast<int>(type));
529 return nullptr;
530 }
531 }
532 return buffer;
533}
534
535/*! \internal
536 Build SDL (Slice Description Language) binary for array get/putSlice.
537 Supports identifiers up to 255 chars (unlike isc_array_gen_sdl which is limited to 31).
538*/
539static QByteArray buildArraySdl(const ISC_ARRAY_DESC &desc,
540 const QByteArray &relationName,
541 const QByteArray &fieldName)
542{
543 QByteArray sdl;
544 sdl.append(char(isc_sdl_version1));
545 // struct with 1 element type descriptor
546 sdl.append(char(isc_sdl_struct));
547 sdl.append(char(1));
548 // BLR-style element type encoding (varies by type)
549 unsigned char dtype = desc.array_desc_dtype;
550 // For varying columns, request text format in SDL to avoid ISC_VARYING prefix complexity
551 unsigned char sdlDtype = (dtype == blr_varying) ? blr_text : dtype;
552 sdl.append(char(sdlDtype));
553 switch (sdlDtype) {
554 case blr_short: // 7: type + scale
555 case blr_long: // 8: type + scale
556 case blr_int64: // 16: type + scale
557 case blr_int128: // 26: type + scale
558 sdl.append(char(desc.array_desc_scale));
559 break;
560 case blr_text: // 14: type + length(2 LE)
561 case blr_varying: // 37: type + length(2 LE)
562 case blr_cstring: // 40: type + length(2 LE)
563 sdl.append(char(desc.array_desc_length & 0xFF));
564 sdl.append(char((desc.array_desc_length >> 8) & 0xFF));
565 break;
566 // Types with no extra bytes: float, double, date, time, timestamp, bool, etc.
567 default:
568 break;
569 }
570 // relation name
571 sdl.append(char(isc_sdl_relation));
572 sdl.append(char(relationName.size()));
573 sdl.append(relationName);
574 // field name
575 sdl.append(char(isc_sdl_field));
576 sdl.append(char(fieldName.size()));
577 sdl.append(fieldName);
578 /* dimension loops (nested: outermost first, each body is next instruction)
579 Use do1 (lower=1) or do2 (explicit lower), NOT do3 (which adds increment) */
580 short dims = desc.array_desc_dimensions;
581 for (short d = 0; d < dims; ++d) {
582 short lo = desc.array_desc_bounds[d].array_bound_lower;
583 short hi = desc.array_desc_bounds[d].array_bound_upper;
584 if (lo == 1) {
585 // do1: only upper bound, lower defaults to 1, increment defaults to 1
586 sdl.append(char(isc_sdl_do1));
587 sdl.append(char(d));
588 } else {
589 // do2: lower + upper bounds, increment defaults to 1
590 sdl.append(char(isc_sdl_do2));
591 sdl.append(char(d));
592 // lower bound
593 if (lo >= -128 && lo <= 127) {
594 sdl.append(char(isc_sdl_tiny_integer));
595 sdl.append(char(static_cast<signed char>(lo)));
596 } else {
597 sdl.append(char(isc_sdl_short_integer));
598 sdl.append(char(lo & 0xFF));
599 sdl.append(char((lo >> 8) & 0xFF));
600 }
601 }
602 // upper bound
603 if (hi >= -128 && hi <= 127) {
604 sdl.append(char(isc_sdl_tiny_integer));
605 sdl.append(char(static_cast<signed char>(hi)));
606 } else {
607 sdl.append(char(isc_sdl_short_integer));
608 sdl.append(char(hi & 0xFF));
609 sdl.append(char((hi >> 8) & 0xFF));
610 }
611 }
612 // element accessor: element 1, scalar 0 ndim, variable per dim
613 sdl.append(char(isc_sdl_element));
614 sdl.append(char(1));
615 sdl.append(char(isc_sdl_scalar));
616 sdl.append(char(0));
617 sdl.append(char(dims));
618 for (short d = 0; d < dims; ++d) {
619 sdl.append(char(isc_sdl_variable));
620 sdl.append(char(d));
621 }
622 sdl.append(char(isc_sdl_eoc));
623 return sdl;
624}
625
626// Fetch array data via OO API (IAttachment::getSlice)
627QVariant fetchArray(IAttachment *att, ITransaction *tra, IStatus *iStatus,
628 IMaster *master,
629 ArrayDescCache &descCache,
630 const ISC_QUAD &arrayId, const QString &relation,
631 const QString &field)
632{
633 QList<QVariant> list;
634 if (arrayId.gds_quad_high == 0 && arrayId.gds_quad_low == 0)
635 return QVariant(list);
636
637 QByteArray relBytes = relation.trimmed().toUtf8();
638 QByteArray fldBytes = field.trimmed().toUtf8();
639
640 ISC_ARRAY_DESC desc;
641 if (!cachedArrayDesc(att, tra, iStatus, master, descCache, relBytes, fldBytes, &desc)) {
642 qCWarning(lcFirebird, "fetchArray: could not look up array descriptor for %s.%s",
643 relBytes.constData(), fldBytes.constData());
644 return QVariant(list);
645 }
646
647 // With blr_text SDL for varying, no +2 prefix needed
648 QVarLengthArray<int> numElements;
649 qint64 bufLen = 0;
650 if (!arraySliceSize(desc, "fetchArray", relBytes, fldBytes, &numElements, &bufLen))
651 return QVariant(list);
652
653 // Generate SDL with original desc_length
654 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
655
656 // Read array data via OO API
657 QByteArray ba(bufLen, '\0');
658 ISC_QUAD id = arrayId;
659 try {
660 ThrowStatusWrapper st(iStatus);
661 int bytesRead = att->getSlice(&st, tra, &id,
662 static_cast<unsigned>(sdlBuf.size()),
663 reinterpret_cast<const unsigned char *>(sdlBuf.constData()),
664 0, nullptr,
665 static_cast<int>(bufLen),
666 reinterpret_cast<unsigned char *>(ba.data()));
667 Q_UNUSED(bytesRead);
668 } catch (const FbException &e) {
669 /* Surface to the caller (QFirebirdResult::data) rather than returning a
670 silently empty array on a read failure. */
671 qCWarning(lcFirebird) << "fetchArray: getSlice:"
672 << fbErrorLog(fbError(master, e.getStatus(), QSqlError::StatementError));
673 throw;
674 }
675
676 readArrayBuffer(list, ba.constData(), 0, numElements.constData(), &desc, master, iStatus);
677 return QVariant(list);
678}
679
680// Write array data via OO API (IAttachment::putSlice)
681bool writeArray(IAttachment *att, ITransaction *tra, IStatus *iStatus,
682 IMaster *master,
683 ArrayDescCache &descCache,
684 ISC_QUAD *arrayId, const QString &relation,
685 const QString &field, const QList<QVariant> &list)
686{
687 QByteArray relBytes = relation.trimmed().toUtf8();
688 QByteArray fldBytes = field.trimmed().toUtf8();
689
690 ISC_ARRAY_DESC desc;
691 if (!cachedArrayDesc(att, tra, iStatus, master, descCache, relBytes, fldBytes, &desc)) {
692 qCWarning(lcFirebird, "writeArray: could not look up array descriptor for %s.%s",
693 relBytes.constData(), fldBytes.constData());
694 return false;
695 }
696
697 // With blr_text SDL for varying, no +2 prefix needed
698 qint64 bufLen = 0;
699 if (!arraySliceSize(desc, "writeArray", relBytes, fldBytes, nullptr, &bufLen))
700 return false;
701
702 // Generate SDL with original desc_length
703 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
704
705 QByteArray ba(bufLen, '\0');
706
707 QMetaType::Type elType = blrTypeToQt(desc.array_desc_dtype, desc.array_desc_scale < 0);
708 QString error;
709 if (!createArrayBuffer(ba.data(), list, elType, 0, &desc, master, iStatus, error)) {
710 qCWarning(lcFirebird) << "writeArray:" << error;
711 return false;
712 }
713
714 try {
715 ThrowStatusWrapper st(iStatus);
716 att->putSlice(&st, tra, arrayId,
717 static_cast<unsigned>(sdlBuf.size()),
718 reinterpret_cast<const unsigned char *>(sdlBuf.constData()),
719 0, nullptr,
720 static_cast<int>(bufLen),
721 reinterpret_cast<unsigned char *>(ba.data()));
722 } catch (const FbException &e) {
723 const auto werr = fbError(master, e.getStatus(), QSqlError::StatementError);
724 qCWarning(lcFirebird) << "writeArray: putSlice:" << fbErrorLog(werr);
725 return false;
726 }
727
728 return true;
729}
730
731QT_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 unsigned fixedElementSize(unsigned char dtype)
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)