Lightweight 0.20251202.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 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 static 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(stmt, column, CType, bufferStart, static_cast<SQLLEN>(bufferCharsAvailable), indicator);
87 }
88 return sqlResult;
89 }
90
91 template <typename Utf16StringType>
92 SQLRETURN GetColumnUtf16(SQLHSTMT stmt,
93 SQLUSMALLINT column,
94 Utf16StringType* result,
95 SQLLEN* indicator,
96 SqlDataBinderCallback const& /*cb*/) noexcept
97 {
98 if constexpr (requires { Utf16StringType::Capacity; })
99 result->resize(Utf16StringType::Capacity);
100 else if (result->size() == 0)
101 result->resize(255);
102
103 return GetRawColumnArrayData<SQL_C_WCHAR>(stmt, column, result, indicator);
104 }
105
106 template <typename StringType>
107 SQLRETURN BindOutputColumnNonUtf16Unicode(
108 SQLHSTMT stmt, SQLUSMALLINT column, StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
109 {
110 using CharType = StringType::value_type;
111
112 auto u16String = std::make_shared<std::u16string>();
113 if (!result->empty())
114 u16String->resize(result->size());
115 else
116 u16String->resize(255);
117
118 cb.PlanPostProcessOutputColumn([stmt, column, result, indicator, u16String = u16String]() {
119 if (*indicator == SQL_NULL_DATA)
120 u16String->clear();
121 else if (*indicator == SQL_NO_TOTAL)
122 ; // We don't know the size of the data
123 else if (std::cmp_less_equal(*indicator, u16String->size() * sizeof(char16_t)))
124 u16String->resize(static_cast<size_t>(*indicator) / sizeof(char16_t));
125 else
126 {
127 auto const totalCharsRequired = static_cast<size_t>(*indicator) / sizeof(char16_t);
128 *indicator += sizeof(char16_t); // Add space to hold the null terminator
129 u16String->resize(totalCharsRequired);
130 auto const sqlResult = SQLGetData(stmt, column, SQL_C_WCHAR, u16String->data(), *indicator, indicator);
131 (void) sqlResult;
132 assert(SQL_SUCCEEDED(sqlResult));
133 assert(std::cmp_equal(*indicator, totalCharsRequired * sizeof(char16_t)));
134 }
135
136 if constexpr (sizeof(typename StringType::value_type) == 1)
137 *result = ToUtf8(*u16String);
138 else if constexpr (sizeof(typename StringType::value_type) == 4)
139 {
140 // *result = ToUtf32(*u16String);
141 auto const u32String = ToUtf32(*u16String);
142 *result = StringType {
143 (CharType const*) u32String.data(),
144 (CharType const*) u32String.data() + u32String.size(),
145 };
146 }
147 });
148
149 return SQLBindCol(stmt,
150 column,
151 SQL_C_WCHAR,
152 static_cast<SQLPOINTER>(u16String->data()),
153 static_cast<SQLLEN>(u16String->size() * sizeof(char16_t)),
154 indicator);
155 }
156
157} // namespace detail
158
159// SqlDataBinder<> specialization for ANSI character strings
160template <typename AnsiStringType>
161 requires SqlBasicStringBinderConcept<AnsiStringType, char>
162struct SqlDataBinder<AnsiStringType>
163{
164 using ValueType = AnsiStringType;
165 using CharType = char;
166 using StringTraits = SqlBasicStringOperations<AnsiStringType>;
167
168 static constexpr auto ColumnType = StringTraits::ColumnType;
169
170 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
171 SQLUSMALLINT column,
172 AnsiStringType const& value,
173 SqlDataBinderCallback& cb) noexcept
174 {
175 auto const charCount = StringTraits::Size(&value);
176 auto const sqlType = static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_LONGVARCHAR : SQL_VARCHAR);
177
178 SQLLEN* indicator = cb.ProvideInputIndicator();
179 *indicator = static_cast<SQLLEN>(charCount);
180
181 return SQLBindParameter(stmt,
182 column,
183 SQL_PARAM_INPUT,
184 SQL_C_CHAR,
185 sqlType,
186 charCount,
187 0,
188 (SQLPOINTER) StringTraits::Data(&value),
189 0,
190 indicator);
191 }
192
193 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN BatchInputParameter(SQLHSTMT stmt,
194 SQLUSMALLINT column,
195 AnsiStringType const* values,
196 size_t rowCount,
197 SqlDataBinderCallback& cb) noexcept
198 {
199 size_t maxLen = 0;
200 SQLLEN* indicators = cb.ProvideInputIndicators(rowCount);
201 for (size_t i = 0; i < rowCount; ++i)
202 {
203 auto const len = StringTraits::Size(&values[i]);
204 indicators[i] = static_cast<SQLLEN>(len);
205 if (len > maxLen)
206 maxLen = len;
207 }
208
209 auto const sqlType = static_cast<SQLSMALLINT>(maxLen > SqlOptimalMaxColumnSize ? SQL_LONGVARCHAR : SQL_VARCHAR);
210
211 return SQLBindParameter(stmt,
212 column,
213 SQL_PARAM_INPUT,
214 SQL_C_CHAR,
215 sqlType,
216 maxLen,
217 0,
218 (SQLPOINTER) values,
219 sizeof(AnsiStringType),
220 indicators);
221 }
222
223 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
224 SQLHSTMT stmt, SQLUSMALLINT column, AnsiStringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
225 {
226 if constexpr (requires { AnsiStringType::Capacity; })
227 StringTraits::Resize(result, AnsiStringType::Capacity);
228 else if (StringTraits::Size(result) == 0)
229 StringTraits::Resize(result, 255);
230
231 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
232 cb.PlanPostProcessOutputColumn(
233 [indicator, result]() { StringTraits::PostProcessOutputColumn(result, *indicator); });
234 else
235 cb.PlanPostProcessOutputColumn(
236 [stmt, column, indicator, result]() { PostProcessOutputColumn(stmt, column, result, indicator); });
237
238 return SQLBindCol(stmt,
239 column,
240 SQL_C_CHAR,
241 (SQLPOINTER) StringTraits::Data(result),
242 (SQLLEN) StringTraits::Size(result),
243 indicator);
244 }
245
246 static void PostProcessOutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, AnsiStringType* result, SQLLEN* indicator)
247 {
248 // Now resize the string to the actual length of the data
249 // NB: If the indicator is greater than the buffer size, we have a truncation.
250 if (*indicator == SQL_NO_TOTAL)
251 {
252 // We have a truncation and the server does not know how much data is left.
253 StringTraits::Resize(result, static_cast<SQLLEN>(StringTraits::Size(result)) - 1);
254 }
255 else if (*indicator == SQL_NULL_DATA)
256 {
257 // We have a NULL value
258 StringTraits::Resize(result, 0);
259 }
260 else if (*indicator <= static_cast<SQLLEN>(StringTraits::Size(result)))
261 {
262 StringTraits::Resize(result, *indicator);
263 }
264 else
265 {
266 // We have a truncation and the server knows how much data is left.
267 // Extend the buffer and fetch the rest via SQLGetData.
268
269 auto const totalCharsRequired = *indicator;
270 StringTraits::Resize(result, totalCharsRequired + 1);
271 auto const sqlResult =
272 SQLGetData(stmt, column, SQL_C_CHAR, StringTraits::Data(result), totalCharsRequired + 1, indicator);
273 (void) sqlResult;
274 assert(SQL_SUCCEEDED(sqlResult));
275 assert(*indicator == totalCharsRequired);
276 StringTraits::Resize(result, totalCharsRequired);
277 }
278 }
279
280 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
281 static SQLRETURN GetColumn(SQLHSTMT stmt,
282 SQLUSMALLINT column,
283 AnsiStringType* result,
284 SQLLEN* indicator,
285 SqlDataBinderCallback const& /*cb*/) noexcept
286 {
287 if constexpr (requires { AnsiStringType::Capacity; })
288 {
289 StringTraits::Resize(result, AnsiStringType::Capacity);
290 SQLRETURN const rv =
291 SQLGetData(stmt, column, SQL_C_CHAR, StringTraits::Data(result), AnsiStringType::Capacity, indicator);
292 if (rv == SQL_SUCCESS || rv == SQL_NO_DATA)
293 {
294 if (*indicator == SQL_NULL_DATA)
295 StringTraits::Resize(result, 0);
296 else if (*indicator != SQL_NO_TOTAL)
297 StringTraits::Resize(
298 result, static_cast<SQLLEN>((std::min) (AnsiStringType::Capacity, static_cast<size_t>(*indicator))));
299 }
300 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
301 StringTraits::PostProcessOutputColumn(result, *indicator);
302 return rv;
303 }
304 else
305 {
306 StringTraits::Reserve(result, 15);
307 size_t writeIndex = 0;
308 *indicator = 0;
309 while (true)
310 {
311 auto* const bufferStart = StringTraits::Data(result) + writeIndex;
312 size_t const bufferSize = StringTraits::Size(result) - writeIndex;
313 SQLRETURN const rv =
314 SQLGetData(stmt, column, SQL_C_CHAR, bufferStart, static_cast<SQLLEN>(bufferSize), indicator);
315 switch (rv)
316 {
317 case SQL_SUCCESS:
318 case SQL_NO_DATA:
319 // last successive call
320 if (*indicator != SQL_NULL_DATA)
321 {
322 StringTraits::Resize(result, static_cast<SQLLEN>(writeIndex) + *indicator);
323 *indicator = static_cast<SQLLEN>(StringTraits::Size(result));
324 }
325 return SQL_SUCCESS;
326 case SQL_SUCCESS_WITH_INFO: {
327 // more data pending
328 if (*indicator == SQL_NO_TOTAL)
329 {
330 // We have a truncation and the server does not know how much data is left.
331 writeIndex += bufferSize - 1;
332 StringTraits::Resize(result, static_cast<SQLLEN>((2 * writeIndex) + 1));
333 }
334 else if (std::cmp_greater_equal(*indicator, bufferSize))
335 {
336 // We have a truncation and the server knows how much data is left.
337 writeIndex += bufferSize - 1;
338 StringTraits::Resize(result, static_cast<SQLLEN>(writeIndex) + *indicator);
339 }
340 else
341 {
342 // We have no truncation and the server knows how much data is left.
343 StringTraits::Resize(result, static_cast<SQLLEN>(writeIndex) + *indicator - 1);
344 return SQL_SUCCESS;
345 }
346 break;
347 }
348 default:
349 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
350 StringTraits::PostProcessOutputColumn(result, *indicator);
351 return rv;
352 }
353 }
354 }
355 }
356
357 static LIGHTWEIGHT_FORCE_INLINE std::string_view Inspect(AnsiStringType const& value) noexcept
358 {
359 return { StringTraits::Data(&value), StringTraits::Size(&value) };
360 }
361};
362
363// SqlDataBinder<> specialization for UTF-16 strings
364template <typename Utf16StringType>
365 requires(SqlBasicStringBinderConcept<Utf16StringType, char16_t>
366 || (SqlBasicStringBinderConcept<Utf16StringType, unsigned short>)
367 || (SqlBasicStringBinderConcept<Utf16StringType, wchar_t> && sizeof(wchar_t) == 2))
368struct SqlDataBinder<Utf16StringType>
369{
370 using ValueType = Utf16StringType;
371 using CharType = std::remove_cvref_t<decltype(std::declval<Utf16StringType>()[0])>;
372 using StringTraits = SqlBasicStringOperations<Utf16StringType>;
373
374 static constexpr auto ColumnType = StringTraits::ColumnType;
375
376 static constexpr auto CType = SQL_C_WCHAR;
377
378 static SQLRETURN InputParameter(SQLHSTMT stmt,
379 SQLUSMALLINT column,
380 Utf16StringType const& value,
381 SqlDataBinderCallback& cb) noexcept
382 {
383 switch (cb.ServerType())
384 {
385 case SqlServerType::POSTGRESQL: {
386 // PostgreSQL only supports UTF-8 as Unicode encoding
387 auto u8String = std::make_shared<std::u8string>(ToUtf8(detail::SqlViewHelper<Utf16StringType>::View(value)));
388 cb.PlanPostExecuteCallback([u8String = u8String]() {}); // Keep the string alive
389
390 SQLLEN* indicator = cb.ProvideInputIndicator();
391 *indicator = static_cast<SQLLEN>(u8String->size());
392
393 return SQLBindParameter(stmt,
394 column,
395 SQL_PARAM_INPUT,
396 SQL_C_CHAR,
397 SQL_VARCHAR,
398 u8String->size(),
399 0,
400 (SQLPOINTER) u8String->data(),
401 0,
402 indicator);
403 }
404 case SqlServerType::MYSQL:
405 case SqlServerType::SQLITE: // We assume UTF-16 for SQLite
406 case SqlServerType::MICROSOFT_SQL:
407 case SqlServerType::UNKNOWN: {
408 using CharType = StringTraits::CharType;
409 auto const* data = StringTraits::Data(&value);
410 auto const sizeInBytes = StringTraits::Size(&value) * sizeof(CharType);
411 auto const charCount = StringTraits::Size(&value);
412 auto const sqlType =
413 static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
414
415 SQLLEN* indicator = cb.ProvideInputIndicator();
416 *indicator = static_cast<SQLLEN>(sizeInBytes);
417
418 return SQLBindParameter(stmt,
419 column,
420 SQL_PARAM_INPUT,
421 CType,
422 sqlType,
423 charCount,
424 0,
425 (SQLPOINTER) data,
426 static_cast<SQLLEN>(sizeInBytes),
427 indicator);
428 }
429 }
430 std::unreachable();
431 }
432
433 static SQLRETURN OutputColumn(
434 SQLHSTMT stmt, SQLUSMALLINT column, Utf16StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
435 {
436 if constexpr (requires { Utf16StringType::Capacity; })
437 StringTraits::Resize(result, Utf16StringType::Capacity);
438 else if (StringTraits::Size(result) == 0)
439 StringTraits::Resize(result, 255);
440
441 if constexpr (requires { StringTraits::PostProcessOutputColumn(result, *indicator); })
442 {
443 cb.PlanPostProcessOutputColumn(
444 [indicator, result]() { StringTraits::PostProcessOutputColumn(result, *indicator); });
445 }
446 else
447 {
448 cb.PlanPostProcessOutputColumn([stmt, column, indicator, result]() {
449 // Now resize the string to the actual length of the data
450 // NB: If the indicator is greater than the buffer size, we have a truncation.
451 if (*indicator == SQL_NULL_DATA)
452 StringTraits::Resize(result, 0);
453 else if (*indicator == SQL_NO_TOTAL)
454 ; // We don't know the size of the data
455 else if (*indicator <= static_cast<SQLLEN>(result->size() * sizeof(char16_t)))
456 result->resize(static_cast<size_t>(*indicator) / sizeof(char16_t));
457 else
458 {
459 auto const totalCharsRequired = static_cast<size_t>(*indicator) / sizeof(char16_t);
460 *indicator += sizeof(char16_t); // Add space to hold the null terminator
461 result->resize(totalCharsRequired);
462 auto const sqlResult = SQLGetData(stmt, column, SQL_C_WCHAR, result->data(), *indicator, indicator);
463 (void) sqlResult;
464 assert(SQL_SUCCEEDED(sqlResult));
465 assert(std::cmp_equal(*indicator, totalCharsRequired * sizeof(char16_t)));
466 }
467 });
468 }
469 return SQLBindCol(
470 stmt, column, CType, (SQLPOINTER) StringTraits::Data(result), (SQLLEN) StringTraits::Size(result), indicator);
471 }
472
473 static SQLRETURN GetColumn(SQLHSTMT stmt,
474 SQLUSMALLINT column,
475 Utf16StringType* result,
476 SQLLEN* indicator,
477 SqlDataBinderCallback const& cb) noexcept
478 {
479 return detail::GetColumnUtf16(stmt, column, result, indicator, cb);
480 }
481
482 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Utf16StringType const& value) noexcept
483 {
484 auto u8String = ToUtf8(detail::SqlViewHelper<Utf16StringType>::View(value));
485 return std::string(reinterpret_cast<char const*>(u8String.data()), u8String.size());
486 }
487};
488
489// SqlDataBinder<> specialization for UTF-32 strings
490template <typename Utf32StringType>
491 requires(SqlBasicStringBinderConcept<Utf32StringType, char32_t>
492 || (SqlBasicStringBinderConcept<Utf32StringType, uint32_t>)
493 || (SqlBasicStringBinderConcept<Utf32StringType, wchar_t> && sizeof(wchar_t) == 4))
494struct SqlDataBinder<Utf32StringType>
495{
496 using ValueType = Utf32StringType;
497 using CharType = Utf32StringType::value_type;
498 using StringTraits = SqlBasicStringOperations<Utf32StringType>;
499
500 static constexpr auto ColumnType = StringTraits::ColumnType;
501
502 static SQLRETURN InputParameter(SQLHSTMT stmt,
503 SQLUSMALLINT column,
504 Utf32StringType const& value,
505 SqlDataBinderCallback& cb) noexcept
506 {
507 switch (cb.ServerType())
508 {
509 case SqlServerType::POSTGRESQL: {
510 // PostgreSQL only supports UTF-8 as Unicode encoding
511 auto u8String = std::make_shared<std::u8string>(ToUtf8(detail::SqlViewHelper<Utf32StringType>::View(value)));
512 cb.PlanPostExecuteCallback([u8String = u8String]() {}); // Keep the string alive
513
514 SQLLEN* indicator = cb.ProvideInputIndicator();
515 *indicator = static_cast<SQLLEN>(u8String->size());
516
517 return SQLBindParameter(stmt,
518 column,
519 SQL_PARAM_INPUT,
520 SQL_C_CHAR,
521 SQL_VARCHAR,
522 u8String->size(),
523 0,
524 (SQLPOINTER) u8String->data(),
525 0,
526 indicator);
527 }
528 case SqlServerType::MYSQL:
529 case SqlServerType::SQLITE: // We assume UTF-16 for SQLite
530 case SqlServerType::MICROSOFT_SQL:
531 case SqlServerType::UNKNOWN: {
532 auto u16String =
533 std::make_shared<std::u16string>(ToUtf16(detail::SqlViewHelper<Utf32StringType>::View(value)));
534 cb.PlanPostExecuteCallback([u8String = u16String]() {}); // Keep the string alive
535 auto const* data = u16String->data();
536 auto const charCount = u16String->size();
537 auto const sizeInBytes = u16String->size() * sizeof(char16_t);
538 auto const CType = SQLSMALLINT { SQL_C_WCHAR };
539 auto const sqlType =
540 static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
541
542 SQLLEN* indicator = cb.ProvideInputIndicator();
543 *indicator = static_cast<SQLLEN>(sizeInBytes);
544
545 return SQLBindParameter(stmt,
546 column,
547 SQL_PARAM_INPUT,
548 CType,
549 sqlType,
550 charCount,
551 0,
552 (SQLPOINTER) data,
553 static_cast<SQLLEN>(sizeInBytes),
554 indicator);
555 }
556 }
557 std::unreachable();
558 }
559
560 static SQLRETURN OutputColumn(
561 SQLHSTMT stmt, SQLUSMALLINT column, Utf32StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
562 {
563 return detail::BindOutputColumnNonUtf16Unicode<Utf32StringType>(stmt, column, result, indicator, cb);
564 }
565
566 static SQLRETURN GetColumn(SQLHSTMT stmt,
567 SQLUSMALLINT column,
568 Utf32StringType* result,
569 SQLLEN* indicator,
570 SqlDataBinderCallback const& cb) noexcept
571 {
572 auto u16String = std::u16string {};
573 auto const sqlResult = detail::GetColumnUtf16(stmt, column, &u16String, indicator, cb);
574 if (!SQL_SUCCEEDED(sqlResult))
575 return sqlResult;
576
577 auto const u32String = ToUtf32(u16String);
578 StringTraits::Resize(result, static_cast<SQLLEN>(u32String.size()));
579 std::copy_n((CharType const*) u32String.data(), u32String.size(), StringTraits::Data(result));
580
581 return sqlResult;
582 }
583
584 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Utf32StringType const& value) noexcept
585 {
586 auto u8String = ToUtf8(detail::SqlViewHelper<Utf32StringType>::View(value));
587 return std::string(reinterpret_cast<char const*>(u8String.data()), u8String.size());
588 }
589};
590
591// SqlDataBinder<> specialization for UTF-8 strings
592template <typename Utf8StringType>
593 requires SqlBasicStringBinderConcept<Utf8StringType, char8_t>
594struct SqlDataBinder<Utf8StringType>
595{
596 using ValueType = Utf8StringType;
597 using CharType = char8_t;
598 using StringTraits = SqlBasicStringOperations<Utf8StringType>;
599
600 static constexpr auto ColumnType = StringTraits::ColumnType;
601
602 static SQLRETURN InputParameter(SQLHSTMT stmt,
603 SQLUSMALLINT column,
604 Utf8StringType const& value,
605 SqlDataBinderCallback& cb) noexcept
606 {
607 switch (cb.ServerType())
608 {
609 case SqlServerType::POSTGRESQL: {
610 // PostgreSQL only supports UTF-8 as Unicode encoding
611 auto const len = value.size();
612 SQLLEN* indicator = cb.ProvideInputIndicator();
613 *indicator = static_cast<SQLLEN>(len);
614
615 return SQLBindParameter(
616 stmt, column, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, len, 0, (SQLPOINTER) value.data(), 0, indicator);
617 }
618 case SqlServerType::MYSQL:
619 case SqlServerType::SQLITE: // We assume UTF-16 for SQLite
620 case SqlServerType::MICROSOFT_SQL:
621 case SqlServerType::UNKNOWN: {
622 auto u16String =
623 std::make_shared<std::u16string>(ToUtf16(detail::SqlViewHelper<Utf8StringType>::View(value)));
624 cb.PlanPostExecuteCallback([u16String = u16String]() {}); // Keep the string alive
625
626 auto const CType = SQL_C_WCHAR;
627 auto const charCount = u16String->size();
628 auto const byteCount = u16String->size() * sizeof(char16_t);
629 auto const sqlType =
630 static_cast<SQLSMALLINT>(charCount > SqlOptimalMaxColumnSize ? SQL_WLONGVARCHAR : SQL_WVARCHAR);
631
632 SQLLEN* indicator = cb.ProvideInputIndicator();
633 *indicator = static_cast<SQLLEN>(byteCount);
634
635 return SQLBindParameter(stmt,
636 column,
637 SQL_PARAM_INPUT,
638 CType,
639 sqlType,
640 charCount,
641 0,
642 (SQLPOINTER) u16String->data(),
643 static_cast<SQLLEN>(byteCount),
644 indicator);
645 }
646 }
647 std::unreachable();
648 }
649
650 static SQLRETURN OutputColumn(
651 SQLHSTMT stmt, SQLUSMALLINT column, Utf8StringType* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
652 {
653 return detail::BindOutputColumnNonUtf16Unicode<Utf8StringType>(stmt, column, result, indicator, cb);
654 }
655
656 static SQLRETURN GetColumn(SQLHSTMT stmt,
657 SQLUSMALLINT column,
658 Utf8StringType* result,
659 SQLLEN* indicator,
660 SqlDataBinderCallback const& cb) noexcept
661 {
662 auto u16String = std::u16string {};
663 u16String.resize(result->size());
664 auto const sqlReturn = detail::GetColumnUtf16(stmt, column, &u16String, indicator, cb);
665 if (SQL_SUCCEEDED(sqlReturn))
666 *result = ToUtf8(u16String);
667 return sqlReturn;
668 }
669
670 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(Utf8StringType const& value) noexcept
671 {
672 // Pass data as-is
673 return std::string(reinterpret_cast<char const*>(value.data()), value.size());
674 }
675};
676
677} // 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)