Lightweight 0.20261105.1
Loading...
Searching...
No Matches
SqlOdbcWide.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5// <sql.h> below needs a few symbols resolved first — see SqlOdbcPrelude.hpp's header comment.
6#include "DataBinder/UnicodeConverter.hpp"
7#include "SqlOdbcPrelude.hpp"
8
9#include <concepts>
10#include <optional>
11#include <string>
12#include <string_view>
13#include <tuple>
14#include <type_traits>
15#include <utility>
16
17#include <sql.h>
18
19namespace Lightweight::detail
20{
21
22// ODBC W variants assume `SQLWCHAR` is layout-compatible with `char16_t` (true on
23// Windows where it's `wchar_t`, and on Linux unixODBC where it's `unsigned short` —
24// both 16-bit). Anything else and the reinterpret_casts below would silently corrupt.
25static_assert(sizeof(SQLWCHAR) == sizeof(char16_t), "ODBC W variants require a 16-bit code unit; SQLWCHAR shape mismatch");
26
27/// @brief Converts a UTF-8 string view into a `std::u16string` for ODBC W variants.
28/// Use this rather than `ToUtf16(std::string const&)` from `UnicodeConverter.hpp`:
29/// that overload treats its input as the platform narrow encoding (CP_ACP on
30/// Windows), which silently corrupts UTF-8 bytes >= 0x80.
31inline std::u16string OdbcUtf8ToUtf16(std::string_view utf8)
32{
33 return ToUtf16(std::u8string_view { reinterpret_cast<char8_t const*>(utf8.data()), utf8.size() });
34}
35
36/// @brief Reinterprets a `char16_t*` buffer as the `SQLWCHAR*` ODBC W variants want.
37/// Note: ODBC W input-string parameters are non-const (a legacy API quirk; the
38/// driver does not mutate them), so callers must hold their `std::u16string` non-const.
39inline SQLWCHAR* AsSqlWChar(char16_t* p) noexcept
40{
41 return reinterpret_cast<SQLWCHAR*>(p);
42}
43
44/// @brief Holds a UTF-16 buffer mapped from a UTF-8 input plus the (pointer, length)
45/// pair the ODBC W introspection / connect / prepare calls expect. Empty input
46/// produces a null pointer / zero length, mirroring the ODBC idiom of "pass nullptr
47/// when the caller does not want to filter on this field". The buffer's lifetime
48/// equals the `OdbcWideArg`'s, satisfying SQL_NTS for the duration of one call.
49struct OdbcWideArg
50{
51 std::u16string buffer;
52
53 explicit OdbcWideArg(std::string_view utf8):
54 buffer(utf8.empty() ? std::u16string {} : OdbcUtf8ToUtf16(utf8))
55 {
56 }
57
58 explicit OdbcWideArg(std::u16string utf16) noexcept:
59 buffer(std::move(utf16))
60 {
61 }
62
63 [[nodiscard]] SQLWCHAR* data() noexcept
64 {
65 return buffer.empty() ? nullptr : AsSqlWChar(buffer.data());
66 }
67
68 [[nodiscard]] SQLSMALLINT length() const noexcept
69 {
70 return static_cast<SQLSMALLINT>(buffer.size());
71 }
72};
73
74/// @brief Holds a UTF-8 string as the (pointer, length) pair an ODBC ANSI entry point expects, for a
75/// driver that takes text only through those (see SqlConnection::AcceptsWideQueryText()). The bytes are
76/// passed on unchanged; empty input produces a null pointer / zero length, as with @ref OdbcWideArg.
77struct OdbcNarrowArg
78{
79 std::string buffer;
80
81 explicit OdbcNarrowArg(std::string_view utf8):
82 buffer(utf8)
83 {
84 }
85
86 [[nodiscard]] SQLCHAR* data() noexcept
87 {
88 return buffer.empty() ? nullptr : reinterpret_cast<SQLCHAR*>(buffer.data());
89 }
90
91 [[nodiscard]] SQLSMALLINT length() const noexcept
92 {
93 return static_cast<SQLSMALLINT>(buffer.size());
94 }
95};
96
97/// @brief Reads the text column @p column of @p cursor as UTF-8.
98///
99/// Catalog and metadata text is fetched as UTF-16 and converted, because a narrow read hands back
100/// whatever the driver chooses for SQL_C_CHAR: the Microsoft SQL Server driver on Windows, for one,
101/// returns a non-ASCII identifier in the client code page rather than UTF-8.
102/// @param cursor A result cursor (or statement) positioned on a row.
103/// @param column The 1-based column.
104/// @return The text, or nothing for NULL.
105template <typename Cursor>
106std::optional<std::string> GetNullableTextColumn(Cursor const& cursor, SQLUSMALLINT column)
107{
108 auto const text = cursor.template GetNullableColumn<std::u16string>(column);
109 if (!text)
110 return std::nullopt;
111 auto const utf8 = ToUtf8(std::u16string_view { *text });
112 return std::string { reinterpret_cast<char const*>(utf8.data()), utf8.size() };
113}
114
115/// @brief Reads the text column @p column of @p cursor as UTF-8, a NULL as the empty string.
116template <typename Cursor>
117std::string GetTextColumn(Cursor const& cursor, SQLUSMALLINT column)
118{
119 return GetNullableTextColumn(cursor, column).value_or(std::string {});
120}
121
122/// @brief Records that @p handle belongs to the connection @p connection, whose driver takes text only
123/// through the ODBC ANSI entry points, so that SqlErrorInfo::FromHandle() reads the handle's diagnostics
124/// through those as well. Best effort: a handle that cannot be recorded is read wide.
125/// @param handle A connection handle, or a statement handle allocated on @p connection.
126/// @param connection The connection handle; @p handle itself for a connection.
127void RegisterAnsiOnlyHandle(SQLHANDLE handle, SQLHDBC connection) noexcept;
128
129/// @brief Forgets @p handle, and for a connection also every handle recorded under it, before the
130/// handle is freed.
131void UnregisterAnsiOnlyHandle(SQLHANDLE handle) noexcept;
132
133/// @brief Tells whether @p handle was recorded by RegisterAnsiOnlyHandle().
134[[nodiscard]] bool IsAnsiOnlyHandle(SQLHANDLE handle) noexcept;
135
136/// @brief Calls @p call with one argument per string of @p texts: an @ref OdbcWideArg each when @p wide,
137/// else an @ref OdbcNarrowArg each, so one call site serves both the W and the ANSI entry point.
138/// @return What @p call returned.
139template <typename Call, typename... Texts>
140decltype(auto) CallWithOdbcText(bool wide, Call const& call, Texts const&... texts)
141{
142 if (wide)
143 {
144 auto args = std::tuple { OdbcWideArg { std::string_view { texts } }... };
145 return std::apply(call, args);
146 }
147 auto args = std::tuple { OdbcNarrowArg { std::string_view { texts } }... };
148 return std::apply(call, args);
149}
150
151/// @brief Selects @p wideFunction for an @ref OdbcWideArg argument and @p ansiFunction for an
152/// @ref OdbcNarrowArg one, to pair with @ref CallWithOdbcText.
153template <typename Arg, typename WideFunction, typename AnsiFunction>
154[[nodiscard]] constexpr auto OdbcEntryPoint(WideFunction* wideFunction, AnsiFunction* ansiFunction) noexcept
155{
156 if constexpr (std::same_as<std::remove_cvref_t<Arg>, OdbcWideArg>)
157 return wideFunction;
158 else
159 return ansiFunction;
160}
161
162} // namespace Lightweight::detail
LIGHTWEIGHT_API std::u8string ToUtf8(std::u32string_view u32InputString)
std::u16string ToUtf16(std::basic_string_view< T > const u32InputString)