26 IMaster *master,
const QByteArray &relName,
27 const QByteArray &fldName, ISC_ARRAY_DESC *desc)
29 std::memset(desc, 0,
sizeof(ISC_ARRAY_DESC));
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);
38 ThrowStatusWrapper st(iStatus);
41
42
43
44
45
46
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>);
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]);
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()),
75 fbRelease<IResultSet>);
76 while (rsG->fetchNext(&st, outBuf.data()) == IStatus::RESULT_OK) {
77 if (!rowFn(outBuf, meta))
80 rsG.closeWith([&](IResultSet *r) { r->close(&st); });
81 stmtG.closeWith([&](IStatement *s) { s->free(&st); });
85
86 const QByteArray sql1 = QByteArrayLiteral(
87 "SELECT f.RDB$FIELD_TYPE, f.RDB$FIELD_SCALE, f.RDB$FIELD_LENGTH, "
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) = ?");
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));
112
113
114 if (!found || desc->array_desc_dimensions <= 0
115 || desc->array_desc_dimensions
116 >
static_cast<
int>(std::size(desc->array_desc_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");
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);
142 }
catch (
const FbException &e) {
143 const auto lerr = fbError(master, e.getStatus(), QSqlError::StatementError);
144 qCWarning(lcFirebird) <<
"lookupArrayDesc:" << fbErrorLog(lerr);
160 ArrayDescCache &descCache,
161 const QByteArray &relName,
const QByteArray &fldName,
162 ISC_ARRAY_DESC *desc)
164 const ArrayFieldKey key = { relName, fldName };
165 const auto it = descCache.constFind(key);
166 if (it != descCache.constEnd()) {
170 if (!lookupArrayDesc(att, tra, iStatus, master, relName, fldName, desc))
172 descCache.insert(key, *desc);
209 const QByteArray &relName,
const QByteArray &fldName,
210 QVarLengthArray<
int> *numElements, qint64 *bufLen)
212 qint64 arraySize = 1;
213 const short dimensions = desc.array_desc_dimensions;
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;
220 qCWarning(lcFirebird,
"%s: invalid bounds for dimension %d of %s.%s",
221 who, i, relName.constData(), fldName.constData());
225 (*numElements)[i] = sub;
229
230
231
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(),
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));
251 short curDim,
const int *numElements,
252 ISC_ARRAY_DESC *desc, IMaster *master,
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;
259 if (curDim < dims - 1) {
261 for (
int i = 0; i < numElements[curDim]; ++i) {
262 QList<QVariant> subList;
263 buffer = readArrayBuffer(subList, buffer, curDim + 1, numElements, desc, master,
265 list.append(QVariant(subList));
272 for (
int i = 0; i < numElements[curDim]; ++i) {
275 while (o > 0 && (buffer[o - 1] ==
' ' || buffer[o - 1] ==
'\0'))
277 list.append(QString::fromUtf8(buffer, o));
283
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);
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);
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);
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);
317 for (
int i = 0; i < numElements[curDim]; ++i) {
318 list.append(
static_cast<
double>(*
reinterpret_cast<
const float *>(buffer)));
319 buffer +=
sizeof(
float);
324 for (
int i = 0; i < numElements[curDim]; ++i) {
325 list.append(*
reinterpret_cast<
const double *>(buffer));
326 buffer +=
sizeof(
double);
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);
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);
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);
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);
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);
364 qCWarning(lcFirebird,
"readArrayBuffer: unsupported BLR type %d", dtype);
365 buffer += strLen * numElements[curDim];
374 QMetaType::Type type,
short curDim,
375 ISC_ARRAY_DESC *desc, IMaster *master,
376 IStatus *iStatus, QString &error)
378 ISC_ARRAY_BOUND *bounds = desc->array_desc_bounds;
379 short dim = desc->array_desc_dimensions - 1;
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());
390 for (
const auto &elem : list) {
391 if (elem.typeId() != QMetaType::QVariantList) {
392 error = u"Array dimensions mismatch"_s;
395 buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1, desc, master,
402
403
404
405
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) {
411 if (dtype == blr_short) {
412 ok = QtPrivate::sqlScaledFromVariant(v, scale,
reinterpret_cast<qint16 *>(buffer),
414 buffer +=
sizeof(qint16);
415 }
else if (dtype == blr_long) {
416 ok = QtPrivate::sqlScaledFromVariant(v, scale,
reinterpret_cast<qint32 *>(buffer),
418 buffer +=
sizeof(qint32);
420 ok = QtPrivate::sqlScaledFromVariant(v, scale,
reinterpret_cast<qint64 *>(buffer),
422 buffer +=
sizeof(qint64);
430 case QMetaType::Short:
431 case QMetaType::UShort:
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);
440 for (
const auto &v : list) {
441 *
reinterpret_cast<qint32 *>(buffer) = v.toInt();
442 buffer +=
sizeof(qint32);
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);
454 for (
const auto &v : list) {
455 *
reinterpret_cast<
double *>(buffer) = v.toDouble();
456 buffer +=
sizeof(
double);
460 case QMetaType::LongLong:
461 for (
const auto &v : list) {
462 *
reinterpret_cast<qint64 *>(buffer) = v.toLongLong();
463 buffer +=
sizeof(qint64);
466 case QMetaType::QString:
467 for (
const auto &v : list) {
468 if (desc->array_desc_dtype == blr_int128) {
470 if (!encodeInt128Slot(v, desc->array_desc_scale, buffer,
471 master, iStatus, error)) {
474 buffer +=
sizeof(FB_I128);
477 if (desc->array_desc_dtype != blr_varying && desc->array_desc_dtype != blr_text) {
479 error = QString::fromLatin1(
"Unsupported array element type %1 for string values")
480 .arg(
static_cast<
int>(desc->array_desc_dtype));
483 const QByteArray utf8 = v.toString().toUtf8();
484 unsigned short len = desc->array_desc_length;
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);
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);
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);
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);
515 *
reinterpret_cast<ISC_TIMESTAMP *>(buffer) =
516 encodeQDateTime(master->getUtilInterface(), dt);
517 buffer +=
sizeof(ISC_TIMESTAMP);
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);
528 error = QString::fromLatin1(
"Unsupported array element type %1").arg(
static_cast<
int>(type));
540 const QByteArray &relationName,
541 const QByteArray &fieldName)
544 sdl.append(
char(isc_sdl_version1));
546 sdl.append(
char(isc_sdl_struct));
549 unsigned char dtype = desc.array_desc_dtype;
551 unsigned char sdlDtype = (dtype == blr_varying) ? blr_text : dtype;
552 sdl.append(
char(sdlDtype));
558 sdl.append(
char(desc.array_desc_scale));
563 sdl.append(
char(desc.array_desc_length & 0xFF));
564 sdl.append(
char((desc.array_desc_length >> 8) & 0xFF));
571 sdl.append(
char(isc_sdl_relation));
572 sdl.append(
char(relationName.size()));
573 sdl.append(relationName);
575 sdl.append(
char(isc_sdl_field));
576 sdl.append(
char(fieldName.size()));
577 sdl.append(fieldName);
579
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;
586 sdl.append(
char(isc_sdl_do1));
590 sdl.append(
char(isc_sdl_do2));
593 if (lo >= -128 && lo <= 127) {
594 sdl.append(
char(isc_sdl_tiny_integer));
595 sdl.append(
char(
static_cast<
signed char>(lo)));
597 sdl.append(
char(isc_sdl_short_integer));
598 sdl.append(
char(lo & 0xFF));
599 sdl.append(
char((lo >> 8) & 0xFF));
603 if (hi >= -128 && hi <= 127) {
604 sdl.append(
char(isc_sdl_tiny_integer));
605 sdl.append(
char(
static_cast<
signed char>(hi)));
607 sdl.append(
char(isc_sdl_short_integer));
608 sdl.append(
char(hi & 0xFF));
609 sdl.append(
char((hi >> 8) & 0xFF));
613 sdl.append(
char(isc_sdl_element));
615 sdl.append(
char(isc_sdl_scalar));
617 sdl.append(
char(dims));
618 for (
short d = 0; d < dims; ++d) {
619 sdl.append(
char(isc_sdl_variable));
622 sdl.append(
char(isc_sdl_eoc));
629 ArrayDescCache &descCache,
630 const ISC_QUAD &arrayId,
const QString &relation,
631 const QString &field)
633 QList<QVariant> list;
634 if (arrayId.gds_quad_high == 0 && arrayId.gds_quad_low == 0)
635 return QVariant(list);
637 QByteArray relBytes = relation.trimmed().toUtf8();
638 QByteArray fldBytes = field.trimmed().toUtf8();
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);
648 QVarLengthArray<
int> numElements;
650 if (!arraySliceSize(desc,
"fetchArray", relBytes, fldBytes, &numElements, &bufLen))
651 return QVariant(list);
654 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
657 QByteArray ba(bufLen,
'\0');
658 ISC_QUAD id = arrayId;
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()),
665 static_cast<
int>(bufLen),
666 reinterpret_cast<
unsigned char *>(ba.data()));
668 }
catch (
const FbException &e) {
670
671 qCWarning(lcFirebird) <<
"fetchArray: getSlice:"
672 << fbErrorLog(fbError(master, e.getStatus(), QSqlError::StatementError));
676 readArrayBuffer(list, ba.constData(), 0, numElements.constData(), &desc, master, iStatus);
677 return QVariant(list);
681bool writeArray(IAttachment *att, ITransaction *tra, IStatus *iStatus,
683 ArrayDescCache &descCache,
684 ISC_QUAD *arrayId,
const QString &relation,
685 const QString &field,
const QList<QVariant> &list)
687 QByteArray relBytes = relation.trimmed().toUtf8();
688 QByteArray fldBytes = field.trimmed().toUtf8();
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());
699 if (!arraySliceSize(desc,
"writeArray", relBytes, fldBytes,
nullptr, &bufLen))
703 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
705 QByteArray ba(bufLen,
'\0');
707 QMetaType::Type elType = blrTypeToQt(desc.array_desc_dtype, desc.array_desc_scale < 0);
709 if (!createArrayBuffer(ba.data(), list, elType, 0, &desc, master, iStatus, error)) {
710 qCWarning(lcFirebird) <<
"writeArray:" << error;
715 ThrowStatusWrapper st(iStatus);
716 att->putSlice(&st, tra, arrayId,
717 static_cast<
unsigned>(sdlBuf.size()),
718 reinterpret_cast<
const unsigned char *>(sdlBuf.constData()),
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);