diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp index d2dc9f6a811..6df7aa95074 100644 --- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp +++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp @@ -3,11 +3,13 @@ #include "qsql_ibase_p.h" #include +#include #include #include #include #include #include +#include #include #include #include @@ -41,6 +43,10 @@ using namespace Qt::StringLiterals; #define blr_boolean_dtype blr_bool #endif +#if (defined(QT_SUPPORTS_INT128) || defined(Q_CC_MSVC)) && (FB_API_VER >= 40) +#define IBASE_INT128_SUPPORTED +#endif + constexpr qsizetype QIBaseChunkSize = SHRT_MAX / 2; #if (FB_API_VER >= 40) @@ -100,6 +106,9 @@ static void initDA(XSQLDA *sqlda) case SQL_TIMESTAMP: #if (FB_API_VER >= 40) case SQL_TIMESTAMP_TZ: +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: +#endif #endif case SQL_TYPE_TIME: case SQL_TYPE_DATE: @@ -400,6 +409,32 @@ protected: int numRowsAffected() override; QSqlRecord record() const override; + template + static QString numberToHighPrecision(T val, int scale) + { + const bool negative = val < 0; + QString number; +#ifdef IBASE_INT128_SUPPORTED + if constexpr (std::is_same_v) { + number = negative ? qint128toBasicLatin(val * -1) + : qint128toBasicLatin(val); + } else +#endif + number = negative ? QString::number(qAbs(val)) + : QString::number(val); + auto len = number.size(); + scale *= -1; + if (scale >= len) { + number = QString(scale - len + 1, u'0') + number; + len = number.size(); + } + const auto sepPos = len - scale; + number = number.left(sepPos) + u'.' + number.mid(sepPos); + if (negative) + number = u'-' + number; + return number; + } + template QVariant applyScale(T val, int scale) const { @@ -413,25 +448,8 @@ protected: return QVariant(qint64(val * pow(10.0, scale))); case QSql::LowPrecisionDouble: return QVariant(double(val * pow(10.0, scale))); - case QSql::HighPrecision: { - const bool negative = val < 0; - QString number; - if constexpr (std::is_signed_v || negative) - number = QString::number(qAbs(val)); - else - number = QString::number(val); - auto len = number.size(); - scale *= -1; - if (scale >= len) { - number = QString(scale - len + 1, u'0') + number; - len = number.size(); - } - const auto sepPos = len - scale; - number = number.left(sepPos) + u'.' + number.mid(sepPos); - if (negative) - number = u'-' + number; - return QVariant(number); - } + case QSql::HighPrecision: + return QVariant(numberToHighPrecision(val, scale)); } return QVariant(val); } @@ -440,8 +458,23 @@ protected: void setWithScale(const QVariant &val, int scale, char *data) { auto ptr = reinterpret_cast(data); - if (scale < 0) - *ptr = static_cast(floor(0.5 + val.toDouble() * pow(10.0, scale * -1))); + if (scale < 0) { + double d = floor(0.5 + val.toDouble() * pow(10.0, scale * -1)); +#ifdef IBASE_INT128_SUPPORTED + if constexpr (std::is_same_v) { + quint64 lower = quint64(d); + quint64 tmp = quint64(std::numeric_limits::max()) + 1; + d /= tmp; + d /= tmp; + quint64 higher = quint64(d); + qinternalint128 result = higher; + result <<= 64; + result += lower; + *ptr = static_cast(result); + } else +#endif + *ptr = static_cast(d); + } else *ptr = val.value(); } @@ -1107,6 +1140,12 @@ bool QIBaseResult::exec() case SQL_INT64: setWithScale(val, d->inda->sqlvar[para].sqlscale, d->inda->sqlvar[para].sqldata); break; +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: + setWithScale(val, d->inda->sqlvar[para].sqlscale, + d->inda->sqlvar[para].sqldata); + break; +#endif case SQL_LONG: if (d->inda->sqlvar[para].sqllen == 4) setWithScale(val, d->inda->sqlvar[para].sqlscale, d->inda->sqlvar[para].sqldata); @@ -1272,6 +1311,17 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) row[idx] = applyScale(val, scale); break; } +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: { + Q_ASSERT(d->sqlda->sqlvar[i].sqllen == sizeof(qinternalint128)); + const qinternalint128 val128 = qFromUnaligned(buf); + const auto scale = d->sqlda->sqlvar[i].sqlscale; + row[idx] = numberToHighPrecision(val128, scale); + if (numericalPrecisionPolicy() != QSql::HighPrecision) + row[idx] = applyScale(row[idx].toDouble(), 0); + break; + } +#endif case SQL_LONG: if (d->sqlda->sqlvar[i].sqllen == 4) { const auto val = *(qint32 *)buf; diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp index 56fb5cd05fc..92bedfab06e 100644 --- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp +++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp @@ -256,6 +256,9 @@ private slots: void ibaseDateTimeWithTZ(); void ibaseTimeStampTzArray_data() { generic_data("QIBASE"); } void ibaseTimeStampTzArray(); + void ibaseInt128_data() { generic_data("QIBASE"); } + void ibaseInt128(); + void psqlJsonOperator_data() { generic_data("QPSQL"); } void psqlJsonOperator(); @@ -1872,6 +1875,9 @@ void tst_QSqlQuery::oci_rawField() QCOMPARE(q.value(0).toByteArray().size(), qsizetype(7)); } +// Test whether we can fetch values with more than DOUBLE precision +// note that SQLite highest precision is that of a double, although +// you can define field with higher precision: // Test whether we can fetch values with more than DOUBLE precision // note that SQLite highest precision is that of a double, although // you can define field with higher precision: @@ -1881,46 +1887,45 @@ void tst_QSqlQuery::precision() QSqlDatabase db = QSqlDatabase::database(dbName); CHECK_DATABASE(db); const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db); - if (dbType == QSqlDriver::Interbase) - QSKIP("DB unable to store high precision"); const auto tidier = qScopeGuard([db, oldPrecision = db.driver()->numericalPrecisionPolicy()]() { db.driver()->setNumericalPrecisionPolicy(oldPrecision); }); + int digits = 21; + int decimals = 20; + std::array precStrings = { "1.2345678901234567891"_L1, + "-1.2345678901234567891"_L1 }; + if (dbType == QSqlDriver::SQLite) { + // SQLite 3.45 does not return more, even when speicfied + digits = 17; + decimals = 16; + precStrings = { "1.2345678901234567"_L1, "-1.2345678901234567"_L1 }; + } else if (dbType == QSqlDriver::Sybase) + decimals = 18; db.driver()->setNumericalPrecisionPolicy(QSql::HighPrecision); TableScope ts(db, "qtest_precision", __FILE__); - static const QLatin1String precStr("1.2345678901234567891"); - { - // need a new scope for SQLITE - QSqlQuery q(db); - - QVERIFY_SQL(q, exec(QLatin1String(tst_Databases::isMSAccess(db) - ? "CREATE TABLE %1 (col1 number)" - : "CREATE TABLE %1 (col1 numeric(21, 20))") - .arg(ts.tableName()))); - - QVERIFY_SQL(q, exec(QLatin1String("INSERT INTO %1 (col1) VALUES (%2)") - .arg(ts.tableName(), precStr))); + QSqlQuery q(db); + QString stmt = "CREATE TABLE %1 (col1 numeric("_L1 + QString::number(digits) + ", "_L1 + + QString::number(decimals) + "))"_L1; + if (tst_Databases::isMSAccess(db)) + stmt = "CREATE TABLE %1 (col1 number)"_L1; + QVERIFY_SQL(q, exec(stmt.arg(ts.tableName()))); + for (const auto &precStr : precStrings) { + QVERIFY_SQL(q, exec("DELETE FROM %1"_L1.arg(ts.tableName()))); + QVERIFY_SQL(q, exec("INSERT INTO %1 (col1) VALUES (%2)"_L1.arg(ts.tableName(), precStr))); QVERIFY_SQL(q, exec("SELECT * FROM " + ts.tableName())); QVERIFY(q.next()); const QString val = q.value(0).toString(); if (!val.startsWith(precStr)) { int i = 0; - while (i < val.size() && precStr[i] != 0 && precStr[i] == val[i].toLatin1()) + while (i < val.size() && precStr[i] != 0 && precStr[i] == val[i]) ++i; - - // TDS has crappy precisions by default - if (dbType == QSqlDriver::Sybase) { - if (i < 18) - qWarning("TDS didn't return the right precision"); - } else { - qWarning() << tst_Databases::dbToString(db) << "didn't return the right precision (" - << i << "out of 21)," << val; - } + qWarning() << tst_Databases::dbToString(db) << "didn't return the right precision (" + << i << "out of " << digits << ")," << val; } - } // SQLITE scope + } } void tst_QSqlQuery::nullResult() @@ -5034,6 +5039,54 @@ void tst_QSqlQuery::ibaseTimeStampTzArray() #endif // QT_CONFIG(timezone) } +void tst_QSqlQuery::ibaseInt128() +{ + QFETCH(QString, dbName); + QSqlDatabase db = QSqlDatabase::database(dbName); + CHECK_DATABASE(db); + + TableScope ts(db, "int128test", __FILE__); + db.setNumericalPrecisionPolicy(QSql::HighPrecision); + QSqlQuery q(db); + if (!q.exec("CREATE TABLE " + ts.tableName() + " (id INT PRIMARY KEY, price NUMERIC(20, 4))")) + QSKIP("Need at least Firebird 4 for this test - skipping"); + + QVERIFY_SQL(q, exec("INSERT INTO " + ts.tableName() + "(id,price) values(1,40001.1234)")); + QVERIFY_SQL(q, prepare("INSERT INTO " + ts.tableName() + "(id,price) values(2,:amount)")); + q.bindValue(":amount", 12345.67890); + QVERIFY_SQL(q, exec()); + { + QSqlQuery q2(db); + q2.setNumericalPrecisionPolicy(QSql::LowPrecisionDouble); + QVERIFY_SQL(q2, exec("SELECT price FROM " + ts.tableName() + " ORDER BY id")); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).metaType().id(), QMetaType::Double); + QCOMPARE(q2.value(0).toDouble(), 40001.1234); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).metaType().id(), QMetaType::Double); + QCOMPARE(q2.value(0).toDouble(), 12345.6789); + QVERIFY_SQL(q2, exec("SELECT sum(price) FROM " + ts.tableName())); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).metaType().id(), QMetaType::Double); + QCOMPARE(q2.value(0).toDouble(), 52346.8023); + } + { + QSqlQuery q2(db); + q2.setNumericalPrecisionPolicy(QSql::HighPrecision); + QVERIFY_SQL(q2, exec("SELECT price FROM " + ts.tableName() + " ORDER BY id")); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).metaType().id(), QMetaType::QString); + QCOMPARE(q2.value(0).toString(), "40001.1234"); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).metaType().id(), QMetaType::QString); + QCOMPARE(q2.value(0).toString(), "12345.6789"); + QVERIFY_SQL(q2, exec("SELECT sum(price) FROM " + ts.tableName())); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).metaType().id(), QMetaType::QString); + QCOMPARE(q2.value(0).toString(), "52346.8023"); + } +} + void tst_QSqlQuery::ibase_executeBlock() { QFETCH(QString, dbName);