Lightweight 0.20261105.1
Loading...
Searching...
No Matches
Primitives.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlColumnTypeDefinitions.hpp"
6#include "Core.hpp"
7
8#include <algorithm>
9#include <array>
10#include <memory>
11#include <span>
12#include <string>
13
14namespace Lightweight
15{
16
17template <typename T, SQLSMALLINT TheCType, SQLINTEGER TheSqlType, auto TheColumnType>
18struct SqlSimpleDataBinder
19{
20 static constexpr SqlColumnTypeDefinition ColumnType = TheColumnType;
21
22 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
23 SQLUSMALLINT column,
24 T const& value,
25 SqlDataBinderCallback& /*cb*/) noexcept
26 {
27 return SQLBindParameter(stmt, column, SQL_PARAM_INPUT, TheCType, TheSqlType, 0, 0, (SQLPOINTER) &value, 0, nullptr);
28 }
29
30 /// Binds a contiguous (column-wise) or row-strided (row-wise) array of values as an input parameter.
31 ///
32 /// @param indicators Optional per-row NULL/length indicator array. Passed straight to ODBC as the
33 /// StrLen_or_IndPtr; defaults to nullptr (no NULLs). For row-wise binding the driver strides it by
34 /// the statement's SQL_ATTR_PARAM_BIND_TYPE, matching the value stride.
35 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN BatchInputParameter(SQLHSTMT stmt,
36 SQLUSMALLINT column,
37 T const* values,
38 size_t /*rowCount*/,
39 SqlDataBinderCallback& /*cb*/,
40 SQLLEN* indicators = nullptr) noexcept
41 {
42 return SQLBindParameter(
43 stmt, column, SQL_PARAM_INPUT, TheCType, TheSqlType, 0, 0, (SQLPOINTER) values, sizeof(T), indicators);
44 }
45
46 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
47 SQLHSTMT stmt, SQLUSMALLINT column, T* result, SQLLEN* indicator, SqlDataBinderCallback& /*unused*/) noexcept
48 {
49 return SQLBindCol(stmt, column, TheCType, result, 0, indicator);
50 }
51
52 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
53 GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, T* result, SQLLEN* indicator, SqlDataBinderCallback const& /*cb*/) noexcept
54 {
55 return SQLGetData(stmt, column, TheCType, result, 0, indicator);
56 }
57
58 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(T value)
59 {
60 return std::to_string(value);
61 }
62};
63
64template <typename Int64Type, SQLSMALLINT TheCType>
65struct Int64DataBinderHelper
66{
67 static constexpr SqlColumnTypeDefinition ColumnType = SqlColumnTypeDefinitions::Bigint {};
68
69 static LIGHTWEIGHT_API SQLRETURN InputParameter(SQLHSTMT stmt,
70 SQLUSMALLINT column,
71 Int64Type const& value,
72 SqlDataBinderCallback& cb) noexcept;
73
74 static LIGHTWEIGHT_API SQLRETURN BatchInputParameter(SQLHSTMT stmt,
75 SQLUSMALLINT column,
76 Int64Type const* values,
77 size_t rowCount,
78 SqlDataBinderCallback& cb,
79 SQLLEN* indicators = nullptr) noexcept;
80
81 static LIGHTWEIGHT_API SQLRETURN OutputColumn(
82 SQLHSTMT stmt, SQLUSMALLINT column, Int64Type* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept;
83
84 static LIGHTWEIGHT_API SQLRETURN GetColumn(
85 SQLHSTMT stmt, SQLUSMALLINT column, Int64Type* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept;
86
87 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Int64Type value)
88 {
89 return std::to_string(value);
90 }
91};
92
93/// Binds a single @c char as a one-character @c CHAR(1) value.
94///
95/// Unlike the numeric primitives, @c char maps to a character type (@c SQL_C_CHAR / @c SQL_CHAR), whose
96/// ODBC contract differs from fixed-width data in three ways, each of which this binder honours:
97/// - On input, the column size is the length in characters and must not be zero (MS SQL Server rejects
98/// zero with HY104 "Invalid precision value").
99/// - On input, the value is not NUL-terminated, so its length must be passed through the indicator —
100/// without one the driver reads the value as a NUL-terminated string, past the end of the @c char.
101/// - On output, the driver NUL-terminates what it writes, so the buffer needs room for one more byte
102/// than the value; a one-byte buffer receives nothing.
103template <>
104struct SqlDataBinder<char>
105{
106 /// The column type a @c char maps to.
107 static constexpr SqlColumnTypeDefinition ColumnType = SqlColumnTypeDefinitions::Char { 1 };
108
109 /// Binds a single @c char as an input parameter.
110 ///
111 /// @param stmt The ODBC statement handle.
112 /// @param column The 1-based parameter index.
113 /// @param value The character to bind; it must outlive the statement's execution.
114 /// @param cb Provides the length indicator, which must also outlive the execution.
115 /// @return The ODBC return code of @c SQLBindParameter.
116 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
117 SQLUSMALLINT column,
118 char const& value,
119 SqlDataBinderCallback& cb) noexcept
120 {
121 auto* const indicator = cb.ProvideInputIndicator();
122 *indicator = CharLength;
123 return SQLBindParameter(
124 stmt, column, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, ColumnSize, 0, (SQLPOINTER) &value, 0, indicator);
125 }
126
127 /// Binds a contiguous (column-wise) array of characters as an input parameter.
128 ///
129 /// @param stmt The ODBC statement handle.
130 /// @param column The 1-based parameter index.
131 /// @param values The first of @p rowCount characters; they must outlive the statement's execution.
132 /// @param rowCount The number of rows in the batch.
133 /// @param cb Provides the per-row length indicators when @p indicators is not given.
134 /// @param indicators Optional per-row indicator array (the length, or @c SQL_NULL_DATA). When null,
135 /// every row is bound as one character.
136 /// @return The ODBC return code of @c SQLBindParameter.
137 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN BatchInputParameter(SQLHSTMT stmt,
138 SQLUSMALLINT column,
139 char const* values,
140 size_t rowCount,
142 SQLLEN* indicators = nullptr) noexcept
143 {
144 if (!indicators)
145 {
146 indicators = cb.ProvideInputIndicators(rowCount);
147 std::ranges::fill(std::span { indicators, rowCount }, CharLength);
148 }
149 return SQLBindParameter(stmt,
150 column,
151 SQL_PARAM_INPUT,
152 SQL_C_CHAR,
153 SQL_CHAR,
154 ColumnSize,
155 0,
156 (SQLPOINTER) values,
157 sizeof(char),
158 indicators);
159 }
160
161 /// Binds @p result as the output column @p column, filled in on every fetch.
162 ///
163 /// The driver writes into a NUL-terminated staging buffer, which a post-process callback copies into
164 /// @p result; a NULL leaves @p result untouched (@c std::optional<char> resets itself on it).
165 ///
166 /// @param stmt The ODBC statement handle.
167 /// @param column The 1-based column index.
168 /// @param result Receives the character; it must outlive the fetches.
169 /// @param indicator Receives the length, or @c SQL_NULL_DATA.
170 /// @param cb Holds the staging buffer and runs the copy after each fetch.
171 /// @return The ODBC return code of @c SQLBindCol.
172 static SQLRETURN OutputColumn(
173 SQLHSTMT stmt, SQLUSMALLINT column, char* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
174 {
175 auto buffer = std::make_shared<Buffer>();
176 auto const sqlReturn = SQLBindCol(stmt, column, SQL_C_CHAR, buffer->data(), BufferSize, indicator);
177 cb.PlanPostProcessOutputColumn([buffer, result, indicator]() {
178 if (*indicator != SQL_NULL_DATA)
179 *result = buffer->front();
180 });
181 return sqlReturn;
182 }
183
184 /// Reads the character in column @p column of the current row.
185 ///
186 /// @param stmt The ODBC statement handle.
187 /// @param column The 1-based column index.
188 /// @param result Receives the character; left untouched on NULL.
189 /// @param indicator Receives the length, or @c SQL_NULL_DATA.
190 /// @return The ODBC return code of @c SQLGetData.
191 static SQLRETURN GetColumn(
192 SQLHSTMT stmt, SQLUSMALLINT column, char* result, SQLLEN* indicator, SqlDataBinderCallback const& /*cb*/) noexcept
193 {
194 auto buffer = Buffer {};
195 auto const sqlReturn = SQLGetData(stmt, column, SQL_C_CHAR, buffer.data(), BufferSize, indicator);
196 if (SQL_SUCCEEDED(sqlReturn) && *indicator != SQL_NULL_DATA)
197 *result = buffer.front();
198 return sqlReturn;
199 }
200
201 /// @param value The character to render.
202 /// @return @p value as a one-character string.
203 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(char value)
204 {
205 return { value };
206 }
207
208 private:
209 // One character plus the NUL terminator the driver appends to SQL_C_CHAR output.
210 using Buffer = std::array<char, 2>;
211 static constexpr SQLLEN CharLength = 1;
212 static constexpr SQLULEN ColumnSize = 1;
213 static constexpr SQLLEN BufferSize = sizeof(Buffer);
214};
215
216// clang-format off
217template <> struct SqlDataBinder<bool>: SqlSimpleDataBinder<bool, SQL_BIT, SQL_BIT, SqlColumnTypeDefinitions::Bool {}> {};
218template <> struct SqlDataBinder<int8_t>: SqlSimpleDataBinder<int8_t, SQL_C_STINYINT, SQL_TINYINT, SqlColumnTypeDefinitions::Tinyint {}> {};
219template <> struct SqlDataBinder<uint8_t>: SqlSimpleDataBinder<uint8_t, SQL_C_UTINYINT, SQL_TINYINT, SqlColumnTypeDefinitions::Tinyint {}> {};
220template <> struct SqlDataBinder<int16_t>: SqlSimpleDataBinder<int16_t, SQL_C_SSHORT, SQL_SMALLINT, SqlColumnTypeDefinitions::Smallint {}> {};
221template <> struct SqlDataBinder<uint16_t>: SqlSimpleDataBinder<uint16_t, SQL_C_USHORT, SQL_SMALLINT, SqlColumnTypeDefinitions::Smallint {}> {};
222template <> struct SqlDataBinder<int32_t>: SqlSimpleDataBinder<int32_t, SQL_C_SLONG, SQL_INTEGER, SqlColumnTypeDefinitions::Integer {}> {};
223template <> struct SqlDataBinder<uint32_t>: SqlSimpleDataBinder<uint32_t, SQL_C_ULONG, SQL_INTEGER, SqlColumnTypeDefinitions::Integer {}> {};
224template <> struct SqlDataBinder<int64_t>: Int64DataBinderHelper<int64_t, SQL_C_SBIGINT> {};
225template <> struct SqlDataBinder<uint64_t>: Int64DataBinderHelper<uint64_t, SQL_C_UBIGINT> {};
226//template <> struct SqlDataBinder<uint64_t>: Int64DataBinderHelper<uint64_t, SQL_C_UBIGINT> {};
227template <> struct SqlDataBinder<float>: SqlSimpleDataBinder<float, SQL_C_FLOAT, SQL_REAL, SqlColumnTypeDefinitions::Real { .precision = 24 }> {};
228template <> struct SqlDataBinder<double>: SqlSimpleDataBinder<double, SQL_C_DOUBLE, SQL_DOUBLE, SqlColumnTypeDefinitions::Real { .precision = 53 }> {};
229#if !defined(_WIN32) && !defined(__APPLE__)
230template <> struct SqlDataBinder<long long>: Int64DataBinderHelper<long long, SQL_C_SBIGINT> {};
231template <> struct SqlDataBinder<unsigned long long>: Int64DataBinderHelper<unsigned long long, SQL_C_UBIGINT> {};
232#endif
233#if defined(__APPLE__) // size_t is a different type on macOS
234// std::size_t is unsigned, so bind it with the unsigned C type (matching SqlDataBinder<uint64_t> above);
235// SQL_C_SBIGINT would sign-corrupt values above INT64_MAX.
236template <> struct SqlDataBinder<std::size_t>: SqlSimpleDataBinder<std::size_t, SQL_C_UBIGINT, SQL_BIGINT, SqlColumnTypeDefinitions::Bigint {}> {};
237#endif
238
239// These fixed-width primitives bind via a plain SQLBindParameter and are eligible for native row-wise
240// batch binding (see SqlIsNativeRowBindableValue in Core.hpp). A single constrained partial
241// specialization, keyed on detail::IsAnyOf, covers them all.
242//
243// char is deliberately absent: row-wise binding strides the value in place, but a char needs a per-row
244// length on input and a NUL-terminated staging buffer on output (see SqlDataBinder<char>), so records
245// carrying one take the per-row paths instead.
246template <typename T>
247 requires detail::IsAnyOf<T, bool, int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t,
248 float, double
249#if !defined(_WIN32) && !defined(__APPLE__)
250 , long long, unsigned long long
251#endif
252#if defined(__APPLE__)
253 , std::size_t
254#endif
255 >
256inline constexpr bool SqlIsNativeRowBindableValue<T> = true;
257// clang-format on
258
259} // namespace Lightweight
virtual SQLLEN * ProvideInputIndicators(size_t rowCount)=0
static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(char value)
static SQLRETURN OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, char *result, SQLLEN *indicator, SqlDataBinderCallback &cb) noexcept
static SQLRETURN GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, char *result, SQLLEN *indicator, SqlDataBinderCallback const &) noexcept
static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt, SQLUSMALLINT column, char const &value, SqlDataBinderCallback &cb) noexcept
static LIGHTWEIGHT_FORCE_INLINE SQLRETURN BatchInputParameter(SQLHSTMT stmt, SQLUSMALLINT column, char const *values, size_t rowCount, SqlDataBinderCallback &cb, SQLLEN *indicators=nullptr) noexcept