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));
414 Q_D(QFirebirdDriver);
418 CheckStatusWrapper st(d->iStatus);
420 for (
auto *sub : std::as_const(d->eventSubscriptions)) {
424 d->eventSubscriptions.clear();
427 ThrowStatusWrapper tsw(d->iStatus);
428 d->iTrans->rollback(&tsw);
430 d->iTrans->release();
436
437
438
441 const std::lock_guard<
std::mutex> lock(d->attMutex);
446 ThrowStatusWrapper tsw(d->iStatus);
463
464
465
466
467
468
469
470
471
474 Q_D(QFirebirdDriver);
475 const std::lock_guard<
std::mutex> lock(d->attMutex);
478 FbGuard<IStatus> status(d->master->getStatus(), fbDispose<IStatus>);
480 ThrowStatusWrapper st(status.get());
481 d->iAtt->cancelOperation(&st, fb_cancel_raise);
482 }
catch (
const FbException &e) {
483 const auto err = fbError(d->master, e.getStatus(), QSqlError::UnknownError);
484 qCWarning(lcFirebird) <<
"cancelQuery:" << fbErrorLog(err);
492 Q_D(QFirebirdDriver);
494
495
496 if (!isOpen() || isOpenError()) {
497 setLastError(QSqlError(u"Database not open"_s, {},
498 QSqlError::TransactionError));
502 setLastError(QSqlError(u"Transaction already active"_s, {},
503 QSqlError::TransactionError));
507 ThrowStatusWrapper st(d->iStatus);
508 d->iTrans = startDefaultTransaction(d->iAtt, st, TxnWait::NoWait);
509 }
catch (
const FbException &e) {
510 setLastError(fbError(d->master, e.getStatus(), QSqlError::TransactionError));
519 Q_D(QFirebirdDriver);
520 return d->finishTransaction(TxnEnd::Commit);
525 Q_D(QFirebirdDriver);
526 return d->finishTransaction(TxnEnd::Rollback);
531 Q_D(
const QFirebirdDriver);
532 return QVariant::fromValue(d->iAtt);
538 QString res = identifier;
539 if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) {
540 res.replace(u'"', u"\"\""_s);
541 res.replace(u'.', u"\".\""_s);
542 res = u'"' + res + u'"';
549 switch (field.metaType().id()) {
550 case QMetaType::QDateTime: {
552
553
554 const QDateTime dt = field.value().toDateTime();
556 return dt.toString(u"''yyyy-MM-dd HH:mm:ss.zzz''");
559 case QMetaType::QTime: {
560 const QTime t = field.value().toTime();
562 return t.toString(u"''HH:mm:ss.zzz''");
565 case QMetaType::QDate: {
566 const QDate d = field.value().toDate();
568 return d.toString(u"''yyyy-MM-dd''");
572 return QSqlDriver::formatValue(field, trimStrings);
585
586
587
589 const QVariantList &binds)
const
591 QSqlQuery q(createResult());
593 for (
const QVariant &v : binds)
596 qCWarning(lcFirebird) <<
"metadata query failed:"
597 << fbErrorLog(q.lastError()) <<
"SQL:" << sql;
610 if (type == QSql::SystemTables) {
611 filter = u"WHERE RDB$SYSTEM_FLAG != 0"_s;
612 }
else if (type == (QSql::SystemTables | QSql::Views)) {
613 filter = u"WHERE RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_SOURCE IS NOT NULL"_s;
615 QStringList conditions;
616 if (!(type & QSql::SystemTables))
617 conditions << u"RDB$SYSTEM_FLAG = 0"_s;
618 if (!(type & QSql::Views))
619 conditions << u"RDB$VIEW_SOURCE IS NULL"_s;
620 if (!(type & QSql::Tables))
621 conditions << u"RDB$VIEW_SOURCE IS NOT NULL"_s;
622 if (!conditions.isEmpty())
623 filter = u"WHERE "_s + conditions.join(u" AND "_s);
626 QSqlQuery q = execMetadataQuery(
627 u"SELECT TRIM(RDB$RELATION_NAME) FROM RDB$RELATIONS "_s
628 + filter + u" ORDER BY RDB$RELATION_NAME"_s);
630 result.append(q.value(0).toString());
641
642
643
644
645 const QString table = stripDelimiters(tableName, QSqlDriver::TableName);
648
650 u"SELECT TRIM(f.RDB$FIELD_NAME), fld.RDB$FIELD_TYPE, fld.RDB$FIELD_SUB_TYPE,"
651 u" fld.RDB$FIELD_SCALE, fld.RDB$CHARACTER_LENGTH, f.RDB$NULL_FLAG,"
652 u" fld.RDB$FIELD_PRECISION, fld.RDB$FIELD_LENGTH"
653 u" FROM RDB$RELATION_FIELDS f"
654 u" JOIN RDB$FIELDS fld ON fld.RDB$FIELD_NAME = f.RDB$FIELD_SOURCE"
655 u" WHERE TRIM(f.RDB$RELATION_NAME) = ?"
656 u" ORDER BY f.RDB$FIELD_POSITION"_s;
658 QSqlQuery q = execMetadataQuery(sql, {table});
661 QString name = q.value(0).toString().trimmed();
662 int rdbType = q.value(1).toInt();
663 int subType = q.value(2).toInt();
664 int scale = q.value(3).toInt();
665 int charLen = q.value(4).toInt();
666 bool notNull = (q.value(5).toInt() == 1);
667 int precision = q.value(6).toInt();
668 int byteLen = q.value(7).toInt();
670 QMetaType::Type qtType = blrTypeToQt(
static_cast<
unsigned char>(rdbType), scale < 0);
671 QSqlField fld(name, QMetaType(qtType));
672 fld.setRequired(notNull);
675 if (scale < 0 && precision > 0) {
676 fld.setLength(precision);
677 fld.setPrecision(-scale);
678 }
else if (rdbType == blr_text || rdbType == blr_varying) {
679 fld.setLength(charLen > 0 ? charLen : byteLen);
695 const QString table = stripDelimiters(tableName, QSqlDriver::TableName);
698 u"SELECT TRIM(s.RDB$FIELD_NAME)"
699 u" FROM RDB$RELATION_CONSTRAINTS c"
700 u" JOIN RDB$INDEX_SEGMENTS s ON s.RDB$INDEX_NAME = c.RDB$INDEX_NAME"
701 u" WHERE c.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'"
702 u" AND TRIM(c.RDB$RELATION_NAME) = ?"
703 u" ORDER BY s.RDB$FIELD_POSITION"_s;
705 QSqlQuery q = execMetadataQuery(sql, {table});
707 QSqlRecord rec = record(tableName);
709 QString col = q.value(0).toString().trimmed();
710 index.append(rec.field(col));
719 Q_D(QFirebirdDriver);
721 qCWarning(lcFirebird,
"QFirebirdDriver::subscribeToNotification: database not open.");
724 if (d->eventSubscriptions.contains(name)) {
725 qCWarning(lcFirebird,
726 "QFirebirdDriver::subscribeToNotification: already subscribed to '%ls'.",
727 qUtf16Printable(name));
731 auto *sub =
new QFirebirdEventSubscription(
this, d->iAtt, name);
735 ThrowStatusWrapper csw(d->iStatus);
736 sub->events = d->iAtt->queEvents(&csw, sub,
737 sub->bufferLength, sub->eventBuffer);
738 }
catch (
const FbException &e) {
740 auto err = fbError(d->master, e.getStatus(), QSqlError::ConnectionError);
741 setLastError(QSqlError(
742 tr(
"Could not subscribe to event notifications for %1.").arg(name),
743 err.databaseText(), err.type(), err.nativeErrorCode()));
747 d->eventSubscriptions.insert(name, sub);
753 Q_D(QFirebirdDriver);
755 qCWarning(lcFirebird,
"QFirebirdDriver::unsubscribeFromNotification: database not open.");
758 if (!d->eventSubscriptions.contains(name)) {
759 qCWarning(lcFirebird,
760 "QFirebirdDriver::unsubscribeFromNotification: not subscribed to '%ls'.",
761 qUtf16Printable(name));
773 Q_D(
const QFirebirdDriver);
774 return QStringList(d->eventSubscriptions.keys());
779 Q_D(QFirebirdDriver);
782 if (!sub || sub->counter.load(
std::memory_order_relaxed) == 0)
786
787 static constexpr int maxEventNamesPerSubscription = 1;
788 ISC_ULONG counts[maxEventNamesPerSubscription] = {};
791
792 std::lock_guard<
std::mutex> lock(sub->mutex);
793 isc_event_counts(counts,
static_cast<ISC_USHORT>(sub
->bufferLength),
794 sub->eventBuffer, sub->resultBuffer);
795 sub->counter.store(0,
std::memory_order_relaxed);
799
800
801
802
803
804
805 IEvents *old = sub->events;
806 sub->events =
nullptr;
808 IStatus *s = d->master->getStatus();
809 ThrowStatusWrapper csw(s);
811 sub->events = sub->attachment->queEvents(&csw, sub,
813 }
catch (
const FbException &e) {
814 const auto err = fbError(d->master, e.getStatus(), QSqlError::ConnectionError);
815 qCWarning(lcFirebird) <<
"qHandleEventNotification: re-arm failed for"
816 << name <<
':' << fbErrorLog(err);
818 qCWarning(lcFirebird,
"qHandleEventNotification: unexpected exception re-arming '%ls'",
819 qUtf16Printable(name));
826
827
828
829 const bool suppress = sub
->first;
831 if (counts[0] > 0 && !suppress)
832 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)