Lightweight 0.20250904.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 <bit>
10#include <cmath>
11#include <compare>
12#include <concepts>
13#include <cstring>
14#include <source_location>
15
16namespace Lightweight
17{
18
19// clang-cl doesn't support __int128_t but defines __SIZEOF_INT128__
20// and also since it pretends to be MSVC, it also defines _MSC_VER
21// clang-format off
22#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
23 #define LIGHTWEIGHT_INT128_T __int128_t
24 static_assert(sizeof(__int128_t) == sizeof(SQL_NUMERIC_STRUCT::val));
25#endif
26// clang-format on
27
28/// Represents a fixed-point number with a given precision and scale.
29///
30/// Precision is *exactly* the total number of digits in the number,
31/// including the digits after the decimal point.
32///
33/// Scale is the number of digits after the decimal point.
34///
35/// @ingroup DataTypes
36template <std::size_t ThePrecision, std::size_t TheScale>
38{
39 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .precision = ThePrecision, .scale = TheScale };
40
41 /// Number of total digits
42 static constexpr auto Precision = ThePrecision;
43
44 /// Number of digits after the decimal point
45 static constexpr auto Scale = TheScale;
46
47 static_assert(TheScale < SQL_MAX_NUMERIC_LEN);
48 static_assert(Scale <= Precision);
49
50 /// The value is stored as a string to avoid floating point precision issues.
51 SQL_NUMERIC_STRUCT sqlValue {};
52
53 constexpr SqlNumeric() noexcept = default;
54 constexpr SqlNumeric(SqlNumeric&&) noexcept = default;
55 constexpr SqlNumeric& operator=(SqlNumeric&&) noexcept = default;
56 constexpr SqlNumeric(SqlNumeric const&) noexcept = default;
57 constexpr SqlNumeric& operator=(SqlNumeric const&) noexcept = default;
58 constexpr ~SqlNumeric() noexcept = default;
59
60 /// Constructs a numeric from a floating point value.
61 constexpr SqlNumeric(std::floating_point auto value) noexcept
62 {
63 assign(value);
64 }
65
66 /// Constructs a numeric from a SQL_NUMERIC_STRUCT.
67 constexpr explicit SqlNumeric(SQL_NUMERIC_STRUCT const& value) noexcept:
68 sqlValue(value)
69 {
70 }
71
72 // For encoding/decoding purposes, we assume little-endian.
73 static_assert(std::endian::native == std::endian::little);
74
75 /// Assigns a value to the numeric.
76 LIGHTWEIGHT_FORCE_INLINE constexpr void assign(std::floating_point auto value) noexcept
77 {
78#if defined(LIGHTWEIGHT_INT128_T)
79 auto const num = static_cast<LIGHTWEIGHT_INT128_T>(value * std::pow(10, Scale));
80 *((LIGHTWEIGHT_INT128_T*) sqlValue.val) = num;
81#else
82 auto const num = static_cast<int64_t>(value * std::pow(10, Scale));
83 std::memset(sqlValue.val, 0, sizeof(sqlValue.val));
84 *((int64_t*) sqlValue.val) = num;
85#endif
86
87 sqlValue.sign = num >= 0; // 1 == positive, 0 == negative
88 sqlValue.precision = Precision;
89 sqlValue.scale = Scale;
90 }
91
92 /// Assigns a floating point value to the numeric.
93 LIGHTWEIGHT_FORCE_INLINE constexpr SqlNumeric& operator=(std::floating_point auto value) noexcept
94 {
95 assign(value);
96 return *this;
97 }
98
99 /// Converts the numeric to an unscaled integer value.
100 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE auto ToUnscaledValue() const noexcept
101 {
102 auto const sign = sqlValue.sign ? 1 : -1;
103#if defined(LIGHTWEIGHT_INT128_T)
104 return sign * *reinterpret_cast<LIGHTWEIGHT_INT128_T const*>(sqlValue.val);
105#else
106 return sign * *reinterpret_cast<int64_t const*>(sqlValue.val);
107#endif
108 }
109
110 /// Converts the numeric to a floating point value.
111 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE float ToFloat() const noexcept
112 {
113 return float(ToUnscaledValue()) / std::powf(10, Scale);
114 }
115
116 /// Converts the numeric to a double precision floating point value.
117 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE double ToDouble() const noexcept
118 {
119 return double(ToUnscaledValue()) / std::pow(10, Scale);
120 }
121
122 /// Converts the numeric to a long double precision floating point value.
123 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE long double ToLongDouble() const noexcept
124 {
125 return static_cast<long double>(ToUnscaledValue()) / std::pow(10, Scale);
126 }
127
128 /// Converts the numeric to a floating point value.
129 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE explicit operator float() const noexcept
130 {
131 return ToFloat();
132 }
133
134 /// Converts the numeric to a double precision floating point value.
135 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE explicit operator double() const noexcept
136 {
137 return ToDouble();
138 }
139
140 /// Converts the numeric to a long double precision floating point value.
141 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE explicit operator long double() const noexcept
142 {
143 return ToLongDouble();
144 }
145
146 /// Converts the numeric to a string.
147 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::string ToString() const
148 {
149 return std::format("{:.{}f}", ToFloat(), Scale);
150 }
151
152 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::weak_ordering operator<=>(SqlNumeric const& other) const noexcept
153 {
154 return ToDouble() <=> other.ToDouble();
155 }
156
157 template <std::size_t OtherPrecision, std::size_t OtherScale>
158 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE bool operator==(
159 SqlNumeric<OtherPrecision, OtherScale> const& other) const noexcept
160 {
161 return ToFloat() == other.ToFloat();
162 }
163};
164
165template <typename T>
166concept SqlNumericType = requires(T t) {
167 { T::Precision } -> std::convertible_to<std::size_t>;
168 { T::Scale } -> std::convertible_to<std::size_t>;
169} && std::same_as<T, SqlNumeric<T::Precision, T::Scale>>;
170
171// clang-format off
172template <std::size_t Precision, std::size_t Scale>
173struct SqlDataBinder<SqlNumeric<Precision, Scale>>
174{
175 using ValueType = SqlNumeric<Precision, Scale>;
176
177 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Decimal { .precision = Precision, .scale = Scale };
178
179 static void RequireSuccess(SQLHSTMT stmt, SQLRETURN error, std::source_location sourceLocation = std::source_location::current())
180 {
181 if (SQL_SUCCEEDED(error))
182 return;
183
184 throw SqlException(SqlErrorInfo::FromStatementHandle(stmt), sourceLocation);
185 }
186
187 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt, SQLUSMALLINT column, ValueType const& value, SqlDataBinderCallback& /*cb*/) noexcept
188 {
189 auto* mut = const_cast<ValueType*>(&value);
190 mut->sqlValue.precision = Precision;
191 mut->sqlValue.scale = Scale;
192 RequireSuccess(stmt, SQLBindParameter(stmt, column, SQL_PARAM_INPUT, SQL_C_NUMERIC, SQL_NUMERIC, Precision, Scale, (SQLPOINTER) &value.sqlValue, 0, nullptr));
193 return SQL_SUCCESS;
194 }
195
196 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& /*unused*/) noexcept
197 {
198 SQLHDESC hDesc {};
199 RequireSuccess(stmt, SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hDesc, 0, nullptr));
200 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_PRECISION, (SQLPOINTER) Precision, 0)); // NOLINT(performance-no-int-to-ptr)
201 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_SCALE, (SQLPOINTER) Scale, 0)); // NOLINT(performance-no-int-to-ptr)
202
203 return SQLBindCol(stmt, column, SQL_C_NUMERIC, &result->sqlValue, sizeof(ValueType), indicator);
204 }
205
206 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& /*cb*/) noexcept
207 {
208 SQLHDESC hDesc {};
209 RequireSuccess(stmt, SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hDesc, 0, nullptr));
210 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_PRECISION, (SQLPOINTER) Precision, 0)); // NOLINT(performance-no-int-to-ptr)
211 RequireSuccess(stmt, SQLSetDescField(hDesc, (SQLSMALLINT) column, SQL_DESC_SCALE, (SQLPOINTER) Scale, 0)); // NOLINT(performance-no-int-to-ptr)
212
213 return SQLGetData(stmt, column, SQL_C_NUMERIC, &result->sqlValue, sizeof(ValueType), indicator);
214 }
215
216 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
217 {
218 return value.ToString();
219 }
220};
221// clang-format off
222
223} // namespace Lightweight
224
225template <Lightweight::SqlNumericType Type>
226struct std::formatter<Type>: std::formatter<std::string>
227{
228 template <typename FormatContext>
229 auto format(Type const& value, FormatContext& ctx)
230 {
231 return formatter<std::string>::format(value.ToString(), ctx);
232 }
233};
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.
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.
LIGHTWEIGHT_FORCE_INLINE constexpr void assign(std::floating_point auto value) noexcept
Assigns a value to the numeric.
SQL_NUMERIC_STRUCT sqlValue
The value is stored as a string to avoid floating point precision issues.
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.