Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsqlnumeric_p.h
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#ifndef QSQLNUMERIC_P_H
6#define QSQLNUMERIC_P_H
7
8//
9// W A R N I N G
10// -------------
11//
12// This file is not part of the Qt API. It exists purely as an
13// implementation detail. This header file may change from version to
14// version without notice, or even be removed.
15//
16// We mean it.
17//
18
19#include <QtSql/private/qtsqlglobal_p.h>
20#include <QtCore/private/qlocale_tools_p.h>
21#include <QtCore/qbytearray.h>
22#include <QtCore/qbytearrayview.h>
23#include <QtCore/qloggingcategory.h>
24#include <QtCore/qmetatype.h>
25#include <QtCore/qstring.h>
26#include <QtCore/qvariant.h>
27
28#include <limits>
29#include <type_traits>
30
31/*
32 Scaled-integer numeric helpers for SQL drivers.
33
34 Firebird and InterBase store NUMERIC/DECIMAL as an integer of 16 to 128
35 bits together with a scale: value = stored * 10^scale (scale <= 0). These
36 helpers convert such integers to and from QVariant according to
37 QSql::NumericalPrecisionPolicy and parse/format the exact decimal text that
38 HighPrecision and the bind path use — without ever going through double,
39 which would silently drop digits above 2^53.
40
41 128-bit support piggybacks on QtCore's private qinternalint128 alias
42 (qint128 where the compiler has __int128, std::_Signed128 on MSVC >= 19.30).
43 QSQL_HAS_INT128 tells a driver whether that type exists; the driver combines
44 it with its own client-library condition. Two rules keep the 128-bit
45 instantiations correct on MSVC, whose std::_Signed128 converts to narrower
46 integers by keeping the low bits and has no conversion to double: never
47 narrow without sqlFitsIn(), and never negate (format, then strip the sign).
48*/
49#if defined(QT_SUPPORTS_INT128) || defined(QT_USE_MSVC_INT128)
50# define QSQL_HAS_INT128
51#endif
52
53QT_BEGIN_NAMESPACE
54
55namespace QtPrivate {
56
57#ifdef QSQL_HAS_INT128
58template <typename T>
59inline constexpr bool sqlIsInt128 = std::is_same_v<T, qinternalint128>;
60#else
61template <typename T>
62inline constexpr bool sqlIsInt128 = false;
63#endif
64
65/*
66 10^n as T (n >= 0). Scale magnitudes are bounded by the column's precision
67 (<= 38 for INT128, <= 18 for INT64), so this never overflows for the type
68 backing the column it is used with.
69*/
70template <typename T>
72{
73 T r = 1;
74 for (int i = 0; i < n; ++i)
75 r *= 10;
76 return r;
77}
78
79/*
80 Decimal text of an integer of any width. The 128-bit type has no
81 QString::number overload; qint128toBasicLatin formats it (correctly for the
82 minimum value, too).
83*/
84template <typename T>
86{
87#ifdef QSQL_HAS_INT128
88 if constexpr (sqlIsInt128<T>)
89 return qint128toBasicLatin(val);
90 else
91#endif
92 return QString::number(static_cast<qint64>(val));
93}
94
95/*
96 True if val is representable as Narrow. The comparison happens in T, so the
97 result is exact even when a cast would silently truncate.
98*/
99template <typename Narrow, typename T>
100bool sqlFitsIn(T val)
101{
102 return val >= T((std::numeric_limits<Narrow>::min)())
103 && val <= T((std::numeric_limits<Narrow>::max)());
104}
105
106/*
107 Format a scaled integer as an exact decimal string.
108 e.g. val=12345, scale=-2 -> "123.45"; val=12, scale=2 -> "1200"
109 The integer is formatted once and the zero padding and the decimal point
110 are inserted in place behind the sign, which never moves: no negation (so
111 the minimum of T formats correctly) and no left/mid/concatenation chain on
112 this per-row path.
113*/
114template <typename T>
115QString sqlScaledToString(T val, int scale)
116{
117 QString number = sqlIntegerToString(val);
118 if (scale == 0)
119 return number;
120 if (scale > 0) {
121 // value = stored * 10^scale -> an integer with `scale` trailing zeros
122 // (but 0 * 10^n is "0", not "000")
123 if (val != 0)
124 number.resize(number.size() + scale, u'0');
125 return number;
126 }
127 const qsizetype signLen = val < 0 ? 1 : 0;
128 const qsizetype absScale = -scale;
129 const qsizetype digits = number.size() - signLen;
130 // Fewer digits than decimals: "5" at scale -3 needs "0.005"
131 const qsizetype leadingZeros = absScale >= digits ? absScale - digits + 1 : 0;
132 number.reserve(number.size() + leadingZeros + 1);
133 if (leadingZeros)
134 number.insert(signLen, QString(leadingZeros, u'0'));
135 number.insert(number.size() - absScale, u'.');
136 return number;
137}
138
139/*
140 The double nearest to val * 10^scale, for every width of T.
141
142 When val is below 2^53 in magnitude and |scale| <= 22, both operands are
143 exact doubles (10^k is exactly representable up to 10^22), so the single
144 IEEE division or multiplication is correctly rounded. Multiplying by an
145 inexact 10^-k, as a double-based conversion naively would, is not:
146 998 * 0.1 gives 99.80000000000001. Anything wider goes through the exact
147 decimal text, which toDouble() rounds correctly as well (and which is the
148 only route for 128-bit values on MSVC, whose std::_Signed128 has no
149 conversion to double). Both routes agree, so a NUMERIC value yields the
150 same double whether the engine backs it with 16, 32, 64 or 128 bits.
151*/
152template <typename T>
153double sqlScaledToDouble(T val, int scale)
154{
155 static constexpr double pow10[] = {
156 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
157 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
158 };
159 constexpr qint64 exactLimit = qint64(1) << 53;
160 using Wide = std::conditional_t<sqlIsInt128<T>, T, qint64>;
161 const Wide wide = static_cast<Wide>(val);
162 if (wide > Wide(-exactLimit) && wide < Wide(exactLimit) && scale >= -22 && scale <= 22) {
163 const double d = static_cast<double>(static_cast<qint64>(wide));
164 return scale < 0 ? d / pow10[-scale] : d * pow10[scale];
165 }
166 return sqlScaledToString(val, scale).toDouble();
167}
168
169/*
170 Return a scaled integer as the QVariant dictated by the precision policy:
171 HighPrecision -> exact decimal QString; LowPrecisionDouble -> the nearest
172 double (sqlScaledToDouble); LowPrecisionInt32/Int64 -> the integer part,
173 computed with integer arithmetic (truncating toward zero) so large values
174 keep their precision. scale == 0 returns a sub-128-bit value as is, with
175 the usual integral promotion (qint16/qint32 -> Int, qint64 -> LongLong).
176
177 An integer part that does not fit the requested LowPrecisionInt target is
178 returned as a double instead of being wrapped, for every width; *overflow
179 is set so the driver can log it.
180*/
181template <typename T>
182QVariant sqlApplyScale(T val, int scale, QSql::NumericalPrecisionPolicy policy,
183 bool *overflow = nullptr)
184{
185 if (overflow)
186 *overflow = false;
187 // The integer policies compute in qint64 below 128 bits, in T itself at 128.
188 using Wide = std::conditional_t<sqlIsInt128<T>, T, qint64>;
189 const auto asDouble = [&] { return sqlScaledToDouble(val, scale); };
190
191 if constexpr (!sqlIsInt128<T>) {
192 if (scale == 0)
193 return QVariant(val);
194 }
195 switch (policy) {
196 case QSql::LowPrecisionInt32:
197 case QSql::LowPrecisionInt64: {
198 const Wide wide = static_cast<Wide>(val);
199 const Wide scaled = scale < 0 ? wide / sqlIntPow10<Wide>(-scale)
200 : wide * sqlIntPow10<Wide>(scale);
201 if (policy == QSql::LowPrecisionInt32 && sqlFitsIn<qint32>(scaled))
202 return QVariant(static_cast<qint32>(scaled));
203 if (policy == QSql::LowPrecisionInt64 && sqlFitsIn<qint64>(scaled))
204 return QVariant(static_cast<qint64>(scaled));
205 if (overflow)
206 *overflow = true;
207 Q_FALLTHROUGH();
208 }
209 case QSql::LowPrecisionDouble:
210 return QVariant(asDouble());
211 case QSql::HighPrecision:
212 return QVariant(sqlScaledToString(val, scale));
213 }
214 return QVariant(asDouble());
215}
216
217/*
218 Report sqlApplyScale's overflow - the integer part did not fit the
219 LowPrecisionInt target, so a double was returned - on the driver's logging
220 category, passed as its category function so qCWarning's enablement check
221 applies. value is the exact decimal text, as sqlScaledToString formats it.
222 The one message text keeps the drivers' output identical.
223*/
224template <typename Category>
225void sqlWarnLowPrecisionOverflow(Category &&category, const QString &value,
226 QSql::NumericalPrecisionPolicy policy)
227{
228 qCWarning(category, "INT128 value %s exceeds the LowPrecisionInt%d range; returning a double",
229 qUtf8Printable(value), policy == QSql::LowPrecisionInt32 ? 32 : 64);
230}
231
232/*
233 Rewrite a decimal in exponent notation, as QByteArray::number(d, 'g', ...)
234 produces it ("1.5e-19", "-2.5e+05"), as the same digits in plain notation
235 ("0.00000000000000000015", "-250000") by moving the decimal point. No digit
236 is dropped or invented. Returns false if the text is not of that form.
237*/
238inline bool sqlExpandExponent(QByteArrayView in, QByteArray *out)
239{
240 qsizetype ePos = in.indexOf('e');
241 if (ePos < 0)
242 ePos = in.indexOf('E');
243 if (ePos < 0)
244 return false;
245 bool ok = false;
246 const int exponent = in.sliced(ePos + 1).toInt(&ok); // "+20", "-05"
247 if (!ok)
248 return false;
249
250 qsizetype pos = 0;
251 bool negative = false;
252 if (pos < ePos && (in[pos] == '+' || in[pos] == '-'))
253 negative = (in[pos++] == '-');
254 QByteArray digits; // the mantissa's digits, point removed
255 qsizetype intDigits = -1; // how many of them precede the point
256 for (; pos < ePos; ++pos) {
257 const char c = in[pos];
258 if (c == '.') {
259 if (intDigits >= 0)
260 return false;
261 intDigits = digits.size();
262 } else if (c >= '0' && c <= '9') {
263 digits.append(c);
264 } else {
265 return false;
266 }
267 }
268 if (digits.isEmpty())
269 return false;
270 if (intDigits < 0)
271 intDigits = digits.size();
272
273 // Where the point lands within digits once the exponent is applied
274 const qsizetype point = intDigits + exponent;
275 const QByteArrayView digitsView(digits);
276 QByteArray result;
277 result.reserve(digits.size() + qAbs(exponent) + 3);
278 if (negative)
279 result.append('-');
280 if (point <= 0) {
281 result.append("0.");
282 result.append(-point, '0');
283 result.append(digitsView);
284 } else if (point >= digits.size()) {
285 result.append(digitsView);
286 result.append(point - digits.size(), '0');
287 } else {
288 result.append(digitsView.first(point));
289 result.append('.');
290 result.append(digitsView.sliced(point));
291 }
292 *out = result;
293 return true;
294}
295
296/*
297 Variant types the encoders treat as plain integers: they scale with one
298 checked multiply where the target type allows (sqlScaledFromVariant), and
299 their decimal text is their value - a Bool is "1" or "0", not "true".
300*/
301inline bool sqlIsIntegralVariant(int typeId)
302{
303 return typeId == QMetaType::Int || typeId == QMetaType::UInt
304 || typeId == QMetaType::LongLong || typeId == QMetaType::ULongLong
305 || typeId == QMetaType::Bool;
306}
307
308/*
309 The two bind failures, worded identically by every encoder (the native
310 128-bit codec and Firebird's string interface alike): an integral value
311 the column cannot hold, and text that is malformed or does not fit.
312*/
313inline QString sqlOutOfRangeError(const QVariant &val, int scale)
314{
315 return QString::fromLatin1("Value '%1' out of range for NUMERIC/DECIMAL with scale %2")
316 .arg(val.toString(), QString::number(scale));
317}
318
319inline QString sqlCannotEncodeError(const QByteArray &text, int scale)
320{
321 return QString::fromLatin1("Cannot encode '%1' as NUMERIC/DECIMAL with scale %2")
322 .arg(QString::fromLatin1(text), QString::number(scale));
323}
324
325/*
326 Canonical decimal text of a value bound to a scaled numeric parameter.
327 Integer and string variants are already plain C-locale decimals; a Bool
328 becomes "1" or "0" (QVariant::toByteArray() would say "true"). A
329 floating-point variant's text is its shortest round-trip form, which
330 QVariant::toByteArray() renders in exponent notation for large and small
331 magnitudes; the plain-decimal grammar below rejects exponents, so that form
332 is expanded digit for digit rather than re-formatted with a fixed
333 precision, which would drop the digits beyond it.
334*/
335inline QByteArray sqlNumericInputString(const QVariant &val)
336{
337 const int t = val.typeId();
338 if (t == QMetaType::Bool)
339 return val.toBool() ? QByteArray("1") : QByteArray("0");
340 QByteArray s = val.toByteArray();
341 if (t == QMetaType::Double || t == QMetaType::Float) {
342 QByteArray plain;
343 if (sqlExpandExponent(s, &plain))
344 s = plain;
345 }
346 return s;
347}
348
349/*
350 Split a plain decimal string into the parts of the scaled integer a column
351 stores: shift the decimal point by -scale digits (right for the usual
352 negative scale) and report the sign, the digits of the resulting integer
353 part and whether the first dropped digit rounds it away from zero. Accepts
354 an optional sign, digits and at most one decimal point, with surrounding
355 whitespace; anything else (including exponent notation) is rejected.
356 Each output pointer may be null; with all three null only the grammar is
357 checked.
358*/
359inline bool sqlSplitScaledDecimal(QByteArrayView decimal, int scale, bool *negativeOut,
360 QByteArray *intPartOut, bool *roundUpOut)
361{
362 const QByteArrayView s = decimal.trimmed();
363 qsizetype pos = 0;
364 bool negative = false;
365 if (pos < s.size() && (s[pos] == '+' || s[pos] == '-'))
366 negative = (s[pos++] == '-');
367
368 QByteArray intPart, fracPart;
369 bool dot = false, anyDigit = false;
370 for (; pos < s.size(); ++pos) {
371 const char c = s[pos];
372 if (c == '.') {
373 if (dot)
374 return false;
375 dot = true;
376 } else if (c >= '0' && c <= '9') {
377 (dot ? fracPart : intPart).append(c);
378 anyDigit = true;
379 } else {
380 return false;
381 }
382 }
383 if (!anyDigit)
384 return false;
385 if (!negativeOut && !intPartOut && !roundUpOut)
386 return true; // validation only
387
388 // Shift the decimal point right by -scale digits (left for positive scale).
389 const int shift = -scale;
390 if (shift >= 0) {
391 while (fracPart.size() < shift)
392 fracPart.append('0');
393 intPart += fracPart.left(shift);
394 fracPart.remove(0, shift);
395 } else {
396 const qsizetype k = -shift;
397 while (intPart.size() < k)
398 intPart.prepend('0');
399 fracPart.prepend(intPart.right(k));
400 intPart.chop(k);
401 }
402
403 // Round half away from zero on the first remaining fractional digit.
404 if (roundUpOut)
405 *roundUpOut = !fracPart.isEmpty() && fracPart[0] >= '5';
406 if (negativeOut)
407 *negativeOut = negative;
408 if (intPartOut)
409 *intPartOut = intPart;
410 return true;
411}
412
413/*
414 True if the string satisfies sqlDecimalToScaled's grammar. Lets a driver
415 that hands the text to its client library reject exactly what the native
416 parser rejects.
417*/
418inline bool sqlIsPlainDecimal(QByteArrayView decimal)
419{
420 return sqlSplitScaledDecimal(decimal, 0, nullptr, nullptr, nullptr);
421}
422
423/*
424 Convert a plain decimal string into the scaled integer of type T, rounding
425 half away from zero and detecting overflow. The digits accumulate toward
426 the sign — acc*10 - d for negatives — so the minimum of T is reachable and
427 T is never negated. Each step is bounds-checked before it happens; `/`
428 truncates toward zero for every T used here, so the bounds are exact.
429*/
430template <typename T>
431bool sqlDecimalToScaled(QByteArrayView decimal, int scale, T *out)
432{
433 bool negative = false, roundUp = false;
434 QByteArray intPart;
435 if (!sqlSplitScaledDecimal(decimal, scale, &negative, &intPart, &roundUp))
436 return false;
437
438 const T lo = (std::numeric_limits<T>::min)();
439 const T hi = (std::numeric_limits<T>::max)();
440 T acc = 0;
441 for (const char c : intPart) {
442 const int d = c - '0';
443 if (negative) {
444 if (acc < (lo + d) / 10)
445 return false; // overflow
446 acc = acc * 10 - d;
447 } else {
448 if (acc > (hi - d) / 10)
449 return false; // overflow
450 acc = acc * 10 + d;
451 }
452 }
453 if (roundUp) {
454 if (acc == (negative ? lo : hi))
455 return false; // overflow
456 acc += negative ? -1 : 1;
457 }
458 *out = acc;
459 return true;
460}
461
462/*
463 Encode a bound value as the scaled integer of type T for a column with the
464 given scale (0 for a plain integer column). Integral variants scale with
465 one checked multiply; everything else goes through its canonical decimal
466 string, so strings and doubles are encoded exactly — never via a double
467 round trip. On failure returns false and, if errorText is given, describes
468 the problem.
469*/
470template <typename T>
471bool sqlScaledFromVariant(const QVariant &val, int scale, T *out, QString *errorText)
472{
473 const auto outOfRange = [&] {
474 if (errorText)
475 *errorText = sqlOutOfRangeError(val, scale);
476 return false;
477 };
478
479 const int t = val.typeId();
480 // 10^digits10 is the largest power of ten T can hold.
481 if (sqlIsIntegralVariant(t) && scale <= 0 && -scale <= std::numeric_limits<T>::digits10) {
482 const T lo = (std::numeric_limits<T>::min)();
483 const T hi = (std::numeric_limits<T>::max)();
484 T v = 0;
485 if (t == QMetaType::ULongLong) {
486 const quint64 u = val.toULongLong();
487 if constexpr (sizeof(T) <= sizeof(quint64)) {
488 if (u > static_cast<quint64>(hi))
489 return outOfRange();
490 }
491 v = T(u);
492 } else {
493 const qint64 ll = val.toLongLong();
494 if constexpr (sizeof(T) < sizeof(qint64)) {
495 // reject before narrowing - T(ll) would silently wrap
496 if (!sqlFitsIn<T>(ll))
497 return outOfRange();
498 }
499 v = T(ll);
500 }
501 const T factor = sqlIntPow10<T>(-scale);
502 if (v < 0 ? v < lo / factor : v > hi / factor)
503 return outOfRange();
504 *out = v * factor;
505 return true;
506 }
507
508 const QByteArray strVal = sqlNumericInputString(val);
509 if (!sqlDecimalToScaled<T>(strVal, scale, out)) {
510 if (errorText)
511 *errorText = sqlCannotEncodeError(strVal, scale);
512 return false;
513 }
514 return true;
515}
516
517#ifdef QSQL_HAS_INT128
518/*
519 Assemble a 128-bit integer from its two 64-bit words and split it again.
520 Engines transmit INT128 as a low and a high word (Firebird's FB_I128), so
521 drivers copy those words and never dereference a 16-byte-aligned type in
522 a message buffer; going word-wise also keeps the result independent of the
523 host's byte order.
524*/
526{
528 u <<= 64;
529 u |= low;
530 return static_cast<qinternalint128>(u);
531}
532
534{
535 const qinternaluint128 u = static_cast<qinternaluint128>(value);
536 *low = static_cast<quint64>(u);
537 *high = static_cast<quint64>(u >> 64);
538}
539#endif // QSQL_HAS_INT128
540
541} // namespace QtPrivate
542
543QT_END_NAMESPACE
544
545#endif // QSQLNUMERIC_P_H
bool sqlFitsIn(T val)
constexpr bool sqlIsInt128
T sqlIntPow10(int n)
QByteArray sqlNumericInputString(const QVariant &val)
double sqlScaledToDouble(T val, int scale)
bool sqlExpandExponent(QByteArrayView in, QByteArray *out)
bool sqlIsPlainDecimal(QByteArrayView decimal)
QString sqlIntegerToString(T val)
bool sqlScaledFromVariant(const QVariant &val, int scale, T *out, QString *errorText)
bool sqlIsIntegralVariant(int typeId)
bool sqlSplitScaledDecimal(QByteArrayView decimal, int scale, bool *negativeOut, QByteArray *intPartOut, bool *roundUpOut)
QVariant sqlApplyScale(T val, int scale, QSql::NumericalPrecisionPolicy policy, bool *overflow=nullptr)
void sqlWarnLowPrecisionOverflow(Category &&category, const QString &value, QSql::NumericalPrecisionPolicy policy)
bool sqlDecimalToScaled(QByteArrayView decimal, int scale, T *out)
QString sqlScaledToString(T val, int scale)
QString sqlCannotEncodeError(const QByteArray &text, int scale)
QString sqlOutOfRangeError(const QVariant &val, int scale)