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