SQL/IBase: add partial support for SQL_INT128 datatype
Implement partial support for SQL_INT128 datatype which is used for DECIMAL/NUMERIC columns with a precision > 18. This support is only available when QT_SUPPORTS_INT128 is defined and for MSVC. Binding values to columns which need SQL_INT128 is supported but numbers given as QString will be converted to doubles even if QSql::HighPrecision is set. [ChangeLog][SQL][IBASE] Added support for SQL_INT128 datatype. Task-number: QTBUG-124575 Change-Id: If3fb1eb0f19dc60f000d163f3bf45da7acb08c87 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Andreas Bacher <andi.bacher@outlook.com>
This commit is contained in:
parent
e9c6adb992
commit
373ae6cbd2
@ -3,11 +3,13 @@
|
||||
|
||||
#include "qsql_ibase_p.h"
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtCore/qendian.h>
|
||||
#include <QtCore/qdatetime.h>
|
||||
#include <QtCore/qtimezone.h>
|
||||
#include <QtCore/qdeadlinetimer.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/private/qlocale_tools_p.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtCore/qmap.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
@ -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<typename T>
|
||||
static QString numberToHighPrecision(T val, int scale)
|
||||
{
|
||||
const bool negative = val < 0;
|
||||
QString number;
|
||||
#ifdef IBASE_INT128_SUPPORTED
|
||||
if constexpr (std::is_same_v<qinternalint128, T>) {
|
||||
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<typename T>
|
||||
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<T> || 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<T *>(data);
|
||||
if (scale < 0)
|
||||
*ptr = static_cast<T>(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<qinternalint128, T>) {
|
||||
quint64 lower = quint64(d);
|
||||
quint64 tmp = quint64(std::numeric_limits<quint32>::max()) + 1;
|
||||
d /= tmp;
|
||||
d /= tmp;
|
||||
quint64 higher = quint64(d);
|
||||
qinternalint128 result = higher;
|
||||
result <<= 64;
|
||||
result += lower;
|
||||
*ptr = static_cast<T>(result);
|
||||
} else
|
||||
#endif
|
||||
*ptr = static_cast<T>(d);
|
||||
}
|
||||
else
|
||||
*ptr = val.value<T>();
|
||||
}
|
||||
@ -1107,6 +1140,12 @@ bool QIBaseResult::exec()
|
||||
case SQL_INT64:
|
||||
setWithScale<qint64>(val, d->inda->sqlvar[para].sqlscale, d->inda->sqlvar[para].sqldata);
|
||||
break;
|
||||
#ifdef IBASE_INT128_SUPPORTED
|
||||
case SQL_INT128:
|
||||
setWithScale<qinternalint128>(val, d->inda->sqlvar[para].sqlscale,
|
||||
d->inda->sqlvar[para].sqldata);
|
||||
break;
|
||||
#endif
|
||||
case SQL_LONG:
|
||||
if (d->inda->sqlvar[para].sqllen == 4)
|
||||
setWithScale<qint32>(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<qinternalint128>(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;
|
||||
|
@ -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<QLatin1String, 2> 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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user