Lightweight 0.20260303.0
Loading...
Searching...
No Matches
BasicStringBinder.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Core.hpp"
6#include "UnicodeConverter.hpp"
7
8#include <cassert>
9#include <cstring>
10#include <memory>
11#include <utility>
12
13namespace Lightweight
14{
15
16/// @brief Magic number, which is used to determine the optimal maximum size of a column.
17///
18/// Columns may be larger than this value, but this is the optimal maximum size for performance,
19/// and usually also means that values are stored in the same row as the rest of the data, or not.
20constexpr inline std::size_t SqlOptimalMaxColumnSize = 4000;
21
22namespace detail
23{
24 /// Helper function to get raw column data of an array-like type.
25 ///
26 /// @param stmt The SQL statement handle.
27 /// @param column The column number to retrieve data from.
28 /// @param result The array-like type to store the data in.
29 /// @param indicator The indicator to store the length of the data.
30 ///
31 /// @return SQLRETURN indicating the result of the operation.
32 template <auto CType, typename ArrayType>
33 requires requires(ArrayType& arr) {
34 { arr.data() } -> std::convertible_to<typename ArrayType::value_type*>;
35 { arr.size() } -> std::convertible_to<std::size_t>;
36 { arr.resize(std::declval<std::size_t>()) };
37 }
38 SQLRETURN GetRawColumnArrayData(SQLHSTMT stmt, SQLUSMALLINT column, ArrayType* result, SQLLEN* indicator) noexcept
39 {
40 using CharType = ArrayType::value_type;
41
42 *indicator = 0;
43
44 // Resize the string to the size of the data
45 // Get the data (take care of SQL_NULL_DATA and SQL_NO_TOTAL)
46 auto sqlResult = SQLGetData(stmt,
47 column,
48 CType,
49 static_cast<SQLPOINTER>(result->data()),
50 static_cast<SQLLEN>(result->size() * sizeof(CharType)),
51 indicator);
52
53 if (sqlResult == SQL_SUCCESS || sqlResult == SQL_NO_DATA)
54 {
55 // Data has been read successfully on first call to SQLGetData, or there is no data to read.
56 if (*indicator == SQL_NULL_DATA)
57 result->clear();
58 else
59 result->resize(static_cast<size_t>(*indicator) / sizeof(CharType));
60 return sqlResult;
61 }
62
63 if (sqlResult == SQL_SUCCESS_WITH_INFO && *indicator > static_cast<SQLLEN>(result->size()))
64 {
65 // We have a truncation and the server knows how much data is left.
66 auto const totalCharCount = static_cast<size_t>(*indicator) / sizeof(CharType);
67 auto const charsWritten = result->size() - 1;
68 result->resize(totalCharCount + 1);
69 auto* bufferCont = result->data() + charsWritten;
70 auto const bufferCharsAvailable = (totalCharCount + 1) - charsWritten;
71 sqlResult = SQLGetData(
72 stmt, column, CType, bufferCont, static_cast<SQLLEN>(bufferCharsAvailable * sizeof(CharType)), indicator);
73 if (SQL_SUCCEEDED(sqlResult))
74 result->resize(charsWritten + (static_cast<size_t>(*indicator) / sizeof(CharType)));
75 return sqlResult;
76 }
77
78 size_t writeIndex = 0;
79 while (sqlResult == SQL_SUCCESS_WITH_INFO && *indicator == SQL_NO_TOTAL)
80 {
81 // We have a truncation and the server does not know how much data is left.
82 writeIndex += result->size() - 1;
83 result->resize(result->size() * 2);
84 auto* const bufferStart = result->data() + writeIndex;
85 size_t const bufferCharsAvailable = result->size() - writeIndex;
86 sqlResult = SQLGetData(
87 stmt, column, CType, bufferStart, static_cast<SQLLEN>(bufferCharsAvailable * sizeof(CharType)), indicator);
88 }
89 return sqlResult;
90 }
91
92 template <typename Utf16StringType>
93 SQLRETURN GetColumnUtf16(SQLHSTMT stmt,
94 SQLUSMALLINT column,
95 Utf16StringType* result,
96 SQLLEN* indicator,
97 SqlDataBinderCallback const& /*cb*/) noexcept
98 {
99 if constexpr (requires { Utf16StringType::Capacity; })
100 result->resize(Utf16StringType::Capacity);
101 else if (result->size() == 0)
102 result->resize(255);
103
104 return GetRawColumnArrayData<SQL_C_WCHAR>(stmt, column, result, indicator);
105 }
106
107 template <typename StringType>
108 SQLRETURN BindOutputColumnNonUtf16Unicode(
109 SQLHSTMT stmt, SQLUSMALLINT column, StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
110 {
111 using CharType = StringType::value_type;
112
113 auto u16String = std::make_shared<std::u16string>();
114 if (!result->empty())
115 u16String->resize(result->size());
116 else
117 u16String->resize(255);
118
119 cb.PlanPostProcessOutputColumn([stmt, column, result, indicator, u16String = u16String]() {
120 if (*indicator == SQL_NULL_DATA)
121 u16String->clear();
122 else if (*indicator == SQL_NO_TOTAL)
123 ; // We don't know the size of the data
124 else if (std::cmp_less_equal(*indicator, u16String->size() * sizeof(char16_t)))
125 u16String->resize(static_cast<size_t>(*indicator) / sizeof(char16_t));
126 else
127 {
128 auto const totalCharsRequired = static_cast<size_t>(*indicator) / sizeof(char16_t);
129 *indicator += sizeof(char16_t); // Add space to hold the null terminator
130 u16String->resize(totalCharsRequired);
131 auto const sqlResult = SQLGetData(stmt, column, SQL_C_WCHAR, u16String->data(), *indicator, indicator);
132 (void) sqlResult;
133 assert(SQL_SUCCEEDED(sqlResult));
134 assert(std::cmp_equal(*indicator, totalCharsRequired * sizeof(char16_t)));
135 }
136
137 if constexpr (sizeof(typename StringType::value_type) == 1)
138 *result = ToUtf8(*u16String);
139 else if constexpr (sizeof(typename StringType::value_type) == 4)
140 {
141 // *result = ToUtf32(*u16String);
142 auto const u32String = ToUtf32(*u16String);
143 *result = StringType {
144 (CharType const*) u32String.data(),
145 (CharType const*) u32String.data() + u32String.size(),
146 };
147 }
148
149 // The UTF-16 binder applies StringTraits::PostProcessOutputColumn (if present) so that
150 // FIXED_SIZE_RIGHT_TRIMMED strings strip the trailing CHAR(N)/NCHAR(N) padding the server
151 // returned. The UTF-32 / UTF-8 path constructs *result above, which preserves the padding,
152 // so we have to invoke the same post-process here. The synthetic indicator is the
153 // result's own byte count: PostProcessOutputColumn divides by sizeof(CharType) to get
154 // the char count, and TrimRight then walks back over trailing whitespace/null.
155 using StringTraits = SqlBasicStringOperations<StringType>;
156 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
157 {
158 if (*indicator != SQL_NULL_DATA && *indicator != SQL_NO_TOTAL)
159 {
160 auto const syntheticIndicator = static_cast<SQLLEN>(StringTraits::Size(result) * sizeof(CharType));
161 StringTraits::PostProcessOutputColumn(result, syntheticIndicator);
162 }
163 }
164 });
165
166 return SQLBindCol(stmt,
167 column,
168 SQL_C_WCHAR,
169 static_cast<SQLPOINTER>(u16String->data()),
170 static_cast<SQLLEN>(u16String->size() * sizeof(char16_t)),
171 indicator);
172 }
173
174} // namespace detail
175
176// SqlDataBinder<> specialization for ANSI character strings
177template <typename AnsiStringType>
178 requires SqlBasicStringBinderConcept<AnsiStringType, char>
179struct SqlDataBinder<AnsiStringType>
180{
181 using ValueType = AnsiStringType;
182 using CharType = char;
183 using StringTraits = SqlBasicStringOperations<AnsiStringType>;
184
185 static constexpr auto ColumnType = StringTraits::ColumnType;
186
187 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
188 SQLUSMALLINT column,
189 AnsiStringType const& value,
190 SqlDataBinderCallback& cb) noexcept
191 {
192 // PostgreSQL: route narrow strings through UTF-16 + SQL_C_WCHAR. Even with the
193 // DBC handle in Unicode-app mode (SQLDriverConnectW), psqlODBC on Windows still
194 // interprets SQL_C_CHAR data as the system codepage (cp1252), not UTF-8 — so a
195 // 200-byte UTF-8 payload representing 100 supplementary-plane characters gets
196 // re-encoded into 400 bytes and trips VARCHAR(100)'s character-count check.
197 // Going via SQL_C_WCHAR makes the driver itself do the encoding conversion to
198 // the server's encoding (UTF-8) using proper Unicode rules. We treat the
199 // incoming `std::string` as UTF-8 by convention — that is what every other
200 // call path in the library produces (file readers, query formatters, etc.).
201 if (cb.ServerType() == SqlServerType::POSTGRESQL)
202 {
203 auto u16String = std::make_shared<std::u16string>(ToUtf16(std::u8string_view {
204 reinterpret_cast<char8_t const*>(StringTraits::Data(&value)), StringTraits::Size(&value) }));
205 cb.PlanPostExecuteCallback([u16String = u16String]() {}); // keep buffer alive
206 auto const charCount = u16String->size();
207 auto const byteCount = charCount * sizeof(char16_t);
208 auto const sqlType =
209 static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
210
211 SQLLEN* indicator = cb.ProvideInputIndicator();
212 *indicator = static_cast<SQLLEN>(byteCount);
213
214 return SQLBindParameter(stmt,
215 column,
216 SQL_PARAM_INPUT,
217 SQL_C_WCHAR,
218 sqlType,
219 charCount,
220 0,
221 (SQLPOINTER) u16String->data(),
222 static_cast<SQLLEN>(byteCount),
223 indicator);
224 }
225
226 auto const charCount = StringTraits::Size(&value);
227 auto const sqlType = static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_LONGVARCHAR : SQL_VARCHAR);
228
229 SQLLEN* indicator = cb.ProvideInputIndicator();
230 *indicator = static_cast<SQLLEN>(charCount);
231
232 return SQLBindParameter(stmt,
233 column,
234 SQL_PARAM_INPUT,
235 SQL_C_CHAR,
236 sqlType,
237 charCount,
238 0,
239 (SQLPOINTER) StringTraits::Data(&value),
240 0,
241 indicator);
242 }
243
244 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN BatchInputParameter(SQLHSTMT stmt,
245 SQLUSMALLINT column,
246 AnsiStringType const* values,
247 size_t rowCount,
248 SqlDataBinderCallback& cb) noexcept
249 {
250 size_t maxLen = 0;
251 SQLLEN* indicators = cb.ProvideInputIndicators(rowCount);
252 for (size_t i = 0; i < rowCount; ++i)
253 {
254 auto const len = StringTraits::Size(&values[i]);
255 indicators[i] = static_cast<SQLLEN>(len);
256 if (len > maxLen)
257 maxLen = len;
258 }
259
260 auto const sqlType = static_cast<SQLSMALLINT>(maxLen > SqlOptimalMaxColumnSize ? SQL_LONGVARCHAR : SQL_VARCHAR);
261
262 return SQLBindParameter(stmt,
263 column,
264 SQL_PARAM_INPUT,
265 SQL_C_CHAR,
266 sqlType,
267 maxLen,
268 0,
269 (SQLPOINTER) values,
270 sizeof(AnsiStringType),
271 indicators);
272 }
273
274 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
275 SQLHSTMT stmt, SQLUSMALLINT column, AnsiStringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
276 {
277 if constexpr (requires { AnsiStringType::Capacity; })
278 StringTraits::Resize(result, AnsiStringType::Capacity);
279 else if (StringTraits::Size(result) == 0)
280 StringTraits::Resize(result, 255);
281
282 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
283 cb.PlanPostProcessOutputColumn(
284 [indicator, result]() { StringTraits::PostProcessOutputColumn(result, *indicator); });
285 else
286 cb.PlanPostProcessOutputColumn(
287 [stmt, column, indicator, result]() { PostProcessOutputColumn(stmt, column, result, indicator); });
288
289 return SQLBindCol(stmt,
290 column,
291 SQL_C_CHAR,
292 (SQLPOINTER) StringTraits::Data(result),
293 (SQLLEN) StringTraits::Size(result),
294 indicator);
295 }
296
297 static void PostProcessOutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, AnsiStringType* result, SQLLEN* indicator)
298 {
299 // Now resize the string to the actual length of the data
300 // NB: If the indicator is greater than the buffer size, we have a truncation.
301 if (*indicator == SQL_NO_TOTAL)
302 {
303 // We have a truncation and the server does not know how much data is left.
304 StringTraits::Resize(result, static_cast<SQLLEN>(StringTraits::Size(result)) - 1);
305 }
306 else if (*indicator == SQL_NULL_DATA)
307 {
308 // We have a NULL value
309 StringTraits::Resize(result, 0);
310 }
311 else if (*indicator <= static_cast<SQLLEN>(StringTraits::Size(result)))
312 {
313 StringTraits::Resize(result, *indicator);
314 }
315 else
316 {
317 // We have a truncation and the server knows how much data is left.
318 // Extend the buffer and fetch the rest via SQLGetData.
319
320 auto const totalCharsRequired = *indicator;
321 StringTraits::Resize(result, totalCharsRequired + 1);
322 auto const sqlResult =
323 SQLGetData(stmt, column, SQL_C_CHAR, StringTraits::Data(result), totalCharsRequired + 1, indicator);
324 (void) sqlResult;
325 assert(SQL_SUCCEEDED(sqlResult));
326 assert(*indicator == totalCharsRequired);
327 StringTraits::Resize(result, totalCharsRequired);
328 }
329 }
330
331 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
332 static SQLRETURN GetColumn(SQLHSTMT stmt,
333 SQLUSMALLINT column,
334 AnsiStringType* result,
335 SQLLEN* indicator,
336 SqlDataBinderCallback const& /*cb*/) noexcept
337 {
338 if constexpr (requires { AnsiStringType::Capacity; })
339 {
340 StringTraits::Resize(result, AnsiStringType::Capacity);
341 SQLRETURN const rv =
342 SQLGetData(stmt, column, SQL_C_CHAR, StringTraits::Data(result), AnsiStringType::Capacity, indicator);
343 if (rv == SQL_SUCCESS || rv == SQL_NO_DATA)
344 {
345 if (*indicator == SQL_NULL_DATA)
346 StringTraits::Resize(result, 0);
347 else if (*indicator != SQL_NO_TOTAL)
348 StringTraits::Resize(
349 result, static_cast<SQLLEN>((std::min) (AnsiStringType::Capacity, static_cast<size_t>(*indicator))));
350 }
351 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
352 StringTraits::PostProcessOutputColumn(result, *indicator);
353 return rv;
354 }
355 else
356 {
357 StringTraits::Reserve(result, 15);
358 size_t writeIndex = 0;
359 *indicator = 0;
360 while (true)
361 {
362 auto* const bufferStart = StringTraits::Data(result) + writeIndex;
363 size_t const bufferSize = StringTraits::Size(result) - writeIndex;
364 SQLRETURN const rv =
365 SQLGetData(stmt, column, SQL_C_CHAR, bufferStart, static_cast<SQLLEN>(bufferSize), indicator);
366 switch (rv)
367 {
368 case SQL_SUCCESS:
369 case SQL_NO_DATA:
370 // last successive call
371 if (*indicator != SQL_NULL_DATA)
372 {
373 StringTraits::Resize(result, static_cast<SQLLEN>(writeIndex) + *indicator);
374 *indicator = static_cast<SQLLEN>(StringTraits::Size(result));
375 }
376 return SQL_SUCCESS;
377 case SQL_SUCCESS_WITH_INFO: {
378 // more data pending
379 if (*indicator == SQL_NO_TOTAL)
380 {
381 // We have a truncation and the server does not know how much data is left.
382 writeIndex += bufferSize - 1;
383 StringTraits::Resize(result, static_cast<SQLLEN>((2 * writeIndex) + 1));
384 }
385 else if (std::cmp_greater_equal(*indicator, bufferSize))
386 {
387 // We have a truncation and the server knows how much data is left.
388 writeIndex += bufferSize - 1;
389 StringTraits::Resize(result, static_cast<SQLLEN>(writeIndex) + *indicator);
390 }
391 else
392 {
393 // We have no truncation and the server knows how much data is left.
394 StringTraits::Resize(result, static_cast<SQLLEN>(writeIndex) + *indicator - 1);
395 return SQL_SUCCESS;
396 }
397 break;
398 }
399 default:
400 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
401 StringTraits::PostProcessOutputColumn(result, *indicator);
402 return rv;
403 }
404 }
405 }
406 }
407
408 static LIGHTWEIGHT_FORCE_INLINE std::string_view Inspect(AnsiStringType const& value) noexcept
409 {
410 return { StringTraits::Data(&value), StringTraits::Size(&value) };
411 }
412};
413
414// SqlDataBinder<> specialization for UTF-16 strings
415template <typename Utf16StringType>
416 requires(SqlBasicStringBinderConcept<Utf16StringType, char16_t>
417 || (SqlBasicStringBinderConcept<Utf16StringType, unsigned short>)
418 || (SqlBasicStringBinderConcept<Utf16StringType, wchar_t> && sizeof(wchar_t) == 2))
419struct SqlDataBinder<Utf16StringType>
420{
421 using ValueType = Utf16StringType;
422 using CharType = std::remove_cvref_t<decltype(std::declval<Utf16StringType>()[0])>;
423 using StringTraits = SqlBasicStringOperations<Utf16StringType>;
424
425 static constexpr auto ColumnType = StringTraits::ColumnType;
426
427 static constexpr auto CType = SQL_C_WCHAR;
428
429 static SQLRETURN InputParameter(SQLHSTMT stmt,
430 SQLUSMALLINT column,
431 Utf16StringType const& value,
432 SqlDataBinderCallback& cb) noexcept
433 {
434 // Bind UTF-16 input directly via SQL_C_WCHAR for every backend; the driver
435 // converts to the server's encoding, including correct handling of UTF-16
436 // surrogate pairs for supplementary-plane code points (emoji, CJK extension B+).
437 using CharType = StringTraits::CharType;
438 auto const* data = StringTraits::Data(&value);
439 auto const sizeInBytes = StringTraits::Size(&value) * sizeof(CharType);
440 auto const charCount = StringTraits::Size(&value);
441 auto const sqlType = static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
442
443 SQLLEN* indicator = cb.ProvideInputIndicator();
444 *indicator = static_cast<SQLLEN>(sizeInBytes);
445
446 return SQLBindParameter(stmt,
447 column,
448 SQL_PARAM_INPUT,
449 CType,
450 sqlType,
451 charCount,
452 0,
453 (SQLPOINTER) data,
454 static_cast<SQLLEN>(sizeInBytes),
455 indicator);
456 }
457
458 static SQLRETURN OutputColumn(
459 SQLHSTMT stmt, SQLUSMALLINT column, Utf16StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
460 {
461 if constexpr (requires { Utf16StringType::Capacity; })
462 StringTraits::Resize(result, Utf16StringType::Capacity);
463 else if (StringTraits::Size(result) == 0)
464 StringTraits::Resize(result, 255);
465
466 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
467 {
468 cb.PlanPostProcessOutputColumn(
469 [indicator, result]() { StringTraits::PostProcessOutputColumn(result, *indicator); });
470 }
471 else
472 {
473 cb.PlanPostProcessOutputColumn([stmt, column, indicator, result]() {
474 // Now resize the string to the actual length of the data
475 // NB: If the indicator is greater than the buffer size, we have a truncation.
476 if (*indicator == SQL_NULL_DATA)
477 StringTraits::Resize(result, 0);
478 else if (*indicator == SQL_NO_TOTAL)
479 ; // We don't know the size of the data
480 else if (*indicator <= static_cast<SQLLEN>(result->size() * sizeof(char16_t)))
481 result->resize(static_cast<size_t>(*indicator) / sizeof(char16_t));
482 else
483 {
484 auto const totalCharsRequired = static_cast<size_t>(*indicator) / sizeof(char16_t);
485 *indicator += sizeof(char16_t); // Add space to hold the null terminator
486 result->resize(totalCharsRequired);
487 auto const sqlResult = SQLGetData(stmt, column, SQL_C_WCHAR, result->data(), *indicator, indicator);
488 (void) sqlResult;
489 assert(SQL_SUCCEEDED(sqlResult));
490 assert(std::cmp_equal(*indicator, totalCharsRequired * sizeof(char16_t)));
491 }
492 });
493 }
494 return SQLBindCol(stmt,
495 column,
496 CType,
497 (SQLPOINTER) StringTraits::Data(result),
498 (SQLLEN) ((StringTraits::Size(result) + 1) * sizeof(CharType)),
499 indicator);
500 }
501
502 static SQLRETURN GetColumn(SQLHSTMT stmt,
503 SQLUSMALLINT column,
504 Utf16StringType* result,
505 SQLLEN* indicator,
506 SqlDataBinderCallback const& cb) noexcept
507 {
508 return detail::GetColumnUtf16(stmt, column, result, indicator, cb);
509 }
510
511 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Utf16StringType const& value) noexcept
512 {
513 auto u8String = ToUtf8(detail::SqlViewHelper<Utf16StringType>::View(value));
514 return std::string(reinterpret_cast<char const*>(u8String.data()), u8String.size());
515 }
516};
517
518// SqlDataBinder<> specialization for UTF-32 strings
519template <typename Utf32StringType>
520 requires(SqlBasicStringBinderConcept<Utf32StringType, char32_t>
521 || (SqlBasicStringBinderConcept<Utf32StringType, uint32_t>)
522 || (SqlBasicStringBinderConcept<Utf32StringType, wchar_t> && sizeof(wchar_t) == 4))
523struct SqlDataBinder<Utf32StringType>
524{
525 using ValueType = Utf32StringType;
526 using CharType = Utf32StringType::value_type;
527 using StringTraits = SqlBasicStringOperations<Utf32StringType>;
528
529 static constexpr auto ColumnType = StringTraits::ColumnType;
530
531 static SQLRETURN InputParameter(SQLHSTMT stmt,
532 SQLUSMALLINT column,
533 Utf32StringType const& value,
534 SqlDataBinderCallback& cb) noexcept
535 {
536 // Always go via UTF-16 + SQL_C_WCHAR; the driver handles encoding conversion
537 // for the target server.
538 auto u16String = std::make_shared<std::u16string>(ToUtf16(detail::SqlViewHelper<Utf32StringType>::View(value)));
539 cb.PlanPostExecuteCallback([u16String = u16String]() {}); // Keep the string alive
540 auto const* data = u16String->data();
541 auto const charCount = u16String->size();
542 auto const sizeInBytes = u16String->size() * sizeof(char16_t);
543 auto const CType = SQLSMALLINT { SQL_C_WCHAR };
544 auto const sqlType = static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
545
546 SQLLEN* indicator = cb.ProvideInputIndicator();
547 *indicator = static_cast<SQLLEN>(sizeInBytes);
548
549 return SQLBindParameter(stmt,
550 column,
551 SQL_PARAM_INPUT,
552 CType,
553 sqlType,
554 charCount,
555 0,
556 (SQLPOINTER) data,
557 static_cast<SQLLEN>(sizeInBytes),
558 indicator);
559 }
560
561 static SQLRETURN OutputColumn(
562 SQLHSTMT stmt, SQLUSMALLINT column, Utf32StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
563 {
564 return detail::BindOutputColumnNonUtf16Unicode<Utf32StringType>(stmt, column, result, indicator, cb);
565 }
566
567 static SQLRETURN GetColumn(SQLHSTMT stmt,
568 SQLUSMALLINT column,
569 Utf32StringType* result,
570 SQLLEN* indicator,
571 SqlDataBinderCallback const& cb) noexcept
572 {
573 auto u16String = std::u16string {};
574 auto const sqlResult = detail::GetColumnUtf16(stmt, column, &u16String, indicator, cb);
575 if (!SQL_SUCCEEDED(sqlResult))
576 return sqlResult;
577
578 auto const u32String = ToUtf32(u16String);
579 StringTraits::Resize(result, static_cast<SQLLEN>(u32String.size()));
580 std::copy_n((CharType const*) u32String.data(), u32String.size(), StringTraits::Data(result));
581
582 return sqlResult;
583 }
584
585 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Utf32StringType const& value) noexcept
586 {
587 auto u8String = ToUtf8(detail::SqlViewHelper<Utf32StringType>::View(value));
588 return std::string(reinterpret_cast<char const*>(u8String.data()), u8String.size());
589 }
590};
591
592// SqlDataBinder<> specialization for UTF-8 strings
593template <typename Utf8StringType>
594 requires SqlBasicStringBinderConcept<Utf8StringType, char8_t>
595struct SqlDataBinder<Utf8StringType>
596{
597 using ValueType = Utf8StringType;
598 using CharType = char8_t;
599 using StringTraits = SqlBasicStringOperations<Utf8StringType>;
600
601 static constexpr auto ColumnType = StringTraits::ColumnType;
602
603 static SQLRETURN InputParameter(SQLHSTMT stmt,
604 SQLUSMALLINT column,
605 Utf8StringType const& value,
606 SqlDataBinderCallback& cb) noexcept
607 {
608 // Always go via UTF-16 + SQL_C_WCHAR; the driver handles encoding conversion
609 // for the target server.
610 auto u16String = std::make_shared<std::u16string>(ToUtf16(detail::SqlViewHelper<Utf8StringType>::View(value)));
611 cb.PlanPostExecuteCallback([u16String = u16String]() {}); // Keep the string alive
612
613 auto const CType = SQL_C_WCHAR;
614 auto const charCount = u16String->size();
615 auto const byteCount = u16String->size() * sizeof(char16_t);
616 auto const sqlType = static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
617
618 SQLLEN* indicator = cb.ProvideInputIndicator();
619 *indicator = static_cast<SQLLEN>(byteCount);
620
621 return SQLBindParameter(stmt,
622 column,
623 SQL_PARAM_INPUT,
624 CType,
625 sqlType,
626 charCount,
627 0,
628 (SQLPOINTER) u16String->data(),
629 static_cast<SQLLEN>(byteCount),
630 indicator);
631 }
632
633 static SQLRETURN OutputColumn(
634 SQLHSTMT stmt, SQLUSMALLINT column, Utf8StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
635 {
636 return detail::BindOutputColumnNonUtf16Unicode<Utf8StringType>(stmt, column, result, indicator, cb);
637 }
638
639 static SQLRETURN GetColumn(SQLHSTMT stmt,
640 SQLUSMALLINT column,
641 Utf8StringType* result,
642 SQLLEN* indicator,
643 SqlDataBinderCallback const& cb) noexcept
644 {
645 auto u16String = std::u16string {};
646 u16String.resize(result->size());
647 auto const sqlReturn = detail::GetColumnUtf16(stmt, column, &u16String, indicator, cb);
648 if (SQL_SUCCEEDED(sqlReturn))
649 *result = ToUtf8(u16String);
650 return sqlReturn;
651 }
652
653 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Utf8StringType const& value) noexcept
654 {
655 // Pass data as-is
656 return std::string(reinterpret_cast<char const*>(value.data()), value.size());
657 }
658};
659
660} // namespace Lightweight
T ToUtf32(std::u8string_view u8InputString)
LIGHTWEIGHT_API std::u8string ToUtf8(std::u32string_view u32InputString)
std::u16string ToUtf16(std::basic_string_view< T > const u32InputString)