summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/sqldrivers/ibase/qsql_ibase.cpp135
-rw-r--r--tests/auto/sql/kernel/qsqldatabase/tst_databases.h13
-rw-r--r--tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp113
3 files changed, 252 insertions, 9 deletions
diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
index 95b994e7cd..4284f9b811 100644
--- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
+++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
@@ -4,6 +4,7 @@
#include "qsql_ibase_p.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
+#include <QtCore/qtimezone.h>
#include <QtCore/qdeadlinetimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
@@ -20,6 +21,7 @@
#include <stdlib.h>
#include <limits.h>
#include <math.h>
+#include <mutex>
QT_BEGIN_NAMESPACE
@@ -38,6 +40,14 @@ using namespace Qt::StringLiterals;
constexpr qsizetype QIBaseChunkSize = SHRT_MAX / 2;
+#if (FB_API_VER >= 40)
+typedef QMap<quint16, QByteArray> QFbTzIdToIanaIdMap;
+typedef QMap<QByteArray, quint16> QIanaIdToFbTzIdMap;
+Q_GLOBAL_STATIC(QFbTzIdToIanaIdMap, qFbTzIdToIanaIdMap)
+Q_GLOBAL_STATIC(QIanaIdToFbTzIdMap, qIanaIdToFbTzIdMap)
+std::once_flag initTZMappingFlag;
+#endif
+
static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode)
{
if (status[0] != 1 || status[1] <= 0)
@@ -85,6 +95,9 @@ static void initDA(XSQLDA *sqlda)
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_TIMESTAMP:
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+#endif
case SQL_TYPE_TIME:
case SQL_TYPE_DATE:
case SQL_TEXT:
@@ -139,6 +152,9 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
case blr_sql_date:
return QMetaType::QDate;
case blr_timestamp:
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+#endif
return QMetaType::QDateTime;
case blr_blob:
return QMetaType::QByteArray;
@@ -174,6 +190,9 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale)
case SQL_DOUBLE:
return QMetaType::Double;
case SQL_TIMESTAMP:
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+#endif
return QMetaType::QDateTime;
case SQL_TYPE_TIME:
return QMetaType::QTime;
@@ -208,12 +227,45 @@ static QDateTime fromTimeStamp(char *buffer)
// have to demangle the structure ourselves because isc_decode_time
// strips the msecs
- t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10));
- d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date));
-
+ auto timebuf = reinterpret_cast<ISC_TIMESTAMP*>(buffer);
+ t = t.addMSecs(static_cast<int>(timebuf->timestamp_time / 10));
+ d = bd.addDays(timebuf->timestamp_date);
return QDateTime(d, t);
}
+#if (FB_API_VER >= 40)
+QDateTime fromTimeStampTz(char *buffer)
+{
+ static const QDate bd(1858, 11, 17);
+ QTime t(0, 0);
+ QDate d;
+
+ // have to demangle the structure ourselves because isc_decode_time
+ // strips the msecs
+ auto timebuf = reinterpret_cast<ISC_TIMESTAMP_TZ*>(buffer);
+ t = t.addMSecs(static_cast<int>(timebuf->utc_timestamp.timestamp_time / 10));
+ d = bd.addDays(timebuf->utc_timestamp.timestamp_date);
+ quint16 fpTzID = timebuf->time_zone;
+
+ QByteArray timeZoneName = qFbTzIdToIanaIdMap()->value(fpTzID);
+ if (!timeZoneName.isEmpty())
+ return QDateTime(d, t, QTimeZone(timeZoneName));
+ else
+ return {};
+}
+
+ISC_TIMESTAMP_TZ toTimeStampTz(const QDateTime &dt)
+{
+ static const QTime midnight(0, 0, 0, 0);
+ static const QDate basedate(1858, 11, 17);
+ ISC_TIMESTAMP_TZ ts;
+ ts.utc_timestamp.timestamp_time = midnight.msecsTo(dt.time()) * 10;
+ ts.utc_timestamp.timestamp_date = basedate.daysTo(dt.date());
+ ts.time_zone = qIanaIdToFbTzIdMap()->value(dt.timeZone().id().simplified(), 0);
+ return ts;
+}
+#endif
+
static ISC_TIME toTime(QTime t)
{
static const QTime midnight(0, 0, 0, 0);
@@ -282,6 +334,34 @@ public:
return true;
}
+#if (FB_API_VER >= 40)
+ void initTZMappingCache()
+ {
+ Q_Q(QIBaseDriver);
+ QSqlQuery qry(q->createResult());
+ qry.setForwardOnly(true);
+ qry.exec(QString("select * from RDB$TIME_ZONES"_L1));
+ if (qry.lastError().type()) {
+ q->setLastError(QSqlError(
+ QCoreApplication::translate("QIBaseDriver",
+ "failed to query time zone mapping from system table"),
+ qry.lastError().databaseText(),
+ QSqlError::StatementError,
+ qry.lastError().nativeErrorCode()));
+
+ return;
+ }
+
+ while (qry.next()) {
+ auto record = qry.record();
+ quint16 fbTzId = record.value(0).value<quint16>();
+ QByteArray ianaId = record.value(1).toByteArray().simplified();
+ qFbTzIdToIanaIdMap()->insert(fbTzId, ianaId);
+ qIanaIdToFbTzIdMap()->insert(ianaId, fbTzId);
+ }
+ }
+#endif
+
public:
isc_db_handle ibase;
isc_tr_handle trans;
@@ -513,8 +593,16 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim,
buffer += sizeof(ISC_TIMESTAMP);
}
break;
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+ for (int i = 0; i < numElements[dim]; ++i) {
+ valList.append(fromTimeStampTz(buffer));
+ buffer += sizeof(ISC_TIMESTAMP_TZ);
+ }
+ break;
+#endif
case blr_sql_time:
- for(int i = 0; i < numElements[dim]; ++i) {
+ for (int i = 0; i < numElements[dim]; ++i) {
valList.append(fromTime(buffer));
buffer += sizeof(ISC_TIME);
}
@@ -709,8 +797,20 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
break;
case QMetaType::QDateTime:
for (const auto &elem : list) {
- *((ISC_TIMESTAMP*)buffer) = toTimeStamp(elem.toDateTime());
- buffer += sizeof(ISC_TIMESTAMP);
+ switch (arrayDesc->array_desc_dtype) {
+ case blr_timestamp:
+ *((ISC_TIMESTAMP*)buffer) = toTimeStamp(elem.toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP);
+ break;
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+ *((ISC_TIMESTAMP_TZ*)buffer) = toTimeStampTz(elem.toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP_TZ);
+ break;
+#endif
+ default:
+ break;
+ }
}
break;
case QMetaType::Bool:
@@ -914,7 +1014,6 @@ bool QIBaseResult::prepare(const QString& query)
return true;
}
-
bool QIBaseResult::exec()
{
Q_D(QIBaseResult);
@@ -988,6 +1087,11 @@ bool QIBaseResult::exec()
case SQL_TIMESTAMP:
*((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime());
break;
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+ *((ISC_TIMESTAMP_TZ*)d->inda->sqlvar[para].sqldata) = toTimeStampTz(val.toDateTime());
+ break;
+#endif
case SQL_TYPE_TIME:
*((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime());
break;
@@ -1171,6 +1275,11 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx)
case SQL_BOOLEAN:
row[idx] = QVariant(bool((*(bool*)buf)));
break;
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+ row[idx] = fromTimeStampTz(buf);
+ break;
+#endif
default:
// unknown type - don't even try to fetch
row[idx] = QVariant();
@@ -1469,6 +1578,14 @@ bool QIBaseDriver::open(const QString &db,
setOpen(true);
setOpenError(false);
+#if (FB_API_VER >= 40)
+ std::call_once(initTZMappingFlag, [d](){ d->initTZMappingCache(); });
+ if (lastError().isValid())
+ {
+ setOpen(true);
+ return false;
+ }
+#endif
return true;
}
@@ -1571,8 +1688,8 @@ QStringList QIBaseDriver::tables(QSql::TableType type) const
q.setForwardOnly(true);
if (!q.exec("select rdb$relation_name from rdb$relations "_L1 + typeFilter))
return res;
- while(q.next())
- res << q.value(0).toString().simplified();
+ while (q.next())
+ res << q.value(0).toString().simplified();
return res;
}
diff --git a/tests/auto/sql/kernel/qsqldatabase/tst_databases.h b/tests/auto/sql/kernel/qsqldatabase/tst_databases.h
index c715c1c2a8..c62f6a97ff 100644
--- a/tests/auto/sql/kernel/qsqldatabase/tst_databases.h
+++ b/tests/auto/sql/kernel/qsqldatabase/tst_databases.h
@@ -9,6 +9,7 @@
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlQuery>
+#include <QSqlRecord>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDir>
@@ -20,9 +21,12 @@
#include <QJsonObject>
#include <QJsonDocument>
#include <QSysInfo>
+#include <QVersionNumber>
#include <QtSql/private/qsqldriver_p.h>
#include <QTest>
+using namespace Qt::StringLiterals;
+
#define CHECK_DATABASE( db ) \
if ( !db.isValid() ) { qFatal( "db is Invalid" ); }
@@ -434,6 +438,15 @@ public:
return ver;
}
+ static QVersionNumber getIbaseEngineVersion(const QSqlDatabase &db)
+ {
+ auto q = db.exec("SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') as version from rdb$database;"_L1);
+ q.next();
+ auto record = q.record();
+ auto version = QVersionNumber::fromString(record.value(0).toString());
+ return version;
+ }
+
QStringList dbNames;
int counter = 0;
diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
index 048b9b0984..431c2f6f92 100644
--- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
+++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
@@ -246,6 +246,11 @@ private slots:
void ibaseArray_data() { generic_data("QIBASE"); }
void ibaseArray();
+ void ibaseDateTimeWithTZ_data();
+ void ibaseDateTimeWithTZ();
+ void ibaseTimeStampTzArray_data() { generic_data("QIBASE"); }
+ void ibaseTimeStampTzArray();
+
// Double addDatabase() with same name leaves system in a state that breaks
// invalidQuery() if run later; so put this one last !
void prematureExec_data() { generic_data(); }
@@ -4818,6 +4823,79 @@ void tst_QSqlQuery::dateTime()
}
}
+void tst_QSqlQuery::ibaseDateTimeWithTZ_data()
+{
+ if (dbs.dbNames.isEmpty())
+ QSKIP("No database drivers are available in this Qt configuration");
+
+ QTest::addColumn<QString>("dbName");
+ QTest::addColumn<QString>("tableName");
+ QTest::addColumn<QList<QDateTime> >("initialDateTimes");
+ QTest::addColumn<QList<QDateTime> >("expectedDateTimes");
+
+ const QTimeZone afterUTCTimeZone("Asia/Hong_Kong");
+ const QTimeZone beforeUTCTimeZone("America/Los_Angeles");
+ const QTimeZone utcTimeZone("UTC");
+
+ const QDateTime dtWithAfterTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), afterUTCTimeZone);
+ const QDateTime dtWithBeforeTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), beforeUTCTimeZone);
+ const QDateTime dtWithUTCTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), utcTimeZone);
+ const QDateTime dtLocalTZ(QDateTime::currentDateTime());
+
+ const QList<QDateTime> dateTimes = {
+ dtWithAfterTZ,
+ dtWithBeforeTZ,
+ dtWithUTCTZ,
+ dtLocalTZ
+ };
+
+ for (const QString &dbName : std::as_const(dbs.dbNames)) {
+ QSqlDatabase db = QSqlDatabase::database(dbName);
+ if (!db.isValid())
+ continue;
+
+ const QString tableNameTSWithTimeZone(qTableName("dateTimeTSWithTZ", __FILE__, db));
+
+ QTest::newRow(QString(dbName + " timestamp with time zone").toLatin1())
+ << dbName
+ << tableNameTSWithTimeZone
+ << dateTimes
+ << dateTimes;
+ }
+}
+
+void tst_QSqlQuery::ibaseDateTimeWithTZ()
+{
+ QFETCH(QString, dbName);
+ QSqlDatabase db = QSqlDatabase::database(dbName);
+ CHECK_DATABASE(db);
+ if (tst_Databases::getDatabaseType(db) != QSqlDriver::Interbase)
+ QSKIP("Implemented only for Interbase");
+
+ if (tst_Databases::getIbaseEngineVersion(db).majorVersion() < 4)
+ QSKIP("Time zone support only implemented for firebird engine version 4 and greater");
+
+ QFETCH(QString, tableName);
+ TableScope ts(db, tableName);
+
+ QSqlQuery q(db);
+ QVERIFY_SQL(q, exec(QString("CREATE TABLE " + tableName + "(dt timestamp with time zone)")));
+
+ QFETCH(QList<QDateTime>, initialDateTimes);
+ QFETCH(QList<QDateTime>, expectedDateTimes);
+
+ for (const QDateTime &dt : std::as_const(initialDateTimes)) {
+ QVERIFY_SQL(q, prepare(QLatin1String("INSERT INTO %1 values(:dt)").arg(tableName)));
+ q.bindValue(":dt", dt);
+ QVERIFY_SQL(q, exec());
+ }
+ QVERIFY_SQL(q, exec("SELECT * FROM " + tableName));
+ for (const QDateTime &dt : std::as_const(expectedDateTimes)) {
+ QVERIFY(q.next());
+ QCOMPARE(q.value(0).toDateTime(), dt);
+ }
+}
+
void tst_QSqlQuery::sqliteVirtualTable()
{
// Virtual tables can behave differently when it comes to prepared
@@ -4953,6 +5031,41 @@ void tst_QSqlQuery::ibaseArray()
QCOMPARE(qry.value(3).toList(), boolArray.toList());
}
+void tst_QSqlQuery::ibaseTimeStampTzArray()
+{
+ QFETCH(QString, dbName);
+ QSqlDatabase db = QSqlDatabase::database(dbName);
+ CHECK_DATABASE(db);
+
+ if (tst_Databases::getIbaseEngineVersion(db).majorVersion() < 4)
+ QSKIP("Time zone support only implemented for firebird engine version 4 and greater");
+
+ TableScope ts(db, "ibasetstzarray", __FILE__);
+ QSqlQuery qry(db);
+ QVERIFY_SQL(qry, exec(QLatin1String(
+ "create table %1 (timeStampData timestamp with time zone[0:4])").arg(ts.tableName())));
+ QVERIFY_SQL(qry, prepare(QLatin1String("insert into %1 (timeStampData)"
+ " values(?)").arg(ts.tableName())));
+
+ const QDateTime dtWithAfterTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), QTimeZone("Asia/Hong_Kong"));
+ const QDateTime dtWithBeforeTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), QTimeZone("America/Los_Angeles"));
+ const QDateTime dtWithUTCTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), QTimeZone("UTC"));
+ const QDateTime dtLocalTZ(QDateTime::currentDateTime());
+ const QDateTime dtWithMETTZ(QDate(2015, 5, 18), QTime(4, 26, 30, 500), QTimeZone("MET"));
+
+
+ const auto timeStampData = QVariant{QVariantList{dtWithAfterTZ,
+ dtWithBeforeTZ,
+ dtWithUTCTZ,
+ dtLocalTZ,
+ dtWithMETTZ}};
+ qry.bindValue(0, timeStampData);
+ QVERIFY_SQL(qry, exec());
+ QVERIFY_SQL(qry, exec("select * from " + ts.tableName()));
+ QVERIFY(qry.next());
+ QCOMPARE(qry.value(0).toList(), timeStampData.toList());
+}
+
void tst_QSqlQuery::ibase_executeBlock()
{
QFETCH(QString, dbName);