6#include "../SqlColumnTypeDefinitions.hpp"
7#include "../SqlError.hpp"
27inline constexpr std::uint8_t SqlMaxDynamicNumericPrecision = 19;
32 [[nodiscard]]
constexpr std::string_view TrimAsciiWhitespace(std::string_view text)
noexcept
34 auto const isSpace = [](
char c)
noexcept {
35 return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n';
37 while (!text.empty() && isSpace(text.front()))
38 text.remove_prefix(1);
39 while (!text.empty() && isSpace(text.back()))
40 text.remove_suffix(1);
47 [[nodiscard]]
constexpr bool TryAppendDecimalDigit(std::int64_t& value, std::int64_t digit)
noexcept
49 if (value > (INT64_MAX - digit) / 10)
51 value = (value * 10) + digit;
65 inline constexpr std::array<std::int64_t, SqlMaxDynamicNumericPrecision> PowersOfTen = [] {
66 auto powers = std::array<std::int64_t, SqlMaxDynamicNumericPrecision> {};
67 auto value = std::int64_t { 1 };
68 for (
auto& power: powers)
72 if (value <= INT64_MAX / 10)
82 inline constexpr std::array<double, SqlMaxDynamicNumericPrecision + 1> DoublePowersOfTen = [] {
83 auto powers = std::array<double, SqlMaxDynamicNumericPrecision + 1> {};
85 for (
auto& power: powers)
98 [[nodiscard]]
constexpr bool TryScaleByPowerOfTen(std::int64_t& value, std::uint8_t count)
noexcept
102 if (count >= PowersOfTen.size())
105 auto const factor = PowersOfTen[count];
108 if (value > 0 && value > INT64_MAX / factor)
110 if (value < 0 && value < INT64_MIN / factor)
118 struct DecimalParseState
120 std::int64_t unscaled = 0;
121 std::uint8_t fractionDigits = 0;
122 bool sawDecimalPoint =
false;
123 bool sawDigit =
false;
129 [[nodiscard]]
constexpr bool TryConsumeDecimalCharacter(DecimalParseState& state,
131 std::uint8_t targetScale)
noexcept
133 if (character ==
'.')
135 if (state.sawDecimalPoint)
137 state.sawDecimalPoint =
true;
141 if (character <
'0' || character >
'9')
144 if (state.sawDecimalPoint)
147 if (state.fractionDigits == targetScale)
149 ++state.fractionDigits;
152 state.sawDigit =
true;
153 return TryAppendDecimalDigit(state.unscaled,
static_cast<std::int64_t
>(character -
'0'));
163 [[nodiscard]]
constexpr std::uint8_t FractionDigitsOf(std::string_view text)
noexcept
168 text = TrimAsciiWhitespace(text);
170 auto const decimalPoint = text.find(
'.');
171 if (decimalPoint == std::string_view::npos)
174 auto digits = std::size_t { 0 };
175 for (
char const character: text.substr(decimalPoint + 1))
177 if (character <
'0' || character >
'9')
181 return static_cast<std::uint8_t
>(std::min<std::size_t>(digits, SqlMaxDynamicNumericPrecision));
198 [[nodiscard]]
constexpr std::optional<std::int64_t> ParseUnscaledDecimal(std::string_view text,
199 std::uint8_t targetScale)
noexcept
201 text = TrimAsciiWhitespace(text);
205 bool isNegative =
false;
206 if (text.front() ==
'+' || text.front() ==
'-')
208 isNegative = text.front() ==
'-';
209 text.remove_prefix(1);
212 auto state = DecimalParseState {};
213 for (
char const character: text)
214 if (!TryConsumeDecimalCharacter(state, character, targetScale))
220 if (!TryScaleByPowerOfTen(state.unscaled,
static_cast<std::uint8_t
>(targetScale - state.fractionDigits)))
223 return isNegative ? -state.unscaled : state.unscaled;
254 if (
scale == other.scale)
258 auto const coarser =
scale < other.scale ? *this : other;
259 auto const finer =
scale < other.scale ? other : *
this;
263 auto rescaled = coarser.unscaledValue;
264 if (!detail::TryScaleByPowerOfTen(rescaled,
static_cast<std::uint8_t
>(finer.scale - coarser.scale)))
267 return rescaled == finer.unscaledValue;
273 return !(*
this == other);
280 [[nodiscard]]
constexpr double ToDouble() const noexcept
283 if (
scale == 0 ||
scale >= detail::DoublePowersOfTen.size())
298 auto const magnitude =
301 auto digits = std::to_string(magnitude);
304 if (digits.size() <=
scale)
305 digits.insert(0, std::string(
scale - digits.size() + 1,
'0'));
306 digits.insert(digits.size() -
scale,
".");
308 return negative ?
"-" + digits : digits;
317 [[nodiscard]]
static constexpr std::optional<SqlDynamicNumeric>
FromString(std::string_view text,
319 std::uint8_t
scale)
noexcept
321 auto const unscaled = detail::ParseUnscaledDecimal(text,
scale);
340 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .
precision = 38, .scale = 0 };
351 auto const literal = value.ToString();
352 auto*
const buffer =
reinterpret_cast<char*
>(cb.ProvideBatchStagingBuffer(literal.size() + 1));
353 std::ranges::copy(literal, buffer);
354 buffer[literal.size()] =
'\0';
356 auto*
const indicator = cb.ProvideInputIndicator();
357 *indicator =
static_cast<SQLLEN
>(literal.size());
359 auto const columnSize =
static_cast<SQLULEN
>(value.precision != 0 ? value.precision : SqlMaxDynamicNumericPrecision);
360 return SQLBindParameter(stmt,
366 static_cast<SQLSMALLINT
>(value.scale),
368 static_cast<SQLLEN
>(literal.size() + 1),
376 SQLRETURN returnCode = SQL_SUCCESS;
399 [[nodiscard]]
static LIGHTWEIGHT_FORCE_INLINE Literal
ReadLiteral(SQLHSTMT stmt,
402 std::span<char> buffer)
noexcept
407 SQLLEN discardedIndicator = 0;
408 auto*
const lengthIndicator = indicator ? indicator : &discardedIndicator;
410 auto const returnCode =
411 SQLGetData(stmt, column, SQL_C_CHAR, buffer.data(),
static_cast<SQLLEN
>(buffer.size()), lengthIndicator);
412 if (!SQL_SUCCEEDED(returnCode))
413 return { .returnCode = returnCode, .text = {}, .isNull =
false };
414 if (*lengthIndicator == SQL_NULL_DATA)
415 return { .returnCode = returnCode, .text = {}, .isNull =
true };
422 auto const truncated = *lengthIndicator == SQL_NO_TOTAL
423 || (*lengthIndicator >= 0 &&
static_cast<std::size_t
>(*lengthIndicator) >= buffer.size());
425 return { .returnCode = SQL_ERROR, .text = {}, .isNull =
false };
430 auto const terminator = std::ranges::find(buffer,
'\0');
431 auto const raw = std::string_view(buffer.data(),
static_cast<std::size_t
>(terminator - buffer.begin()));
432 return { .returnCode = returnCode, .text = detail::TrimAsciiWhitespace(raw), .isNull =
false };
439 [[nodiscard]]
static LIGHTWEIGHT_FORCE_INLINE std::optional<SqlDynamicNumeric>
FromColumnLiteral(
440 SQLHSTMT stmt, SQLUSMALLINT column, std::string_view literal)
noexcept
442 SQLLEN declaredPrecision = 0;
443 SQLLEN declaredScale = 0;
444 (void) SQLColAttributeW(stmt, column, SQL_DESC_PRECISION,
nullptr, SQLSMALLINT { 0 },
nullptr, &declaredPrecision);
445 (void) SQLColAttributeW(stmt, column, SQL_DESC_SCALE,
nullptr, SQLSMALLINT { 0 },
nullptr, &declaredScale);
449 auto const toDigitCount = [](SQLLEN reported)
noexcept {
450 return static_cast<std::uint8_t
>(std::clamp<SQLLEN>(reported, 0, SqlMaxDynamicNumericPrecision));
456 auto const scale = std::max(toDigitCount(declaredScale), detail::FractionDigitsOf(literal));
457 auto const precision = toDigitCount(declaredPrecision);
476 std::array<char, 128> buffer {};
477 auto const literal = ReadLiteral(stmt, column, indicator, buffer);
478 if (literal.text.empty())
483 return literal.returnCode;
486 auto const parsed = FromColumnLiteral(stmt, column, literal.text);
491 return literal.returnCode;
502 auto const returnCode = TryGetColumn(stmt, column, result, indicator, cb);
503 if (returnCode != SQL_ERROR)
509 .message = std::format(
"Column {} holds a decimal that SqlDynamicNumeric cannot represent exactly: "
510 "it carries more than {} significant digits, or the driver truncated the "
511 "literal. Read the column as double or std::string to accept an "
514 SqlMaxDynamicNumericPrecision),
528struct std::formatter<Lightweight::SqlDynamicNumeric>: std::formatter<std::string>
531 -> format_context::iterator
533 return std::formatter<std::string>::format(value.ToString(), ctx);
std::string_view text
The literal, or empty when the column is NULL, the read failed, or the text was truncated.
static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(SqlDynamicNumeric const &value)
Renders the value for SQL trace logs.
static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt, SQLUSMALLINT column, SqlDynamicNumeric const &value, SqlDataBinderCallback &cb) noexcept
Binds the value as an input parameter, rendered as an exact decimal literal.
static LIGHTWEIGHT_FORCE_INLINE SQLRETURN TryGetColumn(SQLHSTMT stmt, SQLUSMALLINT column, SqlDynamicNumeric *result, SQLLEN *indicator, SqlDataBinderCallback const &) noexcept
static LIGHTWEIGHT_FORCE_INLINE std::optional< SqlDynamicNumeric > FromColumnLiteral(SQLHSTMT stmt, SQLUSMALLINT column, std::string_view literal) noexcept
static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, SqlDynamicNumeric *result, SQLLEN *indicator, SqlDataBinderCallback const &cb)
static LIGHTWEIGHT_FORCE_INLINE Literal ReadLiteral(SQLHSTMT stmt, SQLUSMALLINT column, SQLLEN *indicator, std::span< char > buffer) noexcept
A fixed-point decimal whose precision and scale are known only at run time.
constexpr bool operator==(SqlDynamicNumeric const &other) const noexcept
std::string ToString() const
Renders the exact decimal representation, including the trailing zeros implied by scale.
static constexpr std::optional< SqlDynamicNumeric > FromString(std::string_view text, std::uint8_t precision, std::uint8_t scale) noexcept
std::int64_t unscaledValue
The value, scaled by 10^scale. 12.34 at scale 2 is stored as 1234.
std::uint8_t scale
Number of digits after the decimal point.
std::uint8_t precision
Total number of decimal digits the source column holds.
constexpr double ToDouble() const noexcept
constexpr bool operator!=(SqlDynamicNumeric const &other) const noexcept
Inequality, derived from operator==.
Represents an ODBC SQL error.
SQLINTEGER nativeErrorCode
The native ODBC error code.