29 QList<ColumnInfo> cols;
30 unsigned count = meta->getCount(&st);
32 for (
unsigned i = 0; i < count; ++i) {
34 ci.name = QString::fromUtf8(meta->getAlias(&st, i));
35 if (ci.name.isEmpty())
36 ci.name = QString::fromUtf8(meta->getField(&st, i));
37 ci.relation = QString::fromUtf8(meta->getRelation(&st, i));
38 ci.field = QString::fromUtf8(meta->getField(&st, i));
39 ci
.fbType =
static_cast<
int>(meta->getType(&st, i));
41 ci
.scale = meta->getScale(&st, i);
42 ci
.length = meta->getLength(&st, i);
43 ci
.offset = meta->getOffset(&st, i);
45 ci.nullable = (meta->isNullable(&st, i) != FB_FALSE);
46 ci.qtType = fbTypeToQt(ci.fbType, ci.scale);
261 Q_D(QFirebirdResult);
265
266
267 if (
const TxnOp op = classifyTxnControl(query); op != TxnOp::None) {
273 const QByteArray utf8 = query.toUtf8();
275 ThrowStatusWrapper st(d->status());
276 ITransaction *tr = d->ensureTransaction();
277 d->stmt = d->att()->prepare(&st, tr, 0, utf8.constData(),
279 IStatement::PREPARE_PREFETCH_METADATA);
280 unsigned flags = d->stmt->getFlags(&st);
281 d->isSelect = (flags & IStatement::FLAG_HAS_CURSOR) != 0;
282 setSelect(d->isSelect);
284 d->inMeta = d->stmt->getInputMetadata(&st);
285 if (d->inMeta && d->inMeta->getCount(&st) == 0) {
286 d->inMeta->release();
289 d->outMeta = d->stmt->getOutputMetadata(&st);
291 if (d->outMeta->getCount(&st) == 0) {
292 d->outMeta->release();
293 d->outMeta =
nullptr;
295 d->cols = buildColumns(d->outMeta, st);
296 d->outBuffer.resize(
static_cast<qsizetype>(d->outMeta->getAlignedLength(&st)));
299 if (!d->isSelect && d->outMeta) {
301 d->isProcExec =
true;
305 d->inBuffer.resize(
static_cast<qsizetype>(d->inMeta->getAlignedLength(&st)));
306 d->inCols = buildColumns(d->inMeta, st);
308 }
catch (
const FbException &e) {
309 const QSqlError err = fbError(d->master(), e.getStatus(), QSqlError::StatementError);
310 qCInfo(lcFirebird) <<
"prepare:" << fbErrorLog(err);
312
313
331 const QVariant &val, QString &errorText)
334
335
336
337
338 if (fbBaseType(fbType) == SQL_INT128) {
340 const QByteArray strVal = numericInputString(val);
341 ThrowStatusWrapper innerSt(status());
342 master()->getUtilInterface()->getInt128(&innerSt)->fromString(
343 &innerSt, scale, strVal.constData(),
344 reinterpret_cast<FB_I128 *>(data));
348 const auto outOfRange = [&]() {
349 errorText = u"Value '%1' out of range for NUMERIC/DECIMAL with scale %2"_s
350 .arg(val.toString(), QString::number(scale));
355 const int t = val.typeId();
356 if (scale < 0 && -scale < 19
357 && (t == QMetaType::Int || t == QMetaType::UInt || t == QMetaType::LongLong)) {
359
360 const qint64 v = val.toLongLong();
361 const qint64 factor = pow10i(-scale);
362 if (v == (std::numeric_limits<qint64>::min)()
363 || qAbs(v) > (std::numeric_limits<qint64>::max)() / factor) {
368 const QByteArray strVal = numericInputString(val);
369 if (!decimalToScaledInt64(strVal, scale, &iv)) {
370 errorText = u"Cannot encode '%1' as NUMERIC/DECIMAL with scale %2"_s
371 .arg(QString::fromLatin1(strVal), QString::number(scale));
377 qint64 lo = (std::numeric_limits<qint64>::min)();
378 qint64 hi = (std::numeric_limits<qint64>::max)();
381 lo = (std::numeric_limits<qint16>::min)();
382 hi = (std::numeric_limits<qint16>::max)();
385 lo = (std::numeric_limits<qint32>::min)();
386 hi = (std::numeric_limits<qint32>::max)();
391 if (iv < lo || iv > hi)
396 *
reinterpret_cast<qint16 *>(data) =
static_cast<qint16>(iv);
399 *
reinterpret_cast<qint32 *>(data) =
static_cast<qint32>(iv);
402 *
reinterpret_cast<qint64 *>(data) = iv;
451 BlobWriter blobWriter)
456 const qsizetype count = inCols.size();
459
460
461
462 if (vals.size() < count) {
463 errorText = u"Parameter count mismatch: statement expects %1, %2 bound"_s
464 .arg(count).arg(vals.size());
468 for (qsizetype i = 0; i < count; ++i) {
470 short *nullFlag =
reinterpret_cast<
short *>(inBuffer.data() + ci.nullOffset);
471 const QVariant &val = vals[i];
474
475
476
477 bool valueIsNull = val.isNull();
479 switch (val.metaType().id()) {
480 case QMetaType::QString: valueIsNull = val.toString().isNull();
break;
481 case QMetaType::QByteArray: valueIsNull = val.toByteArray().isNull();
break;
482 case QMetaType::QDateTime: valueIsNull = !val.toDateTime().isValid();
break;
483 case QMetaType::QDate: valueIsNull = !val.toDate().isValid();
break;
484 case QMetaType::QTime: valueIsNull = !val.toTime().isValid();
break;
490
491
492
493
494
501 char *data = inBuffer.data() + ci.offset;
505 if (!encodeScaledNumeric(data, ci.fbType, ci.scale, val, numError)) {
506 errorText = u"Parameter %1: %2"_s.arg(QString::number(i + 1), numError);
514 *
reinterpret_cast<qint16 *>(data) =
static_cast<qint16>(val.toInt());
517 *
reinterpret_cast<qint32 *>(data) =
static_cast<qint32>(val.toInt());
520 *
reinterpret_cast<qint64 *>(data) = val.toLongLong();
524 const QByteArray strVal = numericInputString(val);
525 ThrowStatusWrapper innerSt(status());
526 master()->getUtilInterface()->getInt128(&innerSt)->fromString(
527 &innerSt, 0, strVal.constData(),
528 reinterpret_cast<FB_I128 *>(data));
532 *
reinterpret_cast<
float *>(data) =
static_cast<
float>(val.toDouble());
535 *
reinterpret_cast<
double *>(data) = val.toDouble();
538 ThrowStatusWrapper innerSt(status());
539 decFloatFromString(master()->getUtilInterface()->getDecFloat16(&innerSt), innerSt,
540 numericInputString(val),
reinterpret_cast<FB_DEC16 *>(data));
544 ThrowStatusWrapper innerSt(status());
545 decFloatFromString(master()->getUtilInterface()->getDecFloat34(&innerSt), innerSt,
546 numericInputString(val),
reinterpret_cast<FB_DEC34 *>(data));
550 *
reinterpret_cast<FB_BOOLEAN *>(data) =
551 val.toBool() ? FB_TRUE : FB_FALSE;
554 *
reinterpret_cast<ISC_DATE *>(data) =
555 encodeQDate(master()->getUtilInterface(), val.toDate());
558 *
reinterpret_cast<ISC_TIME *>(data) =
559 encodeQTime(master()->getUtilInterface(), val.toTime());
562 *
reinterpret_cast<ISC_TIMESTAMP *>(data) =
563 encodeQDateTime(master()->getUtilInterface(), val.toDateTime());
565 case SQL_TIMESTAMP_TZ:
566 *
reinterpret_cast<ISC_TIMESTAMP_TZ *>(data) =
567 encodeQDateTimeTz(status(), master()->getUtilInterface(), val.toDateTime());
570 *
reinterpret_cast<ISC_TIME_TZ *>(data) =
571 encodeQTimeTz(status(), master()->getUtilInterface(), val.toDateTime());
575 encodeTextValue(data, ci
.fbType, ci
.length, val.toString().toUtf8());
578 const QByteArray blobData = val.toByteArray();
579 ISC_QUAD &blobId = *
reinterpret_cast<ISC_QUAD *>(data);
582
583 if (!blobWriter(blobId, blobData)) {
584 errorText = u"Failed to write BLOB for parameter %1"_s.arg(i + 1);
588 writeInlineBlob(blobId, blobData);
593 if (val.typeId() != QMetaType::QVariantList) {
594 errorText = u"Cannot bind non-list value to ARRAY parameter %1"_s.arg(i + 1);
597 ISC_QUAD &arrId = *
reinterpret_cast<ISC_QUAD *>(data);
598 ITransaction *tr = ensureTransaction();
599 if (!writeArray(att(), tr, status(), master(), arrayDescCache,
600 &arrId, ci.relation, ci.field, val.toList())) {
601 errorText = u"Failed to write ARRAY parameter %1"_s.arg(i + 1);
640 Q_D(QFirebirdResult);
643
644
645
646 if (d->txnOp != TxnOp::None) {
647 ITransaction *&iTrans = d->drv_d_func()->iTrans;
648 ITransaction *tr = iTrans ? iTrans : d->autoTrans;
651 ThrowStatusWrapper st(d->status());
652 if (d->txnOp == TxnOp::Commit)
656 }
catch (
const FbException &e) {
657 const QSqlError err = fbError(d->master(), e.getStatus(), QSqlError::TransactionError);
658 qCInfo(lcFirebird) <<
"exec(txn):" << fbErrorLog(err);
665 d->autoTrans =
nullptr;
668 setAt(QSql::AfterLastRow);
673 setLastError(QSqlError(u"Statement not prepared"_s, QString(), QSqlError::StatementError));
678
679
680
682 const qsizetype paramCount = d->inMeta ? d->inCols.size() : 0;
684 for (
int p = 0, bc = boundValueCount(); p < bc; ++p) {
685 if (bindValueType(p).testFlag(QSql::In))
688 if (inputCount > paramCount) {
689 setLastError(QSqlError(
690 u"Parameter count mismatch: statement expects %1 input parameter(s), %2 bound"_s
691 .arg(paramCount).arg(inputCount),
692 QString(), QSqlError::StatementError));
699 d->affectedRows = -1;
702 ThrowStatusWrapper st(d->status());
703 ITransaction *tr = d->ensureTransaction();
705 if (!d->buildInputMessage(bindError)) {
706 setLastError(QSqlError(bindError, QString(), QSqlError::StatementError));
711 d->cursor = d->stmt->openCursor(&st, tr,
713 d->inBuffer.isEmpty() ?
nullptr : d->inBuffer.data(),
715 isForwardOnly() ? 0 : IStatement::CURSOR_TYPE_SCROLLABLE);
718 d->cacheComplete =
false;
719 d->useRowCache = d->drv_d_func()->cacheScrollableResults && !isForwardOnly();
720 setAt(QSql::BeforeFirstRow);
723 d->isProcExec =
false;
724 d->procRowFetched =
false;
726
727
728 if (!d->outBuffer.isEmpty())
729 d->outBuffer.fill(0);
730 ITransaction *newTr = d->stmt->execute(&st, tr,
732 d->inBuffer.isEmpty() ?
nullptr : d->inBuffer.data(),
734 d->outBuffer.isEmpty() ?
nullptr : d->outBuffer.data());
736
737 d->adoptReplacementTransaction(tr, newTr);
738 d->affectedRows = QFirebirdResultPrivate::AffectedPending;
741
742
743 d->isProcExec =
true;
744 setAt(QSql::BeforeFirstRow);
748 if (d->autoTrans && !d->drv_d_func()->iTrans)
750 setAt(QSql::AfterLastRow);
754 }
catch (
const FbException &e) {
755 const QSqlError err = fbError(d->master(), e.getStatus(), QSqlError::StatementError);
756 qCInfo(lcFirebird) <<
"exec:" << fbErrorLog(err);
765 Q_D(QFirebirdResult);
767 if (!d->stmt || !d->inMeta) {
769 return QSqlResult::execBatch(arrayBind);
772 const QList<QVariant> &batchValues = d->values;
773 if (batchValues.isEmpty()) {
774 setLastError(QSqlError(u"No values bound for batch execution"_s,
775 QString(), QSqlError::StatementError));
779 const qsizetype paramCount = batchValues.size();
780 const QVariantList firstList = batchValues.at(0).toList();
781 const qsizetype batchCount = firstList.size();
787 d->affectedRows = -1;
789 IUtil *utl = d->master()->getUtilInterface();
792 ThrowStatusWrapper st(d->status());
793 ITransaction *tr = d->ensureTransaction();
796
797
798 FbGuard<IXpbBuilder> pb(utl->getXpbBuilder(&st, IXpbBuilder::BATCH,
nullptr, 0),
799 fbDispose<IXpbBuilder>);
800 pb->insertInt(&st, IBatch::TAG_RECORD_COUNTS, 1);
803 bool hasBlobs =
false;
804 for (
const ColumnInfo &ci : std::as_const(d->inCols)) {
805 if (fbBaseType(ci.fbType) == SQL_BLOB) {
811 pb->insertInt(&st, IBatch::TAG_BLOB_POLICY, IBatch::BLOB_ID_ENGINE);
814
815 FbGuard<IBatch> batch(d->stmt->createBatch(&st, d->inMeta,
816 pb->getBufferLength(&st), pb->getBuffer(&st)),
820
821
822 QVarLengthArray<QVariantList, 16> paramLists;
823 paramLists.reserve(paramCount);
824 for (qsizetype p = 0; p < paramCount; ++p)
825 paramLists.append(batchValues.at(p).toList());
827 for (qsizetype row = 0; row < batchCount; ++row) {
828 QList<QVariant> rowValues;
829 rowValues.reserve(paramCount);
830 for (qsizetype p = 0; p < paramCount; ++p) {
831 const QVariantList &col = paramLists[p];
832 rowValues.append(row < col.size() ? col.at(row) : QVariant());
838 auto blobWriter = [&](ISC_QUAD &blobId,
const QByteArray &data) ->
bool {
840 batch->addBlob(&st,
static_cast<
unsigned>(data.size()),
841 data.constData(), &blobId, 0,
nullptr);
843 }
catch (
const FbException &e) {
844 const auto berr = fbError(d->master(), e.getStatus(), QSqlError::StatementError);
845 qCWarning(lcFirebird) <<
"batch addBlob:" << fbErrorLog(berr);
851 if (!d->fillInputBuffer(rowValues, bindError,
852 hasBlobs ? blobWriter : QFirebirdResultPrivate::BlobWriter{})) {
853 setLastError(QSqlError(bindError, QString(), QSqlError::StatementError));
857 batch->add(&st, 1, d->inBuffer.data());
860 FbGuard<IBatchCompletionState> cs(batch->execute(&st, tr),
861 fbDispose<IBatchCompletionState>);
863 unsigned total = cs->getSize(&st);
864 int totalAffected = 0;
865 bool hadError =
false;
866 for (
unsigned p = 0; p < total; ++p) {
867 int state = cs->getState(&st, p);
868 if (state == IBatchCompletionState::EXECUTE_FAILED) {
870 }
else if (state != IBatchCompletionState::SUCCESS_NO_INFO) {
871 totalAffected += state;
877 d->affectedRows = totalAffected;
880 unsigned errPos = cs->findError(&st, 0);
881 if (errPos != IBatchCompletionState::NO_MORE_ERRORS) {
882 IStatus *errStatus = d->master()->getStatus();
884 cs->getStatus(&st, errStatus, errPos);
885 setLastError(fbError(d->master(), errStatus, QSqlError::StatementError));
887 setLastError(QSqlError(u"Batch execution failed at message %1"_s
889 QString(), QSqlError::StatementError));
891 errStatus->dispose();
894
895 setLastError(QSqlError(u"Batch execution failed"_s,
896 QString(), QSqlError::StatementError));
901
902
903 batch.closeWith([&](IBatch *b) { b->close(&st); });
906 if (d->autoTrans && !d->drv_d_func()->iTrans)
909 setAt(QSql::AfterLastRow);
913 }
catch (
const FbException &e) {
914 const QSqlError err = fbError(d->master(), e.getStatus(), QSqlError::StatementError);
915 qCInfo(lcFirebird) <<
"execBatch:" << fbErrorLog(err);
1168 Q_D(
const QFirebirdResult);
1170 if (field < 0 || field >= d->cols.size()) {
1171 qCDebug(lcFirebird,
"data(%d): out of range, cols.size()=%d", field,
int(d->cols.size()));
1178
1179
1181 qCDebug(lcFirebird,
"data(%d): SQL NULL, fbType=%d qtType=%d", field, ci
.fbType,
int(ci.qtType));
1182 return QVariant(QMetaType(ci.qtType));
1185 qCDebug(lcFirebird,
"data(%d): fbType=%d qtType=%d", field, ci
.fbType,
int(ci.qtType));
1186 const char *ptr = d->outBuffer.constData() + ci.offset;
1189
1190
1191
1195 const auto policy = numericalPrecisionPolicy();
1197 case SQL_SHORT:
return applyScale(*
reinterpret_cast<
const qint16 *>(ptr), ci
.scale, policy);
1198 case SQL_LONG:
return applyScale(*
reinterpret_cast<
const qint32 *>(ptr), ci
.scale, policy);
1201 ThrowStatusWrapper st(d->status());
1202 const QString str = int128ToString(d->master()->getUtilInterface(), st,
1203 reinterpret_cast<
const FB_I128 *>(ptr),
1205 if (policy == QSql::HighPrecision)
1207 if (policy == QSql::LowPrecisionDouble)
1208 return QVariant(str.toDouble());
1210
1211
1212
1213
1214 const qsizetype dot = str.indexOf(u'.');
1215 const QStringView intPart =
1216 dot < 0 ? QStringView(str) : QStringView(str).first(dot);
1217 if (policy == QSql::LowPrecisionInt32)
1218 return QVariant(intPart.toInt());
1219 return QVariant(intPart.toLongLong());
1221 default:
return applyScale(*
reinterpret_cast<
const qint64 *>(ptr), ci
.scale, policy);
1227 return static_cast<
int>(*
reinterpret_cast<
const qint16 *>(ptr));
1229 return static_cast<
int>(*
reinterpret_cast<
const qint32 *>(ptr));
1231 return *
reinterpret_cast<
const qint64 *>(ptr);
1234 ThrowStatusWrapper st(d->status());
1235 return int128ToString(d->master()->getUtilInterface(), st,
1236 reinterpret_cast<
const FB_I128 *>(ptr), 0);
1239 return static_cast<
double>(*
reinterpret_cast<
const float *>(ptr));
1241 return *
reinterpret_cast<
const double *>(ptr);
1243 ThrowStatusWrapper st(d->status());
1244 return decFloatToString(d->master()->getUtilInterface()->getDecFloat16(&st), st,
1245 reinterpret_cast<
const FB_DEC16 *>(ptr));
1248 ThrowStatusWrapper st(d->status());
1249 return decFloatToString(d->master()->getUtilInterface()->getDecFloat34(&st), st,
1250 reinterpret_cast<
const FB_DEC34 *>(ptr));
1253 return *
reinterpret_cast<
const FB_BOOLEAN *>(ptr) != FB_FALSE;
1255 return decodeFirebirdDate(d->master()->getUtilInterface(),
1256 *
reinterpret_cast<
const ISC_DATE *>(ptr));
1258 return decodeFirebirdTime(d->master()->getUtilInterface(),
1259 *
reinterpret_cast<
const ISC_TIME *>(ptr));
1261 return decodeFirebirdTimestamp(d->master()->getUtilInterface(),
1262 *
reinterpret_cast<
const ISC_TIMESTAMP *>(ptr));
1263 case SQL_TIMESTAMP_TZ:
1264 return decodeFirebirdTimestampTz(d->status(), d->master()->getUtilInterface(),
1265 *
reinterpret_cast<
const ISC_TIMESTAMP_TZ *>(ptr));
1267 return decodeFirebirdTimeTz(d->status(), d->master()->getUtilInterface(),
1268 *
reinterpret_cast<
const ISC_TIME_TZ *>(ptr));
1270 const ISC_QUAD &blobId = *
reinterpret_cast<
const ISC_QUAD *>(ptr);
1271 QByteArray ba = readBlob(d->att(), d->activeTransaction(),
1272 d->status(), blobId);
1274 if (ci.fbSubType == 1)
1275 return QString::fromUtf8(ba);
1279 const ISC_QUAD &arrayId = *
reinterpret_cast<
const ISC_QUAD *>(ptr);
1280 ITransaction *tra = d->activeTransaction();
1281 return fetchArray(d->att(), tra, d->status(), d->master(),
1282 d->arrayDescCache, arrayId, ci.relation, ci.field);
1286
1287
1288 const unsigned short len = *
reinterpret_cast<
const unsigned short *>(ptr);
1289 const qsizetype maxLen =
1290 std::max(qsizetype(0),
static_cast<qsizetype>(ci
.length) - 2);
1291 return QString::fromUtf8(ptr + 2, std::min(
static_cast<qsizetype>(len), maxLen));
1295
1296 QString s = QString::fromUtf8(ptr,
static_cast<qsizetype>(ci.length));
1297 while (s.endsWith(u' '))
1302 qCWarning(lcFirebird,
"data: unhandled Firebird type %d for field %d", ci
.fbType, field);
1303 return QString::fromUtf8(ptr);
1305 }
catch (
const FbException &e) {
1306 setLastError(fbError(d->master(), e.getStatus(), QSqlError::StatementError));