Lightweight 0.20260213.0
Loading...
Searching...
No Matches
SqlNumeric.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlColumnTypeDefinitions.hpp"
6#include "../SqlError.hpp"
7#include "Primitives.hpp"
8
9#include <algorithm>
10#include <bit>
11#include <cmath>
12#include <compare>
13#include <concepts>
14#include <cstring>
15#include <source_location>
16
17namespace Lightweight
18{
19
20// clang-cl doesn't support __int128_t but defines __SIZEOF_INT128__
21// and also since it pretends to be MSVC, it also defines _MSC_VER
22// clang-format off
23#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
24 #define LIGHTWEIGHT_INT128_T __int128_t
25 static_assert(sizeof(__int128_t) == sizeof(SQL_NUMERIC_STRUCT::val));
26#endif
27// clang-format on
28
29/// Represents a fixed-point number with a given precision and scale.
30///
31/// Precision is *exactly* the total number of digits in the number,
32/// including the digits after the decimal point.
33///
34/// Scale is the number of digits after the decimal point.
35///
36/// @ingroup DataTypes
37template <std::size_t ThePrecision, std::size_t TheScale>
39{
40 /// Number of total digits
41 static constexpr auto Precision = ThePrecision;
42
43 /// Number of digits after the decimal point
44 static constexpr auto Scale = TheScale;
45
46 /// The SQL column type definition for this numeric type.
47 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .precision = Precision, .scale = TheScale };
48
49 static_assert(Precision <= SQL_MAX_NUMERIC_LEN);
50 static_assert(Scale < Precision);
51
52 /// The SQL numeric struct for ODBC binding.
53 SQL_NUMERIC_STRUCT sqlValue {};
54
55 /// Cached native value for drivers without SQL_NUMERIC_STRUCT support.
56 double nativeValue {};
57
58 /// Default constructor.
59 constexpr SqlNumeric() noexcept = default;
60 /// Move constructor.
61 constexpr SqlNumeric(SqlNumeric&&) noexcept = default;
62 /// Move assignment operator.
63 constexpr SqlNumeric& operator=(SqlNumeric&&) noexcept = default;
64 /// Copy constructor.
65 constexpr SqlNumeric(SqlNumeric const&) noexcept = default;
66 /// Copy assignment operator.
67 constexpr SqlNumeric& operator=(SqlNumeric const&) noexcept = default;
68 constexpr ~SqlNumeric() noexcept = default;
69
70 /// Constructs a numeric from a floating point value.
71 constexpr SqlNumeric(std::floating_point auto value) noexcept
72 {
73 assign(value);
74 }
75
76 /// Constructs a numeric from a SQL_NUMERIC_STRUCT.
77 constexpr explicit SqlNumeric(SQL_NUMERIC_STRUCT const& value) noexcept:
78 sqlValue(value)
79 {
80 }
81
82 // For encoding/decoding purposes, we assume little-endian.
83 static_assert(std::endian::native == std::endian::little);
84
85 /// Assigns a value to the numeric.
86 LIGHTWEIGHT_FORCE_INLINE constexpr void assign(std::floating_point auto inputValue) noexcept
87 {
88 nativeValue = static_cast<decltype(nativeValue)>(inputValue);
89
90 sqlValue = {};
91 sqlValue.sign = std::signbit(inputValue) ? 0 : 1;
92 sqlValue.precision = static_cast<SQLCHAR>(Precision);
93 sqlValue.scale = static_cast<SQLSCHAR>(Scale);
94
95 auto const unscaledValue = std::roundl(static_cast<long double>(std::abs(inputValue) * std::powl(10.0L, Scale)));
96#if defined(LIGHTWEIGHT_INT128_T)
97 auto const num = static_cast<LIGHTWEIGHT_INT128_T>(unscaledValue);
98 std::memcpy(sqlValue.val, &num, sizeof(num));
99#else
100 auto const num = static_cast<int64_t>(unscaledValue);
101 std::memcpy(sqlValue.val, &num, sizeof(num));
102#endif
103 }
104
105 /// Assigns a floating point value to the numeric.
106 LIGHTWEIGHT_FORCE_INLINE constexpr SqlNumeric& operator=(std::floating_point auto value) noexcept
107 {
108 assign(value);
109 return *this;
110 }
111
112 /// Converts the numeric to an unscaled integer value.
113 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE auto ToUnscaledValue() const noexcept
114 {
115 if (nativeValue != 0.0)
116 {
117 auto const unscaledValue = std::roundl(nativeValue * std::powl(10.0L, Scale));
118#if defined(LIGHTWEIGHT_INT128_T)
119 return static_cast<LIGHTWEIGHT_INT128_T>(unscaledValue);
120#else
121 return static_cast<int64_t>(unscaledValue);
122#endif
123 }
124
125 auto const sign = sqlValue.sign ? 1 : -1;
126#if defined(LIGHTWEIGHT_INT128_T)
127 return sign * *reinterpret_cast<LIGHTWEIGHT_INT128_T const*>(sqlValue.val);
128#else
129 return sign * *reinterpret_cast<int64_t const*>(sqlValue.val);
130#endif
131 }
132
133 /// Converts the numeric to a floating point value.
134 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE float ToFloat() const noexcept
135 {
136 return static_cast<float>(ToUnscaledValue()) / std::powf(10, sqlValue.scale);
137 }
138
139 /// Converts the numeric to a double precision floating point value.
140 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE double ToDouble() const noexcept
141 {
142 return static_cast<double>(ToUnscaledValue()) / std::pow(10, sqlValue.scale);
143 }
144
145 /// Converts the numeric to a long double precision floating point value.
146 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE long double ToLongDouble() const noexcept
147 {
148 return static_cast<long double>(ToUnscaledValue()) / std::pow(10, sqlValue.scale);
149 }
150
151 /// Converts the numeric to a floating point value.
152 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE explicit operator float() const noexcept
153 {
154 return ToFloat();
155 }
156
157 /// Converts the numeric to a double precision floating point value.
158 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE explicit operator double() const noexcept
159 {
160 return ToDouble();
161 }
162
163 /// Converts the numeric to a long double precision floating point value.
164 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE explicit operator long double() const noexcept
165 {
166 return ToLongDouble();
167 }
168
169 /// Converts the numeric to a string.
170 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::string ToString() const
171 {
172 return std::format("{:.{}f}", ToLongDouble(), Scale);
173 }
174
175 /// Three-way comparison operator.
176 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::weak_ordering operator<=>(SqlNumeric const& other) const noexcept
177 {
178 return ToDouble() <=> other.ToDouble();
179 }
180
181 /// Equality comparison operator.
182 template <std::size_t OtherPrecision, std::size_t OtherScale>
183 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE bool operator==(
184 SqlNumeric<OtherPrecision, OtherScale> const& other) const noexcept
185 {
186 return ToFloat() == other.ToFloat();
187 }
188};
189
190template <typename T>
191concept SqlNumericType = requires(T t) {
192 { T::Precision } -> std::convertible_to<std::size_t>;
193 { T::Scale } -> std::convertible_to<std::size_t>;
194} && std::same_as<T, SqlNumeric<T::Precision, T::Scale>>;
195
196// clang-format off
197template <std::size_t Precision, std::size_t Scale>
198struct SqlDataBinder<SqlNumeric<Precision, Scale>>
199{
200 using ValueType = SqlNumeric<Precision, Scale>;
201
202 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .precision = Precision, .scale = Scale };
203
204 static void RequireSuccess(SQLHSTMT stmt, SQLRETURN error, std::source_location sourceLocation = std::source_location::current())
205 {
206 if (SQL_SUCCEEDED(error))
207 return;
208
209 throw SqlException(SqlErrorInfo::FromStatementHandle(stmt), sourceLocation);
210 }
211
212 static constexpr bool NativeNumericSupportIsBroken(SqlServerType serverType) noexcept
213 {
214 // SQLite's ODBC driver does not support SQL_NUMERIC_STRUCT (it's all just floating point numbers).
215 // Microsoft SQL Server's ODBC driver also has issues (keeps reporting out of bound, on Linux at least).
216 return serverType == SqlServerType::SQLITE || serverType == SqlServerType::MICROSOFT_SQL;
217 }
218
219 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
220 SQLUSMALLINT column,
221 ValueType const& value,
222 SqlDataBinderCallback& cb) noexcept
223 {
224 if (NativeNumericSupportIsBroken(cb.ServerType()))
225 {
226 return SQLBindParameter(stmt,
227 column,
228 SQL_PARAM_INPUT,
229 SQL_C_DOUBLE,
230 SQL_DOUBLE,
231 0,
232 0,
233 (SQLPOINTER) &value.nativeValue,
234 sizeof(value.nativeValue),
235 nullptr);
236 }
237
238 return SQLBindParameter(stmt,
239 column,
240 SQL_PARAM_INPUT,
241 SQL_C_NUMERIC,
242 SQL_NUMERIC,
243 value.sqlValue.precision,
244 value.sqlValue.scale,
245 (SQLPOINTER) &value,
246 sizeof(value),
247 nullptr);
248 }
249
250
251 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
252 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
253 {
254 if (NativeNumericSupportIsBroken(cb.ServerType()))
255 {
256 result->sqlValue = { .precision = Precision, .scale = Scale, .sign = 0, .val = {} };
257 return SQLBindCol(stmt, column, SQL_C_DOUBLE, &result->nativeValue, sizeof(result->nativeValue), indicator);
258 }
259
260 SQLHDESC hDesc {};
261 RequireSuccess(stmt, SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hDesc, 0, nullptr));
262 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_PRECISION, (SQLPOINTER) Precision, 0)); // NOLINT(performance-no-int-to-ptr)
263 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_SCALE, (SQLPOINTER) Scale, 0)); // NOLINT(performance-no-int-to-ptr)
264
265 return SQLBindCol(stmt, column, SQL_C_NUMERIC, &result->sqlValue, sizeof(ValueType), indicator);
266 }
267
268 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
269 {
270 if (NativeNumericSupportIsBroken(cb.ServerType()))
271 {
272 result->sqlValue = { .precision = Precision, .scale = Scale, .sign = 0, .val = {} };
273 return SQLGetData(stmt, column, SQL_C_DOUBLE, &result->nativeValue, sizeof(result->nativeValue), indicator);
274 }
275
276 SQLHDESC hDesc {};
277 RequireSuccess(stmt, SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hDesc, 0, nullptr));
278 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_PRECISION, (SQLPOINTER) Precision, 0)); // NOLINT(performance-no-int-to-ptr)
279 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_SCALE, (SQLPOINTER) Scale, 0)); // NOLINT(performance-no-int-to-ptr)
280
281 return SQLGetData(stmt, column, SQL_C_NUMERIC, &result->sqlValue, sizeof(ValueType), indicator);
282 }
283
284 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
285 {
286 return value.ToString();
287 }
288};
289// clang-format off
290
291} // namespace Lightweight
292
293template <Lightweight::SqlNumericType Type>
294struct std::formatter<Type>: std::formatter<std::string>
295{
296 template <typename FormatContext>
297 auto format(Type const& value, FormatContext& ctx)
298 {
299 return formatter<std::string>::format(value.ToString(), ctx);
300 }
301};
static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
Constructs an ODBC error info object from the given ODBC statement handle.
Definition SqlError.hpp:48
constexpr LIGHTWEIGHT_FORCE_INLINE float ToFloat() const noexcept
Converts the numeric to a 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.
constexpr LIGHTWEIGHT_FORCE_INLINE bool operator==(SqlNumeric< OtherPrecision, OtherScale > const &other) const noexcept
Equality comparison operator.
constexpr LIGHTWEIGHT_FORCE_INLINE double ToDouble() const noexcept
Converts the numeric to a double precision floating point value.
constexpr SqlNumeric(SQL_NUMERIC_STRUCT const &value) noexcept
Constructs a numeric from a SQL_NUMERIC_STRUCT.
LIGHTWEIGHT_FORCE_INLINE std::string ToString() const
Converts the numeric to a string.
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.
constexpr LIGHTWEIGHT_FORCE_INLINE auto ToUnscaledValue() const noexcept
Converts the numeric to an unscaled integer value.
constexpr LIGHTWEIGHT_FORCE_INLINE std::weak_ordering operator<=>(SqlNumeric const &other) const noexcept
Three-way comparison operator.
static constexpr auto Scale
Number of digits after the decimal point.
constexpr LIGHTWEIGHT_FORCE_INLINE long double ToLongDouble() const noexcept
Converts the numeric to a long double precision floating point value.
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.