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