5#include "../SqlColumnTypeDefinitions.hpp"
6#include "../SqlError.hpp"
8#include "Primitives.hpp"
17#include <source_location>
23static_assert(
sizeof(Int128) ==
sizeof(SQL_NUMERIC_STRUCT::val));
35 [[nodiscard]]
constexpr std::size_t DecimalDigitsForBits(std::size_t bits)
noexcept
37 return (bits * 30103) / 100'000;
80inline constexpr std::size_t SqlMaxNumericPrecision =
81 detail::DecimalDigitsForBits(64);
93template <std::
size_t ThePrecision, std::
size_t TheScale>
100 static constexpr auto Scale = TheScale;
103 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .precision =
Precision, .scale = TheScale };
105 static_assert(
Precision > 0,
"A fixed-point number must have at least one digit.");
110 static_assert(
Precision <= SqlMaxNumericPrecision,
111 "Precision exceeds the number of decimal digits this implementation can carry. Read the column as a "
112 "string instead, or narrow the column.");
117 static_assert(
Scale <=
Precision,
"Scale counts digits after the decimal point and cannot exceed Precision.");
138 constexpr
SqlNumeric(std::floating_point auto value) noexcept
144 constexpr explicit SqlNumeric(SQL_NUMERIC_STRUCT
const& value)
noexcept:
150 static_assert(std::endian::native == std::endian::little);
153 LIGHTWEIGHT_FORCE_INLINE
constexpr void assign(std::floating_point
auto inputValue)
noexcept
158 sqlValue.sign = std::signbit(inputValue) ? 0 : 1;
162 auto const unscaledValue = std::roundl(
static_cast<long double>(std::abs(inputValue) * std::powl(10.0L,
Scale)));
167 auto const num =
static_cast<Int128
>(unscaledValue);
168 std::memcpy(
sqlValue.val, &num,
sizeof(num));
188 return static_cast<Int128
>(std::roundl(
nativeValue * std::powl(10.0L,
Scale)));
193 auto magnitude = Int128 {};
194 std::memcpy(&magnitude,
sqlValue.val,
sizeof(magnitude));
196 return sqlValue.sign ? magnitude : -magnitude;
200 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
float ToFloat() const noexcept
206 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
double ToDouble() const noexcept
212 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
long double ToLongDouble() const noexcept
218 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
explicit operator float() const noexcept
224 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
explicit operator double() const noexcept
230 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
explicit operator long double() const noexcept
243 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::string
ToString()
const
246 auto digits = detail::Int128ToString(unscaled);
248 auto const negative = !digits.empty() && digits.front() ==
'-';
250 digits.erase(digits.begin());
252 if constexpr (
Scale == 0)
253 return negative ?
"-" + digits : digits;
257 if (digits.size() <=
Scale)
258 digits.insert(digits.begin(),
Scale + 1 - digits.size(),
'0');
260 digits.insert(digits.size() -
Scale, 1,
'.');
262 return negative ?
"-" + digits : digits;
272 return ToDouble() <=> other.ToDouble();
276 template <std::
size_t OtherPrecision, std::
size_t OtherScale>
280 return ToFloat() == other.ToFloat();
285concept SqlNumericType =
requires(T t) {
286 { T::Precision } -> std::convertible_to<std::size_t>;
287 { T::Scale } -> std::convertible_to<std::size_t>;
288} && std::same_as<T, SqlNumeric<T::Precision, T::Scale>>;
291template <std::
size_t Precision, std::
size_t Scale>
292struct SqlDataBinder<SqlNumeric<Precision, Scale>>
294 using ValueType = SqlNumeric<Precision, Scale>;
296 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .precision = Precision, .scale = Scale };
298 static void RequireSuccess(SQLHSTMT stmt, SQLRETURN error, std::source_location sourceLocation = std::source_location::current())
300 if (SQL_SUCCEEDED(error))
306 static constexpr bool NativeNumericSupportIsBroken(SqlServerType serverType)
noexcept
310 return serverType == SqlServerType::SQLITE || serverType == SqlServerType::MICROSOFT_SQL;
313 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
315 ValueType
const& value,
316 SqlDataBinderCallback& cb)
noexcept
318 if (NativeNumericSupportIsBroken(cb.ServerType()))
320 return SQLBindParameter(stmt,
327 (SQLPOINTER) &value.nativeValue,
328 sizeof(value.nativeValue),
337 return SQLBindParameter(stmt,
342 static_cast<SQLULEN
>(Precision),
343 static_cast<SQLSMALLINT
>(Scale),
350 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
351 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
noexcept
353 if (NativeNumericSupportIsBroken(cb.ServerType()))
355 result->sqlValue = { .precision = Precision, .scale = Scale, .sign = 0, .val = {} };
356 return SQLBindCol(stmt, column, SQL_C_DOUBLE, &result->nativeValue,
sizeof(result->nativeValue), indicator);
360 RequireSuccess(stmt, SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hDesc, 0,
nullptr));
361 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_PRECISION, (SQLPOINTER) Precision, 0));
362 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_SCALE, (SQLPOINTER) Scale, 0));
364 return SQLBindCol(stmt, column, SQL_C_NUMERIC, &result->sqlValue,
sizeof(ValueType), indicator);
367 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback
const& cb)
noexcept
369 if (NativeNumericSupportIsBroken(cb.ServerType()))
371 result->sqlValue = { .precision = Precision, .scale = Scale, .sign = 0, .val = {} };
372 return SQLGetData(stmt, column, SQL_C_DOUBLE, &result->nativeValue,
sizeof(result->nativeValue), indicator);
376 RequireSuccess(stmt, SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hDesc, 0,
nullptr));
377 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_PRECISION, (SQLPOINTER) Precision, 0));
378 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_SCALE, (SQLPOINTER) Scale, 0));
380 return SQLGetData(stmt, column, SQL_C_NUMERIC, &result->sqlValue,
sizeof(ValueType), indicator);
383 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType
const& value)
noexcept
385 return value.ToString();
392template <std::
size_t ThePrecision, std::
size_t TheScale>
393inline constexpr bool SqlIsNativeRowBindableValue<SqlNumeric<ThePrecision, TheScale>> =
true;
394template <std::
size_t ThePrecision, std::
size_t TheScale>
395inline constexpr bool SqlIsNumericValue<SqlNumeric<ThePrecision, TheScale>> =
true;
400template <Lightweight::SqlNumericType Type>
401struct std::formatter<Type>: std::formatter<std::string>
403 template <
typename FormatContext>
404 auto format(Type
const& value, FormatContext& ctx)
const
406 return formatter<std::string>::format(value.ToString(), ctx);
static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
Constructs an ODBC error info object from the given ODBC statement handle.
LIGHTWEIGHT_FORCE_INLINE long double ToLongDouble() const noexcept
Converts the numeric to a long double precision floating point value.
LIGHTWEIGHT_FORCE_INLINE constexpr void assign(std::floating_point auto inputValue) noexcept
Assigns a value to the numeric.
double nativeValue
Cached native value for drivers without SQL_NUMERIC_STRUCT support.
LIGHTWEIGHT_FORCE_INLINE bool operator==(SqlNumeric< OtherPrecision, OtherScale > const &other) const noexcept
Equality comparison operator.
LIGHTWEIGHT_FORCE_INLINE double ToDouble() const noexcept
Converts the numeric to a double precision floating point value.
LIGHTWEIGHT_FORCE_INLINE std::partial_ordering operator<=>(SqlNumeric const &other) const noexcept
constexpr SqlNumeric(SQL_NUMERIC_STRUCT const &value) noexcept
Constructs a numeric from a SQL_NUMERIC_STRUCT.
LIGHTWEIGHT_FORCE_INLINE std::string ToString() const
static constexpr auto Precision
Number of total digits.
constexpr SqlNumeric() noexcept=default
Default constructor.
SQL_NUMERIC_STRUCT sqlValue
The SQL numeric struct for ODBC binding.
static constexpr auto Scale
Number of digits after the decimal point.
LIGHTWEIGHT_FORCE_INLINE float ToFloat() const noexcept
Converts the numeric to a floating point value.
LIGHTWEIGHT_FORCE_INLINE Int128 ToUnscaledValue() const noexcept
static constexpr auto ColumnType
The SQL column type definition for this numeric type.
LIGHTWEIGHT_FORCE_INLINE constexpr SqlNumeric & operator=(std::floating_point auto value) noexcept
Assigns a floating point value to the numeric.