Lightweight 0.20261105.1
Loading...
Searching...
No Matches
Core.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5// See SqlOdbcPrelude.hpp's header comment for why this replaces a direct <Windows.h> include.
6#include "../Api.hpp"
7#include "../SqlOdbcPrelude.hpp"
8#include "../SqlServerType.hpp"
9
10#include <algorithm>
11#include <concepts>
12#include <cstddef>
13#include <cstdint>
14#include <functional>
15#include <memory>
16#include <optional>
17
18#include <sql.h>
19#include <sqlext.h>
20#include <sqltypes.h>
21
22namespace Lightweight
23{
24
25/// @defgroup DataTypes Data Types
26///
27/// @brief Special purpose data types for SQL data binding.
28
29/// Callback interface for SqlDataBinder to allow post-processing of output columns.
30///
31/// This is needed because the SQLBindCol() function does not allow to specify a callback function to be called
32/// after the data has been fetched from the database. This is needed to trim strings to the correct size, for
33/// example.
34class LIGHTWEIGHT_API SqlDataBinderCallback
35{
36 public:
37 /// Default constructor.
39 /// Default move constructor.
41 /// Default copy constructor.
43 /// Default move assignment operator.
45 /// Default copy assignment operator.
47
48 virtual ~SqlDataBinderCallback() = default;
49
50 /// Plans a callback to be called after the statement has been executed.
51 ///
52 /// @see SqlDataBinder::PostExecute()
53 virtual void PlanPostExecuteCallback(std::function<void()>&&) = 0;
54
55 /// Plans a callback to be called after a column has been processed.
56 ///
57 /// @see SqlDataBinder::PostProcessOutputColumn()
58 virtual void PlanPostProcessOutputColumn(std::function<void()>&&) = 0;
59
60 /// Provides a pointer to a single indicator for a single input parameter.
61 ///
62 /// @note The caller is responsible for filling the indicator with the length of the data or
63 /// SQL_NULL_DATA.
64 /// @note The indicator must remain valid until the statement is executed.
65 ///
66 /// @return A pointer to the indicator.
67 virtual SQLLEN* ProvideInputIndicator() = 0;
68
69 /// Provides a pointer to a contiguous array of indicators for a batch of input parameters.
70 ///
71 /// @note The caller is responsible for filling the indicators with the lengths of the data or
72 /// SQL_NULL_DATA.
73 /// @note The indicators must remain valid until the statement is executed.
74 ///
75 /// @param rowCount The number of rows in the batch.
76 /// @return A pointer to the first element of the indicator array.
77 virtual SQLLEN* ProvideInputIndicators(size_t rowCount) = 0;
78
79 /// Provides a pointer to a contiguous, suitably aligned temporary byte buffer that remains valid
80 /// until the statement is executed.
81 ///
82 /// This is used by batch input parameter binders that need scratch storage whose lifetime must
83 /// outlive the bind call but not the execution — for example a row-strided NULL/length indicator
84 /// array used by native row-wise array binding of @c std::optional columns.
85 ///
86 /// @note The buffer contents are unspecified; the caller is responsible for initializing it.
87 /// @note The returned storage is aligned to at least @c alignof(std::max_align_t).
88 /// @note Row-wise callers request @c ~rowStride*rowCount bytes to hold only @c rowCount @c SQLLEN
89 /// indicators. This over-allocation is intrinsic to ODBC row-wise binding, which strides the
90 /// @c StrLen_or_IndPtr array by @c SQL_ATTR_PARAM_BIND_TYPE (the row stride) — there is no separate
91 /// indicator stride — so a tightly packed indicator array would require descriptor-level binding.
92 ///
93 /// @param byteCount The number of bytes to provide.
94 /// @return A pointer to the first byte of the buffer.
95 virtual std::byte* ProvideBatchStagingBuffer(std::size_t byteCount) = 0;
96
97 /// @return The server type of the database.
98 [[nodiscard]] virtual SqlServerType ServerType() const noexcept = 0;
99
100 /// @return The driver name of the database.
101 [[nodiscard]] virtual std::string const& DriverName() const noexcept = 0;
102
103 /// Resolves the SQL type the server declares for an input parameter of the prepared statement.
104 ///
105 /// A binder that has no value to derive the type from - a NULL - uses this to bind with a type
106 /// the target column accepts. Unlike a bare @c SQLDescribeParam() on the statement handle, it
107 /// answers regardless of how many parameters are already bound.
108 ///
109 /// @param column The 1-based parameter index.
110 /// @return The declared SQL type, or @c std::nullopt when it cannot be determined.
111 [[nodiscard]] virtual std::optional<SQLSMALLINT> DescribeInputParameterType(SQLUSMALLINT column) noexcept
112 {
113 (void) column;
114 return std::nullopt;
115 }
116};
117
118template <typename>
119struct SqlDataBinder;
120
121// Default traits for output string parameters
122// This needs to be implemented for each string type that should be used as output parameter via
123// SqlDataBinder<>. An std::string specialization is provided below. Feel free to add more specializations for
124// other string types, such as CString, etc.
125template <typename>
126struct SqlBasicStringOperations;
127
128// -----------------------------------------------------------------------------------------------
129
130namespace detail
131{
132
133 /// @brief Column size to declare for a variable-length parameter of @p length characters or bytes.
134 ///
135 /// ODBC defines a column size of 0 as invalid; strict drivers reject it with HY104 when an empty
136 /// string or binary is bound, so the declared size never drops below 1. The bound data length is unaffected.
137 [[nodiscard]] constexpr SQLULEN NonZeroColumnSize(std::size_t length) noexcept
138 {
139 return std::max<SQLULEN>(static_cast<SQLULEN>(length), 1);
140 }
141
142 /// @brief Satisfied when @p T is the same type as at least one of @p Us.
143 ///
144 /// Mirrors @c Lightweight::detail::OneOf, but lives in the DataBinder layer so the low-level binder
145 /// headers can use it without reaching up to the higher-level Utils.hpp.
146 template <typename T, typename... Us>
147 concept IsAnyOf = (std::same_as<T, Us> || ...);
148
149 /// @brief Byte offset of the contained value within a @c std::optional<T>.
150 ///
151 /// Used by the row-wise batch binders to address the inner value of each row's optional in place. The
152 /// offset is 0 on all known standard libraries, but is computed rather than assumed so the address
153 /// arithmetic does not bake in that assumption. It is derived from the integer addresses of a probe
154 /// optional and its contained value (rather than @c byte* subtraction) to keep the computation defined.
155 ///
156 /// @tparam T The contained value type.
157 /// @return The offset, in bytes, of the contained value within the optional.
158 template <typename T>
159 [[nodiscard]] inline std::size_t OptionalValueOffset() noexcept
160 {
161 std::optional<T> const probe { T {} };
162 return static_cast<std::size_t>(reinterpret_cast<std::uintptr_t>(std::addressof(*probe))
163 - reinterpret_cast<std::uintptr_t>(std::addressof(probe)));
164 }
165
166 // clang-format off
167template <typename T>
168concept HasGetStringAndGetLength = requires(T const& t) {
169 { t.GetLength() } -> std::same_as<int>;
170 { t.GetString() } -> std::same_as<char const*>;
171};
172
173template <typename T>
174concept HasGetStringAndLength = requires(T const& t)
175{
176 { t.Length() } -> std::same_as<int>;
177 { t.GetString() } -> std::same_as<char const*>;
178};
179 // clang-format on
180
181 template <typename>
182 struct SqlViewHelper;
183
184 template <typename T>
185 concept HasSqlViewHelper = requires(T const& t) {
186 { SqlViewHelper<T>::View(t) } -> std::convertible_to<std::string_view>;
187 };
188
189 template <typename CharT>
190 struct SqlViewHelper<std::basic_string<CharT>>
191 {
192 static LIGHTWEIGHT_FORCE_INLINE std::basic_string_view<CharT> View(std::basic_string<CharT> const& str) noexcept
193 {
194 return { str.data(), str.size() };
195 }
196 };
197
198 template <detail::HasGetStringAndGetLength CStringLike>
199 struct SqlViewHelper<CStringLike>
200 {
201 static LIGHTWEIGHT_FORCE_INLINE std::string_view View(CStringLike const& str) noexcept
202 {
203 return { str.GetString(), static_cast<size_t>(str.GetLength()) };
204 }
205 };
206
207 template <detail::HasGetStringAndLength StringLike>
208 struct SqlViewHelper<StringLike>
209 {
210 static LIGHTWEIGHT_FORCE_INLINE std::string_view View(StringLike const& str) noexcept
211 {
212 return { str.GetString(), static_cast<size_t>(str.Length()) };
213 }
214 };
215
216} // namespace detail
217
218// -----------------------------------------------------------------------------------------------
219
220template <typename T>
221concept SqlInputParameterBinder = requires(SQLHSTMT hStmt, SQLUSMALLINT column, T const& value, SqlDataBinderCallback& cb) {
222 { SqlDataBinder<T>::InputParameter(hStmt, column, value, cb) } -> std::same_as<SQLRETURN>;
223};
224
225template <typename T>
226concept SqlOutputColumnBinder =
227 requires(SQLHSTMT hStmt, SQLUSMALLINT column, T* result, SQLLEN* indicator, SqlDataBinderCallback& cb) {
228 { SqlDataBinder<T>::OutputColumn(hStmt, column, result, indicator, cb) } -> std::same_as<SQLRETURN>;
229 };
230
231template <typename T>
232concept SqlInputParameterBatchBinder =
233 requires(SQLHSTMT hStmt, SQLUSMALLINT column, std::ranges::range_value_t<T>* result, SqlDataBinderCallback& cb) {
234 {
235 SqlDataBinder<std::ranges::range_value_t<T>>::InputParameter(
236 hStmt, column, std::declval<std::ranges::range_value_t<T>>(), cb)
237 } -> std::same_as<SQLRETURN>;
238 };
239
240/// @brief Opt-in trait marking a value type as bindable in a native ODBC row-wise parameter array.
241///
242/// A type qualifies when it is fixed-width, stored inline, bound via a plain @c SQLBindParameter with
243/// no per-call heap conversion, and identically across all supported backends — so its address can be
244/// handed to ODBC and strided by @c SQL_ATTR_PARAM_BIND_TYPE. The primary template is @c false; each
245/// eligible binder header specializes it to @c true (primitives, date/time/datetime, numeric).
246///
247/// @note @c SqlGuid is intentionally NOT marked: on SQLite it is bound via a per-value text conversion,
248/// which cannot be expressed as a zero-copy row-wise array — GUID columns use the soft batch path.
249template <typename T>
250inline constexpr bool SqlIsNativeRowBindableValue = false;
251
252/// @brief Opt-in trait marking a value type as an @c SqlNumeric specialization.
253///
254/// Numeric values are row-wise bindable, but @c std::optional<SqlNumeric> is not (the contained value
255/// is not bound at a uniform offset/representation across backends), so the optional batch path
256/// excludes them via this trait.
257template <typename T>
258inline constexpr bool SqlIsNumericValue = false;
259
260/// @brief Whether @p T is a @c std::optional specialization.
261///
262/// Defined locally rather than reusing @c Lightweight::IsSpecializationOf (Utils.hpp) or the DataMapper
263/// @c IsStdOptional (Field.hpp): this low-level binder header is deliberately kept free of dependencies on
264/// those higher-level headers, so it carries its own minimal optional traits.
265template <typename T>
266inline constexpr bool SqlIsStdOptional = false;
267
268template <typename T>
269inline constexpr bool SqlIsStdOptional<std::optional<T>> = true;
270
271template <typename T>
272concept SqlGetColumnNativeType =
273 requires(SQLHSTMT hStmt, SQLUSMALLINT column, T* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) {
274 { SqlDataBinder<T>::GetColumn(hStmt, column, result, indicator, cb) } -> std::same_as<SQLRETURN>;
275 };
276
277template <typename T>
278concept SqlDataBinderSupportsInspect = requires(T const& value) {
279 { SqlDataBinder<std::remove_cvref_t<T>>::Inspect(value) } -> std::convertible_to<std::string>;
280};
281
282// clang-format off
283template <typename StringType, typename CharType>
284concept SqlBasicStringBinderConcept = requires(StringType* str) {
285 { SqlBasicStringOperations<StringType>::Data(str) } -> std::same_as<CharType*>;
286 { SqlBasicStringOperations<StringType>::Size(str) } -> std::same_as<SQLULEN>;
287 { SqlBasicStringOperations<StringType>::Reserve(str, size_t {}) } -> std::same_as<void>;
288 { SqlBasicStringOperations<StringType>::Resize(str, SQLLEN {}) } -> std::same_as<void>;
289 { SqlBasicStringOperations<StringType>::Clear(str) } -> std::same_as<void>;
290};
291// clang-format on
292
293} // namespace Lightweight
virtual void PlanPostExecuteCallback(std::function< void()> &&)=0
virtual SQLLEN * ProvideInputIndicator()=0
SqlDataBinderCallback(SqlDataBinderCallback &&)=default
Default move constructor.
virtual SqlServerType ServerType() const noexcept=0
virtual void PlanPostProcessOutputColumn(std::function< void()> &&)=0
SqlDataBinderCallback & operator=(SqlDataBinderCallback const &)=default
Default copy assignment operator.
SqlDataBinderCallback & operator=(SqlDataBinderCallback &&)=default
Default move assignment operator.
SqlDataBinderCallback()=default
Default constructor.
virtual SQLLEN * ProvideInputIndicators(size_t rowCount)=0
SqlDataBinderCallback(SqlDataBinderCallback const &)=default
Default copy constructor.
virtual std::byte * ProvideBatchStagingBuffer(std::size_t byteCount)=0