11#include <QtCore/qdatetime.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/qvariant.h>
14#include <QtSql/qsqlfield.h>
15#include <QtSql/qsqlindex.h>
16#include <QtSql/qsqlquery.h>
17#include <QtSql/qsqlrecord.h>
20#include <firebird/Interface.h>
28
29
30
31static_assert(FB_API_VER >= 40,
32 "The QFIREBIRD driver requires Firebird 4.0 or later client libraries.");
35
36
37
38#ifdef QT_NO_EXCEPTIONS
39# error The QFIREBIRD driver requires C++ exception support
45using namespace Firebird;
47using namespace Qt::StringLiterals;
50
51
52
53
54
55
56
57
58
59
60
61
68 const QString &eventName)
72 isc_event_block(&eventBuffer, &resultBuffer,
73 1, eventName.toUtf8().constData()));
79 if (--refCount == 0) {
87
88
89
90
91
92
93
94
95
96
97
102 std::lock_guard<
std::mutex> lock(mutex);
104
107 memcpy(resultBuffer, data, length);
110 QMetaObject::invokeMethod(driver,
"qHandleEventNotification",
111 Qt::QueuedConnection, Q_ARG(QString, name));
118
119
120
121
122
123
127 std::lock_guard<
std::mutex> lock(mutex);
149 ~QFirebirdEventSubscription()
154 isc_free(
reinterpret_cast<
char *>(eventBuffer));
156 isc_free(
reinterpret_cast<
char *>(resultBuffer));
159 std::atomic<
int> refCount = 0;
166 Q_Q(QFirebirdDriver);
167 auto err = fbError(master, iStatus, type);
168 q->setLastError(QSqlError(context + u": " + err.databaseText(),
169 {}, err.type(), err.nativeErrorCode()));
175 Q_Q(QFirebirdDriver);
177 q->setLastError(QSqlError(u"No active transaction"_s, {},
178 QSqlError::TransactionError));
182 ThrowStatusWrapper st(iStatus);
183 finishAndClear(st, iTrans, end);
184 }
catch (
const FbException &e) {
185 q->setLastError(fbError(master, e.getStatus(), QSqlError::TransactionError));
203
204
205 Q_D(QFirebirdDriver);
206 for (QFirebirdResultPrivate *r : std::as_const(d->activeResults))
207 r->driverAlive =
false;
208 d->activeResults.clear();
213 Q_D(
const QFirebirdDriver);
218 case PreparedQueries:
219 case PositionalPlaceholders:
220 case LowPrecisionNumbers:
221 case EventNotifications:
222 case BatchOperations:
228
229
230
231 return d->cacheScrollableResults;
232 case NamedPlaceholders:
235 case MultipleResultSets:
242
243
244
245
246
248enum DpbValueKind : quint8 { DpbString, DpbInt };
250 QLatin1StringView key;
254constexpr DpbOption dpbOptionTable[] = {
255 {
"ISC_DPB_SQL_ROLE_NAME"_L1, isc_dpb_sql_role_name, DpbString },
256 {
"ISC_DPB_SESSION_TIME_ZONE"_L1, isc_dpb_session_time_zone, DpbString },
257 {
"ISC_DPB_NUM_BUFFERS"_L1, isc_dpb_num_buffers, DpbInt },
258 {
"ISC_DPB_CONNECT_TIMEOUT"_L1, isc_dpb_connect_timeout, DpbInt },
259 {
"ISC_DPB_DUMMY_PACKET_INTERVAL"_L1, isc_dpb_dummy_packet_interval, DpbInt },
260 {
"ISC_DPB_NO_GARBAGE_COLLECT"_L1, isc_dpb_no_garbage_collect, DpbInt },
261 {
"ISC_DPB_NO_DB_TRIGGERS"_L1, isc_dpb_no_db_triggers, DpbInt },
265
266
267
268
269
270constexpr QLatin1StringView driverManagedKeys[] = {
271 "ISC_DPB_LC_CTYPE"_L1,
272 "ISC_DPB_SQL_DIALECT"_L1,
278 const QString &password,
281 const QString &connOpts)
283 Q_D(QFirebirdDriver);
289 if (!host.isEmpty()) {
292 connStr += u'/' + QString::number(port);
298 IUtil *util = d->master->getUtilInterface();
299 ThrowStatusWrapper st(d->iStatus);
302 FbGuard<IXpbBuilder> dpb(util->getXpbBuilder(&st, IXpbBuilder::DPB,
nullptr, 0),
303 fbDispose<IXpbBuilder>);
305 dpb->insertString(&st, isc_dpb_user_name,
306 user.toUtf8().constData());
307 dpb->insertString(&st, isc_dpb_password,
308 password.toUtf8().constData());
310 dpb->insertString(&st, isc_dpb_lc_ctype,
"UTF8");
313
314
315 for (
const auto &opt : QStringView(connOpts).split(u';', Qt::SkipEmptyParts)) {
316 const auto kv = opt.trimmed();
317 const auto eqIdx = kv.indexOf(u'=');
319 qCWarning(lcFirebird) <<
"QFirebirdDriver: ignoring malformed connection option"
323 const QString key = kv.left(eqIdx).trimmed().toString().toUpper();
324 const QString value = kv.mid(eqIdx + 1).trimmed().toString();
327
328
329 if (key ==
"FB_SCROLLABLE_CACHE"_L1) {
330 d->cacheScrollableResults = (value !=
"0"_L1)
331 && (value.compare(
"false"_L1, Qt::CaseInsensitive) != 0);
336
337
338 bool managed =
false;
339 for (
const auto &k : driverManagedKeys) {
346 const bool isLcCtype = (key ==
"ISC_DPB_LC_CTYPE"_L1);
347 const bool matchesDefault = isLcCtype
348 ? (value.compare(
"UTF8"_L1, Qt::CaseInsensitive) == 0
349 || value.compare(
"UTF-8"_L1, Qt::CaseInsensitive) == 0)
351 if (!matchesDefault) {
352 qCWarning(lcFirebird) <<
"QFirebirdDriver: connection option" << key
353 <<
"is managed by the driver and cannot be overridden"
354 " (charset is fixed to UTF-8, dialect to 3); ignoring"
360 const DpbOption *match =
nullptr;
361 for (
const auto &o : dpbOptionTable) {
368 qCWarning(lcFirebird) <<
"QFirebirdDriver: ignoring unknown connection option" << key;
371 if (match->kind == DpbString) {
372 QByteArray s = value.toUtf8();
373 if (match->tag == isc_dpb_sql_role_name)
375 dpb->insertString(&st, match->tag, s.constData());
378 const int n = value.toInt(&ok);
380 qCWarning(lcFirebird) <<
"QFirebirdDriver: non-integer value for connection option"
381 << key <<
':' << value;
384 dpb->insertInt(&st, match->tag, n);
388 const unsigned char *dpbData = dpb->getBuffer(&st);
389 unsigned dpbLen = dpb->getBufferLength(&st);
391 IAttachment *att = d->master->getDispatcher()->attachDatabase(
392 &st, connStr.toUtf8().constData(), dpbLen, dpbData);
394
396 const std::lock_guard<
std::mutex> lock(d->attMutex);
399 }
catch (
const FbException &e) {
400 setLastError(fbError(d->master, e.getStatus(), QSqlError::ConnectionError));
409#ifdef QSQL_HAS_INT128
410 qCDebug(lcFirebird,
"QFirebirdDriver: INT128 codec: native 128-bit integer");
412 qCDebug(lcFirebird,
"QFirebirdDriver: INT128 codec: IInt128 string interface");
420 Q_D(QFirebirdDriver);
424 CheckStatusWrapper st(d->iStatus);
426 for (
auto *sub : std::as_const(d->eventSubscriptions)) {
430 d->eventSubscriptions.clear();
433 ThrowStatusWrapper tsw(d->iStatus);
434 d->iTrans->rollback(&tsw);
436 d->iTrans->release();
442
443
444
447 const std::lock_guard<
std::mutex> lock(d->attMutex);
452 ThrowStatusWrapper tsw(d->iStatus);
469
470
471
472
473
474
475
476
477
480 Q_D(QFirebirdDriver);
481 const std::lock_guard<
std::mutex> lock(d->attMutex);
484 FbGuard<IStatus> status(d->master->getStatus(), fbDispose<IStatus>);
486 ThrowStatusWrapper st(status.get());
487 d->iAtt->cancelOperation(&st, fb_cancel_raise);
488 }
catch (
const FbException &e) {
489 const auto err = fbError(d->master, e.getStatus(), QSqlError::UnknownError);
490 qCWarning(lcFirebird) <<
"cancelQuery:" << fbErrorLog(err);
498 Q_D(QFirebirdDriver);
500
501
502 if (!isOpen() || isOpenError()) {
503 setLastError(QSqlError(u"Database not open"_s, {},
504 QSqlError::TransactionError));
508 setLastError(QSqlError(u"Transaction already active"_s, {},
509 QSqlError::TransactionError));
513 ThrowStatusWrapper st(d->iStatus);
514 d->iTrans = startDefaultTransaction(d->iAtt, st, TxnWait::NoWait);
515 }
catch (
const FbException &e) {
516 setLastError(fbError(d->master, e.getStatus(), QSqlError::TransactionError));
525 Q_D(QFirebirdDriver);
526 return d->finishTransaction(TxnEnd::Commit);
531 Q_D(QFirebirdDriver);
532 return d->finishTransaction(TxnEnd::Rollback);
537 Q_D(
const QFirebirdDriver);
538 return QVariant::fromValue(d->iAtt);
544 QString res = identifier;
545 if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) {
546 res.replace(u'"', u"\"\""_s);
547 res.replace(u'.', u"\".\""_s);
548 res = u'"' + res + u'"';
555 switch (field.metaType().id()) {
556 case QMetaType::QDateTime: {
558
559
560 const QDateTime dt = field.value().toDateTime();
562 return dt.toString(u"''yyyy-MM-dd HH:mm:ss.zzz''");
565 case QMetaType::QTime: {
566 const QTime t = field.value().toTime();
568 return t.toString(u"''HH:mm:ss.zzz''");
571 case QMetaType::QDate: {
572 const QDate d = field.value().toDate();
574 return d.toString(u"''yyyy-MM-dd''");
578 return QSqlDriver::formatValue(field, trimStrings);
591
592
593
595 const QVariantList &binds)
const
597 QSqlQuery q(createResult());
599 for (
const QVariant &v : binds)
602 qCWarning(lcFirebird) <<
"metadata query failed:"
603 << fbErrorLog(q.lastError()) <<
"SQL:" << sql;
616 if (type == QSql::SystemTables) {
617 filter = u"WHERE RDB$SYSTEM_FLAG != 0"_s;
618 }
else if (type == (QSql::SystemTables | QSql::Views)) {
619 filter = u"WHERE RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_SOURCE IS NOT NULL"_s;
621 QStringList conditions;
622 if (!(type & QSql::SystemTables))
623 conditions << u"RDB$SYSTEM_FLAG = 0"_s;
624 if (!(type & QSql::Views))
625 conditions << u"RDB$VIEW_SOURCE IS NULL"_s;
626 if (!(type & QSql::Tables))
627 conditions << u"RDB$VIEW_SOURCE IS NOT NULL"_s;
628 if (!conditions.isEmpty())
629 filter = u"WHERE "_s + conditions.join(u" AND "_s);
632 QSqlQuery q = execMetadataQuery(
633 u"SELECT TRIM(RDB$RELATION_NAME) FROM RDB$RELATIONS "_s
634 + filter + u" ORDER BY RDB$RELATION_NAME"_s);
636 result.append(q.value(0).toString());
647
648
649
650
651 const QString table = stripDelimiters(tableName, QSqlDriver::TableName);
654
656 u"SELECT TRIM(f.RDB$FIELD_NAME), fld.RDB$FIELD_TYPE, fld.RDB$FIELD_SUB_TYPE,"
657 u" fld.RDB$FIELD_SCALE, fld.RDB$CHARACTER_LENGTH, f.RDB$NULL_FLAG,"
658 u" fld.RDB$FIELD_PRECISION, fld.RDB$FIELD_LENGTH"
659 u" FROM RDB$RELATION_FIELDS f"
660 u" JOIN RDB$FIELDS fld ON fld.RDB$FIELD_NAME = f.RDB$FIELD_SOURCE"
661 u" WHERE TRIM(f.RDB$RELATION_NAME) = ?"
662 u" ORDER BY f.RDB$FIELD_POSITION"_s;
664 QSqlQuery q = execMetadataQuery(sql, {table});
667 QString name = q.value(0).toString().trimmed();
668 int rdbType = q.value(1).toInt();
669 int subType = q.value(2).toInt();
670 int scale = q.value(3).toInt();
671 int charLen = q.value(4).toInt();
672 bool notNull = (q.value(5).toInt() == 1);
673 int precision = q.value(6).toInt();
674 int byteLen = q.value(7).toInt();
676 QMetaType::Type qtType = blrTypeToQt(
static_cast<
unsigned char>(rdbType), scale < 0);
677 QSqlField fld(name, QMetaType(qtType));
678 fld.setRequired(notNull);
681 if (scale < 0 && precision > 0) {
682 fld.setLength(precision);
683 fld.setPrecision(-scale);
684 }
else if (rdbType == blr_text || rdbType == blr_varying) {
685 fld.setLength(charLen > 0 ? charLen : byteLen);
701 const QString table = stripDelimiters(tableName, QSqlDriver::TableName);
704 u"SELECT TRIM(s.RDB$FIELD_NAME)"
705 u" FROM RDB$RELATION_CONSTRAINTS c"
706 u" JOIN RDB$INDEX_SEGMENTS s ON s.RDB$INDEX_NAME = c.RDB$INDEX_NAME"
707 u" WHERE c.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'"
708 u" AND TRIM(c.RDB$RELATION_NAME) = ?"
709 u" ORDER BY s.RDB$FIELD_POSITION"_s;
711 QSqlQuery q = execMetadataQuery(sql, {table});
713 QSqlRecord rec = record(tableName);
715 QString col = q.value(0).toString().trimmed();
716 index.append(rec.field(col));
725 Q_D(QFirebirdDriver);
727 qCWarning(lcFirebird,
"QFirebirdDriver::subscribeToNotification: database not open.");
730 if (d->eventSubscriptions.contains(name)) {
731 qCWarning(lcFirebird,
732 "QFirebirdDriver::subscribeToNotification: already subscribed to '%ls'.",
733 qUtf16Printable(name));
737 auto *sub =
new QFirebirdEventSubscription(
this, d->iAtt, name);
741 ThrowStatusWrapper csw(d->iStatus);
742 sub->events = d->iAtt->queEvents(&csw, sub,
743 sub->bufferLength, sub->eventBuffer);
744 }
catch (
const FbException &e) {
746 auto err = fbError(d->master, e.getStatus(), QSqlError::ConnectionError);
747 setLastError(QSqlError(
748 tr(
"Could not subscribe to event notifications for %1.").arg(name),
749 err.databaseText(), err.type(), err.nativeErrorCode()));
753 d->eventSubscriptions.insert(name, sub);
759 Q_D(QFirebirdDriver);
761 qCWarning(lcFirebird,
"QFirebirdDriver::unsubscribeFromNotification: database not open.");
764 if (!d->eventSubscriptions.contains(name)) {
765 qCWarning(lcFirebird,
766 "QFirebirdDriver::unsubscribeFromNotification: not subscribed to '%ls'.",
767 qUtf16Printable(name));
779 Q_D(
const QFirebirdDriver);
780 return QStringList(d->eventSubscriptions.keys());
785 Q_D(QFirebirdDriver);
788 if (!sub || sub->counter.load(
std::memory_order_relaxed) == 0)
792
793 static constexpr int maxEventNamesPerSubscription = 1;
794 ISC_ULONG counts[maxEventNamesPerSubscription] = {};
797
798 std::lock_guard<
std::mutex> lock(sub->mutex);
799 isc_event_counts(counts,
static_cast<ISC_USHORT>(sub
->bufferLength),
800 sub->eventBuffer, sub->resultBuffer);
801 sub->counter.store(0,
std::memory_order_relaxed);
805
806
807
808
809
810
811 IEvents *old = sub->events;
812 sub->events =
nullptr;
814 IStatus *s = d->master->getStatus();
815 ThrowStatusWrapper csw(s);
817 sub->events = sub->attachment->queEvents(&csw, sub,
819 }
catch (
const FbException &e) {
820 const auto err = fbError(d->master, e.getStatus(), QSqlError::ConnectionError);
821 qCWarning(lcFirebird) <<
"qHandleEventNotification: re-arm failed for"
822 << name <<
':' << fbErrorLog(err);
824 qCWarning(lcFirebird,
"qHandleEventNotification: unexpected exception re-arming '%ls'",
825 qUtf16Printable(name));
832
833
834
835 const bool suppress = sub
->first;
837 if (counts[0] > 0 && !suppress)
838 emit notification(name, QSqlDriver::UnknownSource, QVariant());
bool finishTransaction(TxnEnd end)
void setFbError(const QString &context, QSqlError::ErrorType type=QSqlError::UnknownError)
bool commitTransaction() override
This function is called to commit a transaction.
QSqlResult * createResult() const override
Creates an empty SQL result on the database.
bool cancelQuery() override
bool hasFeature(DriverFeature feature) const override
Returns true if the driver supports feature feature; otherwise returns false.
QString escapeIdentifier(const QString &identifier, IdentifierType type) const override
Returns the identifier escaped according to the database rules.
QSqlRecord record(const QString &tableName) const override
Returns a QSqlRecord populated with the names of the fields in table tableName.
QFirebirdDriver(QObject *parent=nullptr)
bool rollbackTransaction() override
This function is called to rollback a transaction.
QSqlIndex primaryIndex(const QString &tableName) const override
Returns the primary index for table tableName.
int maximumIdentifierLength(IdentifierType type) const override
QVariant handle() const override
Returns the low-level database handle wrapped in a QVariant or an invalid variant if there is no hand...
bool open(const QString &db, const QString &user, const QString &password, const QString &host, int port, const QString &connOpts) override
Derived classes must reimplement this pure virtual function to open a database connection on database...
QStringList subscribedToNotifications() const override
Returns a list of the names of the event notifications that are currently subscribed to.
bool unsubscribeFromNotification(const QString &name) override
This function is called to unsubscribe from event notifications from the database.
QString formatValue(const QSqlField &field, bool trimStrings) const override
Returns a string representation of the field value for the database.
bool beginTransaction() override
This function is called to begin a transaction.
bool subscribeToNotification(const QString &name) override
This function is called to subscribe to event notifications from the database.
~QFirebirdDriver() override
void close() override
Derived classes must reimplement this pure virtual function in order to close the database connection...
void eventCallbackFunction(unsigned length, const unsigned char *data) override
std::atomic< int > counter
QFirebirdEventSubscription(QFirebirdDriver *drv, IAttachment *att, const QString &eventName)
QFirebirdResult(const QFirebirdDriver *driver)