Lightweight 0.20260617.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 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) data(this auto&& self) noexcept
144 {
145 self.EnsureNonNullBuffer();
146 return self._base.data();
147 }
148
149 /// Clears the storad data.
150 LIGHTWEIGHT_FORCE_INLINE void clear() noexcept
151 {
152 _base.clear();
153 }
154
155 private:
156 void EnsureNonNullBuffer() const
157 {
158 if (_base.data() == nullptr)
159 {
160 const_cast<SqlDynamicBinary<N>*>(this)->_base.resize(8);
161 const_cast<SqlDynamicBinary<N>*>(this)->_base.clear();
162 }
163 }
164
165 BaseType _base;
166 mutable SQLLEN _indicator = 0;
167
168 friend struct SqlDataBinder<SqlDynamicBinary<N>>;
169};
170
171namespace detail
172{
173
174 template <typename>
175 struct IsSqlDynamicBinaryImpl: std::false_type
176 {
177 };
178
179 template <size_t T>
180 struct IsSqlDynamicBinaryImpl<SqlDynamicBinary<T>>: std::true_type
181 {
182 };
183
184 template <size_t T>
185 struct IsSqlDynamicBinaryImpl<std::optional<SqlDynamicBinary<T>>>: std::true_type
186 {
187 };
188
189} // namespace detail
190
191template <typename T>
192constexpr bool IsSqlDynamicBinary = detail::IsSqlDynamicBinaryImpl<T>::value;
193
194} // namespace Lightweight
195
196template <std::size_t N>
197struct std::formatter<Lightweight::SqlDynamicBinary<N>>: std::formatter<std::string>
198{
199 auto format(Lightweight::SqlDynamicBinary<N> const& text, format_context& ctx) const
200 {
201 std::string humanReadableText;
202 for (auto byte: text)
203 {
204 if (byte >= 0x20 && byte <= 0x7E)
205 humanReadableText += static_cast<char>(byte);
206 else
207 humanReadableText += std::format("\\x{:02X}", byte);
208 }
209 return std::formatter<std::string>::format(humanReadableText.data(), ctx);
210 }
211};
212
213namespace Lightweight
214{
215
216template <std::size_t N>
217struct SqlDataBinder<SqlDynamicBinary<N>>
218{
219 using ValueType = SqlDynamicBinary<N>;
220
221 static constexpr auto ColumnType = SqlColumnTypeDefinitions::VarBinary { N };
222
223 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
224 SQLUSMALLINT column,
225 ValueType const& value,
226 SqlDataBinderCallback& /*cb*/) noexcept
227 {
228 value._indicator = static_cast<SQLLEN>(value.size());
229
230 auto const sqlType = static_cast<SQLSMALLINT>(value.size() > 8000 ? SQL_LONGVARBINARY : SQL_VARBINARY);
231 auto const bufferSize = static_cast<SQLULEN>(value.size());
232 auto* const ptr = (SQLPOINTER) value.data();
233
234 return SQLBindParameter(
235 stmt, column, SQL_PARAM_INPUT, SQL_C_BINARY, sqlType, bufferSize, 0, ptr, 0, &value._indicator);
236 }
237
238 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
239 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
240 {
241 if (result->empty())
242 result->resize(255);
243
244 cb.PlanPostProcessOutputColumn([stmt, column, result, indicator]() {
245 if (*indicator == SQL_NULL_DATA)
246 // The data is NULL.
247 result->clear();
248 else if (*indicator == SQL_NO_TOTAL)
249 // We have a truncation and the server does not know how much data is left.
250 result->resize(result->size() - 1);
251 else if (*indicator <= static_cast<SQLLEN>(result->size()))
252 result->resize(static_cast<size_t>(*indicator));
253 else
254 {
255 // We have a truncation and the server knows how much data is left.
256 // Extend the buffer and fetch the rest via SQLGetData.
257
258 auto const totalCharsRequired = *indicator;
259 result->resize(static_cast<size_t>(totalCharsRequired + 1));
260 auto const sqlResult =
261 SQLGetData(stmt, column, SQL_C_BINARY, (SQLPOINTER) result->data(), totalCharsRequired + 1, indicator);
262 (void) sqlResult;
263 assert(SQL_SUCCEEDED(sqlResult));
264 assert(*indicator == totalCharsRequired);
265 result->resize(static_cast<size_t>(totalCharsRequired));
266 }
267 });
268
269 return SQLBindCol(stmt, column, SQL_C_BINARY, (SQLPOINTER) result->data(), 255, indicator);
270 }
271
272 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
273 SQLUSMALLINT column,
274 ValueType* result,
275 SQLLEN* indicator,
276 SqlDataBinderCallback const& /*cb*/) noexcept
277 {
278 if (result->empty())
279 result->resize(255);
280
281 return detail::GetRawColumnArrayData<SQL_C_BINARY>(stmt, column, result, indicator);
282 }
283
284 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value)
285 {
286 return std::format("SqlBinary<{}>(size={})", N, value.size());
287 }
288};
289
290} // 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 decltype(auto) data(this auto &&self) noexcept
Retrieves the pointer to the string data.
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.
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.
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