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);
185 const QByteArray &relName,
const QByteArray &fldName,
186 QVarLengthArray<
int> *numElements, qint64 *bufLen)
188 qint64 arraySize = 1;
189 const short dimensions = desc.array_desc_dimensions;
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;
196 qCWarning(lcFirebird,
"%s: invalid bounds for dimension %d of %s.%s",
197 who, i, relName.constData(), fldName.constData());
201 (*numElements)[i] = sub;
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));
216 short curDim,
const int *numElements,
217 ISC_ARRAY_DESC *desc, IMaster *master,
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;
224 if (curDim < dims - 1) {
226 for (
int i = 0; i < numElements[curDim]; ++i) {
227 QList<QVariant> subList;
228 buffer = readArrayBuffer(subList, buffer, curDim + 1, numElements, desc, master,
230 list.append(QVariant(subList));
237 for (
int i = 0; i < numElements[curDim]; ++i) {
240 while (o > 0 && (buffer[o - 1] ==
' ' || buffer[o - 1] ==
'\0'))
242 list.append(QString::fromUtf8(buffer, o));
248 for (
int i = 0; i < numElements[curDim]; ++i) {
249 list.append(
static_cast<
int>(*
reinterpret_cast<
const short *>(buffer)));
250 buffer +=
sizeof(
short);
254 for (
int i = 0; i < numElements[curDim]; ++i) {
255 list.append(*
reinterpret_cast<
const int *>(buffer));
256 buffer +=
sizeof(
int);
260 for (
int i = 0; i < numElements[curDim]; ++i) {
261 list.append(*
reinterpret_cast<
const qint64 *>(buffer));
262 buffer +=
sizeof(qint64);
266 for (
int i = 0; i < numElements[curDim]; ++i) {
267 list.append(
static_cast<
double>(*
reinterpret_cast<
const float *>(buffer)));
268 buffer +=
sizeof(
float);
273 for (
int i = 0; i < numElements[curDim]; ++i) {
274 list.append(*
reinterpret_cast<
const double *>(buffer));
275 buffer +=
sizeof(
double);
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);
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);
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);
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);
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);
313 qCWarning(lcFirebird,
"readArrayBuffer: unsupported BLR type %d", dtype);
314 buffer += strLen * numElements[curDim];
323 QMetaType::Type type,
short curDim,
324 ISC_ARRAY_DESC *desc, IMaster *master,
325 IStatus *iStatus, QString &error)
327 ISC_ARRAY_BOUND *bounds = desc->array_desc_bounds;
328 short dim = desc->array_desc_dimensions - 1;
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());
339 for (
const auto &elem : list) {
340 if (elem.typeId() != QMetaType::QVariantList) {
341 error = u"Array dimensions mismatch"_s;
344 buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1, desc, master,
351 case QMetaType::Short:
352 case QMetaType::UShort:
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);
361 for (
const auto &v : list) {
362 *
reinterpret_cast<
int *>(buffer) = v.toInt();
363 buffer +=
sizeof(
int);
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);
375 for (
const auto &v : list) {
376 *
reinterpret_cast<
double *>(buffer) = v.toDouble();
377 buffer +=
sizeof(
double);
381 case QMetaType::LongLong:
382 for (
const auto &v : list) {
383 *
reinterpret_cast<qint64 *>(buffer) = v.toLongLong();
384 buffer +=
sizeof(qint64);
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) {
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);
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);
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);
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);
423 *
reinterpret_cast<ISC_TIMESTAMP *>(buffer) =
424 encodeQDateTime(master->getUtilInterface(), dt);
425 buffer +=
sizeof(ISC_TIMESTAMP);
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);
436 error = QString::fromLatin1(
"Unsupported array element type %1").arg(
static_cast<
int>(type));
448 const QByteArray &relationName,
449 const QByteArray &fieldName)
452 sdl.append(
char(isc_sdl_version1));
454 sdl.append(
char(isc_sdl_struct));
457 unsigned char dtype = desc.array_desc_dtype;
459 unsigned char sdlDtype = (dtype == blr_varying) ? blr_text : dtype;
460 sdl.append(
char(sdlDtype));
466 sdl.append(
char(desc.array_desc_scale));
471 sdl.append(
char(desc.array_desc_length & 0xFF));
472 sdl.append(
char((desc.array_desc_length >> 8) & 0xFF));
479 sdl.append(
char(isc_sdl_relation));
480 sdl.append(
char(relationName.size()));
481 sdl.append(relationName);
483 sdl.append(
char(isc_sdl_field));
484 sdl.append(
char(fieldName.size()));
485 sdl.append(fieldName);
487
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;
494 sdl.append(
char(isc_sdl_do1));
498 sdl.append(
char(isc_sdl_do2));
501 if (lo >= -128 && lo <= 127) {
502 sdl.append(
char(isc_sdl_tiny_integer));
503 sdl.append(
char(
static_cast<
signed char>(lo)));
505 sdl.append(
char(isc_sdl_short_integer));
506 sdl.append(
char(lo & 0xFF));
507 sdl.append(
char((lo >> 8) & 0xFF));
511 if (hi >= -128 && hi <= 127) {
512 sdl.append(
char(isc_sdl_tiny_integer));
513 sdl.append(
char(
static_cast<
signed char>(hi)));
515 sdl.append(
char(isc_sdl_short_integer));
516 sdl.append(
char(hi & 0xFF));
517 sdl.append(
char((hi >> 8) & 0xFF));
521 sdl.append(
char(isc_sdl_element));
523 sdl.append(
char(isc_sdl_scalar));
525 sdl.append(
char(dims));
526 for (
short d = 0; d < dims; ++d) {
527 sdl.append(
char(isc_sdl_variable));
530 sdl.append(
char(isc_sdl_eoc));
537 ArrayDescCache &descCache,
538 const ISC_QUAD &arrayId,
const QString &relation,
539 const QString &field)
541 QList<QVariant> list;
542 if (arrayId.gds_quad_high == 0 && arrayId.gds_quad_low == 0)
543 return QVariant(list);
545 QByteArray relBytes = relation.trimmed().toUtf8();
546 QByteArray fldBytes = field.trimmed().toUtf8();
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);
556 QVarLengthArray<
int> numElements;
558 if (!arraySliceSize(desc,
"fetchArray", relBytes, fldBytes, &numElements, &bufLen))
559 return QVariant(list);
562 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
565 QByteArray ba(bufLen,
'\0');
566 ISC_QUAD id = arrayId;
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()),
573 static_cast<
int>(bufLen),
574 reinterpret_cast<
unsigned char *>(ba.data()));
576 }
catch (
const FbException &e) {
578
579 qCWarning(lcFirebird) <<
"fetchArray: getSlice:"
580 << fbErrorLog(fbError(master, e.getStatus(), QSqlError::StatementError));
584 readArrayBuffer(list, ba.constData(), 0, numElements.constData(), &desc, master, iStatus);
585 return QVariant(list);
589bool writeArray(IAttachment *att, ITransaction *tra, IStatus *iStatus,
591 ArrayDescCache &descCache,
592 ISC_QUAD *arrayId,
const QString &relation,
593 const QString &field,
const QList<QVariant> &list)
595 QByteArray relBytes = relation.trimmed().toUtf8();
596 QByteArray fldBytes = field.trimmed().toUtf8();
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());
607 if (!arraySliceSize(desc,
"writeArray", relBytes, fldBytes,
nullptr, &bufLen))
611 QByteArray sdlBuf = buildArraySdl(desc, relBytes, fldBytes);
613 QByteArray ba(bufLen,
'\0');
615 QMetaType::Type elType = blrTypeToQt(desc.array_desc_dtype, desc.array_desc_scale < 0);
617 if (!createArrayBuffer(ba.data(), list, elType, 0, &desc, master, iStatus, error)) {
618 qCWarning(lcFirebird) <<
"writeArray:" << error;
623 ThrowStatusWrapper st(iStatus);
624 att->putSlice(&st, tra, arrayId,
625 static_cast<
unsigned>(sdlBuf.size()),
626 reinterpret_cast<
const unsigned char *>(sdlBuf.constData()),
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);