Lightweight 0.20260921.0
Loading...
Searching...
No Matches
SqlDynamicBinary.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlColumnTypeDefinitions.hpp"
6#include "BasicStringBinder.hpp"
7#include "Core.hpp"
8
9#include <cassert>
10#include <format>
11#include <span>
12#include <string>
13
14namespace Lightweight
15{
16
17/// SQL dynamic-capacity string that mimicks standard library string.
18///
19/// The underlying memory is allocated dynamically and the string can grow up to the maximum size of a SQL column.
20///
21/// @ingroup DataTypes
22template <std::size_t N>
24{
25 using BaseType = std::vector<uint8_t>;
26
27 public:
28 /// The element type of the binary data.
29 using value_type = uint8_t;
30
31 /// Read-only view over the stored bytes.
32 ///
33 /// MSVC's STL is pinned to `std::basic_string_view` here: its `std::span` reverse iterator drags
34 /// the legacy `std::iter_move` into overload resolution, which is then ambiguous with
35 /// `std::ranges::enumerate_view`'s own `iter_move` in any translation unit that uses both.
36 /// Other standard libraries keep `std::span` because `std::basic_string_view<uint8_t>` is
37 /// ill-formed there (no `std::char_traits<unsigned char>`).
38#if defined(_MSVC_STL_VERSION)
39 using ByteView = std::basic_string_view<value_type>;
40#else
41 using ByteView = std::span<value_type const>;
42#endif
43
44 /// The SQL column type definition for this binary type.
45 static constexpr auto ColumnType = SqlColumnTypeDefinitions::VarBinary { N };
46
47 /// The maximum size of the underlying data storage.
48 static constexpr std::size_t DynamicCapacity = N;
49
50 /// Default constructor, creates an empty object.
51 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary() noexcept = default;
52
53 /// Defaulted copy constructor.
54 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary(SqlDynamicBinary const&) noexcept = default;
55
56 /// Defaulted copy assignment operator.
57 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary& operator=(SqlDynamicBinary const&) noexcept = default;
58
59 /// Defaulted move constructor.
60 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary(SqlDynamicBinary&&) noexcept = default;
61
62 /// Defaulted move assignment operator.
63 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary& operator=(SqlDynamicBinary&&) noexcept = default;
64
65 /// Defaulted destructor.
66 LIGHTWEIGHT_FORCE_INLINE constexpr ~SqlDynamicBinary() noexcept = default;
67
68 /// Constructs a fixed-size string from a string literal.
69 template <std::size_t SourceSize>
70 constexpr LIGHTWEIGHT_FORCE_INLINE SqlDynamicBinary(value_type const (&text)[SourceSize]):
71 _base { text, text + SourceSize }
72 {
73 static_assert(SourceSize <= N + 1, "RHS string size must not exceed target string's capacity.");
74 }
75
76 /// Constructs the object from an initializer list of bytes.
77 template <std::size_t SourceSize>
78 constexpr LIGHTWEIGHT_FORCE_INLINE SqlDynamicBinary(std::initializer_list<value_type> data):
79 _base { data }
80 {
81 static_assert(SourceSize <= N + 1, "RHS string size must not exceed target string's capacity.");
82 }
83
84 /// Constructs a fixed-size string from a string pointer and end pointer.
85 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary(value_type const* s, value_type const* e) noexcept:
86 _base { s, e }
87 {
88 }
89#if !defined(LIGHTWEIGHT_CXX26_REFLECTION)
90 /// Constructs the binary payload from a contiguous view of bytes.
91 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary(ByteView s) noexcept:
92 _base { s.data(), s.data() + s.size() }
93 {
94 }
95
96 /// Retrieves a read-only view over the stored bytes.
97 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ByteView Bytes() const noexcept
98 {
99 return { _base.data(), _base.size() };
100 }
101
102 /// @deprecated Use @ref Bytes(). Alias kept for source compatibility; the name is a misnomer since
103 /// this type holds raw bytes, not text.
104 [[deprecated("use Bytes(); SqlDynamicBinary stores raw bytes, not text")]] [[nodiscard]]
105 LIGHTWEIGHT_FORCE_INLINE constexpr ByteView ToStringView() const noexcept
106 {
107 return Bytes();
108 }
109#endif
110
111 /// Three-way comparison operator. Compares the byte payload only — the private
112 /// `_indicator` member is ODBC bookkeeping and must not influence equality.
113 constexpr auto operator<=>(SqlDynamicBinary<N> const& other) const noexcept
114 {
115 return _base <=> other._base;
116 }
117
118 /// Equality operator derived from the spaceship comparison above.
119 constexpr bool operator==(SqlDynamicBinary<N> const& other) const noexcept
120 {
121 return _base == other._base;
122 }
123
124 /// Retrieves the size of the string.
125 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr std::size_t size() const noexcept
126 {
127 return _base.size();
128 }
129
130 /// Resizes the underlying data storage to the given size.
131 void LIGHTWEIGHT_FORCE_INLINE resize(std::size_t newSize)
132 {
133 _base.resize(newSize);
134 }
135
136 /// Tests if the stored data is empty.
137 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool empty() const noexcept
138 {
139 return _base.empty();
140 }
141
142 /// Retrieves the pointer to the string data.
143 ///
144 /// The non-const overload lazily grows `_base` so a null `data()` never reaches ODBC — some
145 /// drivers reject a null `ParameterValuePtr` even for a zero-length bind. The const overload must
146 /// not mutate `_base` (a genuinely const-qualified object's members may live in read-only memory,
147 /// and writing through a `const_cast` pointer to one is undefined behavior per [dcl.type.cv]), so
148 /// it returns a static empty-but-non-null sentinel instead when the buffer hasn't been
149 /// materialized yet. Safe because every caller pairs this pointer with `size()` (0 in the
150 /// null-buffer case) rather than dereferencing it directly, so the sentinel byte is never read.
151 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr value_type const* data() const noexcept
152 {
153 if (_base.data() != nullptr)
154 return _base.data();
155
156 static constexpr value_type emptySentinel = 0;
157 return &emptySentinel;
158 }
159
160 /// Retrieves the pointer to the string data.
161 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr value_type* data() noexcept
162 {
163 if (_base.data() == nullptr)
164 {
165 _base.resize(8);
166 _base.clear();
167 }
168 return _base.data();
169 }
170
171 /// Clears the storad data.
172 LIGHTWEIGHT_FORCE_INLINE void clear() noexcept
173 {
174 _base.clear();
175 }
176
177 private:
178 BaseType _base;
179 mutable SQLLEN _indicator = 0;
180
181 friend struct SqlDataBinder<SqlDynamicBinary<N>>;
182};
183
184namespace detail
185{
186
187 template <typename>
188 struct IsSqlDynamicBinaryImpl: std::false_type
189 {
190 };
191
192 template <size_t T>
193 struct IsSqlDynamicBinaryImpl<SqlDynamicBinary<T>>: std::true_type
194 {
195 };
196
197 template <size_t T>
198 struct IsSqlDynamicBinaryImpl<std::optional<SqlDynamicBinary<T>>>: std::true_type
199 {
200 };
201
202} // namespace detail
203
204template <typename T>
205constexpr bool IsSqlDynamicBinary = detail::IsSqlDynamicBinaryImpl<T>::value;
206
207} // namespace Lightweight
208
209template <std::size_t N>
210struct std::formatter<Lightweight::SqlDynamicBinary<N>>: std::formatter<std::string>
211{
212 auto format(Lightweight::SqlDynamicBinary<N> const& text, format_context& ctx) const
213 {
214 // SqlDynamicBinary<N> exposes data()/size(), not begin()/end() — it is a raw-byte buffer,
215 // not an iterable range. Views it as a std::span instead of range-for'ing over the type
216 // directly, keeping the loop itself range-based.
217 std::string humanReadableText;
218 for (auto const byte: std::span { text.data(), text.size() })
219 {
220 if (byte >= 0x20 && byte <= 0x7E)
221 humanReadableText += static_cast<char>(byte);
222 else
223 humanReadableText += std::format("\\x{:02X}", byte);
224 }
225 return std::formatter<std::string>::format(humanReadableText.data(), ctx);
226 }
227};
228
229namespace Lightweight
230{
231
232template <std::size_t N>
233struct SqlDataBinder<SqlDynamicBinary<N>>
234{
235 using ValueType = SqlDynamicBinary<N>;
236
237 static constexpr auto ColumnType = SqlColumnTypeDefinitions::VarBinary { N };
238
239 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
240 SQLUSMALLINT column,
241 ValueType const& value,
242 SqlDataBinderCallback& /*cb*/) noexcept
243 {
244 value._indicator = static_cast<SQLLEN>(value.size());
245
246 auto const sqlType = static_cast<SQLSMALLINT>(value.size() > 8000 ? SQL_LONGVARBINARY : SQL_VARBINARY);
247 auto const bufferSize = static_cast<SQLULEN>(value.size());
248 auto* const ptr = (SQLPOINTER) value.data();
249
250 return SQLBindParameter(
251 stmt, column, SQL_PARAM_INPUT, SQL_C_BINARY, sqlType, bufferSize, 0, ptr, 0, &value._indicator);
252 }
253
254 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
255 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
256 {
257 if (result->empty())
258 result->resize(255);
259
260 cb.PlanPostProcessOutputColumn([stmt, column, result, indicator]() {
261 if (*indicator == SQL_NULL_DATA)
262 // The data is NULL.
263 result->clear();
264 else if (*indicator == SQL_NO_TOTAL)
265 // We have a truncation and the server does not know how much data is left.
266 result->resize(result->size() - 1);
267 else if (*indicator <= static_cast<SQLLEN>(result->size()))
268 result->resize(static_cast<size_t>(*indicator));
269 else
270 {
271 // We have a truncation and the server knows how much data is left.
272 // Extend the buffer and fetch the rest via SQLGetData.
273
274 auto const totalCharsRequired = *indicator;
275 result->resize(static_cast<size_t>(totalCharsRequired + 1));
276 auto const sqlResult =
277 SQLGetData(stmt, column, SQL_C_BINARY, (SQLPOINTER) result->data(), totalCharsRequired + 1, indicator);
278 (void) sqlResult;
279 assert(SQL_SUCCEEDED(sqlResult));
280 assert(*indicator == totalCharsRequired);
281 result->resize(static_cast<size_t>(totalCharsRequired));
282 }
283 });
284
285 return SQLBindCol(stmt, column, SQL_C_BINARY, (SQLPOINTER) result->data(), 255, indicator);
286 }
287
288 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
289 SQLUSMALLINT column,
290 ValueType* result,
291 SQLLEN* indicator,
292 SqlDataBinderCallback const& /*cb*/) noexcept
293 {
294 if (result->empty())
295 result->resize(255);
296
297 return detail::GetRawColumnArrayData<SQL_C_BINARY>(stmt, column, result, indicator);
298 }
299
300 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value)
301 {
302 return std::format("SqlBinary<{}>(size={})", N, value.size());
303 }
304};
305
306} // namespace Lightweight
LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary(value_type const *s, value_type const *e) noexcept
Constructs a fixed-size string from a string pointer and end pointer.
static constexpr auto ColumnType
The SQL column type definition for this binary type.
LIGHTWEIGHT_FORCE_INLINE constexpr bool empty() const noexcept
Tests if the stored data is empty.
LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary(ByteView s) noexcept
Constructs the binary payload from a contiguous view of bytes.
LIGHTWEIGHT_FORCE_INLINE void clear() noexcept
Clears the storad data.
LIGHTWEIGHT_FORCE_INLINE constexpr std::size_t size() const noexcept
Retrieves the size of the string.
LIGHTWEIGHT_FORCE_INLINE constexpr ByteView Bytes() const noexcept
Retrieves a read-only view over the stored bytes.
LIGHTWEIGHT_FORCE_INLINE constexpr ByteView ToStringView() const noexcept
void LIGHTWEIGHT_FORCE_INLINE resize(std::size_t newSize)
Resizes the underlying data storage to the given size.
LIGHTWEIGHT_FORCE_INLINE constexpr value_type const * data() const noexcept
uint8_t value_type
The element type of the binary data.
constexpr bool operator==(SqlDynamicBinary< N > const &other) const noexcept
Equality operator derived from the spaceship comparison above.
LIGHTWEIGHT_FORCE_INLINE constexpr SqlDynamicBinary() noexcept=default
Default constructor, creates an empty object.
static constexpr std::size_t DynamicCapacity
The maximum size of the underlying data storage.
LIGHTWEIGHT_FORCE_INLINE constexpr value_type * data() noexcept
Retrieves the pointer to the string data.
constexpr LIGHTWEIGHT_FORCE_INLINE SqlDynamicBinary(std::initializer_list< value_type > data)
Constructs the object from an initializer list of bytes.
std::span< value_type const > ByteView
constexpr auto operator<=>(SqlDynamicBinary< N > const &other) const noexcept