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