Lightweight 0.20261002.0
Loading...
Searching...
No Matches
SqlGuid.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlColumnTypeDefinitions.hpp"
6#include "Core.hpp"
7
8#include <algorithm>
9#include <bit>
10#include <charconv>
11#include <format>
12#include <optional>
13#include <span>
14#include <string>
15
16namespace Lightweight
17{
18
19/// Represents a GUID (Globally Unique Identifier).
20///
21/// @ingroup DataTypes
22struct LIGHTWEIGHT_API SqlGuid
23{
24 /// The raw GUID data bytes.
25 uint8_t data[16] {};
26
27 /// Creates a new non-empty GUID.
28 static SqlGuid Create() noexcept;
29
30 /// Parses a GUID from a string.
31 static std::optional<SqlGuid> TryParse(std::string_view const& text) noexcept;
32
33 /// Parses a GUID from a string. Use with caution and always prefer TryParse at all cost.
34 static SqlGuid constexpr UnsafeParse(std::string_view const& text) noexcept;
35
36 /// Three-way comparison operator.
37 constexpr std::weak_ordering operator<=>(SqlGuid const& other) const noexcept = default;
38
39 /// Equality comparison operator.
40 constexpr bool operator==(SqlGuid const& other) const noexcept
41 {
42 return (*this <=> other) == std::weak_ordering::equivalent;
43 }
44
45 /// Inequality comparison operator.
46 constexpr bool operator!=(SqlGuid const& other) const noexcept
47 {
48 return !(*this == other);
49 }
50
51 /// Tests if the GUID is non-empty.
52 constexpr explicit operator bool() const noexcept
53 {
54 return *this != SqlGuid {};
55 }
56
57 /// Tests if the GUID is empty.
58 constexpr bool operator!() const noexcept
59 {
60 return !static_cast<bool>(*this);
61 }
62};
63
64constexpr SqlGuid SqlGuid::UnsafeParse(std::string_view const& text) noexcept
65{
66 SqlGuid guid {};
67
68 // UUID format: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
69 // M is the version and N is the variant
70
71 // Check for length
72 if (text.size() != 36)
73 return { "\x01" };
74
75 // Check for dashes
76 if (text[8] != '-' || text[13] != '-' || text[18] != '-' || text[23] != '-')
77 return { "\x02" };
78
79 // Version must be 1, 2, 3, 4, or 5
80 auto const version = text[14];
81 if (!('1' <= version && version <= '5'))
82 return { "\x03" };
83
84 // Variant nibble at position 19 must be a valid hex digit
85 // We accept all variants (RFC 4122: 8-B, Microsoft: C-D, etc.)
86 auto const variant = text[19];
87 auto const isHexDigit =
88 (variant >= '0' && variant <= '9') || (variant >= 'A' && variant <= 'F') || (variant >= 'a' && variant <= 'f');
89 if (!isHexDigit)
90 return { "\x04" };
91
92 // clang-format off
93 size_t i = 0;
94 for (auto const index: { 0, 2, 4, 6,
95 9, 11,
96 14, 16,
97 19, 21,
98 24, 26, 28, 30, 32, 34 })
99 {
100 if (std::from_chars(text.data() + index, text.data() + index + 2, guid.data[i], 16).ec != std::errc())
101 return { "\x05" };
102 i++;
103 }
104 // clang-format on
105
106 return guid;
107}
108
109} // namespace Lightweight
110
111template <>
112struct std::formatter<Lightweight::SqlGuid>: std::formatter<std::string>
113{
114 LIGHTWEIGHT_FORCE_INLINE auto format(Lightweight::SqlGuid const& guid, format_context& ctx) const
115 -> format_context::iterator
116 {
117 // clang-format off
118 return formatter<std::string>::format(std::format(
119 "{:08X}-{:04X}-{:04X}-{:04X}-{:012X}",
120 (uint32_t) guid.data[3] | (uint32_t) guid.data[2] << 8 |
121 (uint32_t) guid.data[1] << 16 | (uint32_t) guid.data[0] << 24,
122 (uint16_t) guid.data[5] | (uint16_t) guid.data[4] << 8,
123 (uint16_t) guid.data[7] | (uint16_t) guid.data[6] << 8,
124 (uint16_t) guid.data[9] | (uint16_t) guid.data[8] << 8,
125 (uint64_t) guid.data[15] | (uint64_t) guid.data[14] << 8 |
126 (uint64_t) guid.data[13] << 16 | (uint64_t) guid.data[12] << 24 |
127 (uint64_t) guid.data[11] << 32 | (uint64_t) guid.data[10] << 40
128 ),
129 ctx
130 );
131 // clang-format on
132 }
133};
134
135namespace Lightweight
136{
137
138inline LIGHTWEIGHT_FORCE_INLINE std::string to_string(SqlGuid const& guid)
139{
140 return std::format("{}", guid);
141}
142
143namespace detail
144{
145 /// Swaps `data[0..7]` between `SqlGuid`'s canonical byte order (the order `TryParse()`,
146 /// `UnsafeParse()`, and the `std::formatter` specialization agree on: `data[i]` is the i-th
147 /// hex-pair of the textual representation, left to right) and the physical `SQL_C_GUID` /
148 /// Win32 `GUID` wire layout that ODBC drivers expect for `Data1`/`Data2`/`Data3` (those three
149 /// fields are transmitted in native/little-endian byte order, not textual order). `data[8..15]`
150 /// (`Data4`) is a plain byte array in both representations and is left untouched.
151 ///
152 /// Self-inverse: applying this twice restores the original bytes. Every call site that binds
153 /// or fetches raw `SQL_C_GUID` bytes (bypassing `SqlDataBinder<SqlGuid>`'s own conversion) must
154 /// apply this exactly once when crossing that boundary, in either direction.
155 ///
156 /// @param data The 16-byte GUID payload to convert in place.
157 inline void SwapGuidWireByteOrder(std::span<uint8_t, 16> data) noexcept
158 {
159 // The reversal below undoes exactly the byte-order change a little-endian host applies
160 // when it stores Data1/Data2/Data3 as native integers; on a big-endian host those fields
161 // are already in textual order and reversing them would corrupt the GUID instead. Assume
162 // little-endian, matching the project-wide precedent in SqlNumeric.hpp.
163 static_assert(std::endian::native == std::endian::little);
164
165 // Data1 (4 bytes), Data2 (2) and Data3 (2) differ between the two representations;
166 // Data4 (the trailing 8) is a plain byte array in both and is left untouched.
167 std::ranges::reverse(data.first<4>());
168 std::ranges::reverse(data.subspan<4, 2>());
169 std::ranges::reverse(data.subspan<6, 2>());
170 }
171} // namespace detail
172
173template <>
174struct LIGHTWEIGHT_API SqlDataBinder<SqlGuid>
175{
176 static constexpr auto ColumnType = SqlColumnTypeDefinitions::Guid {};
177
178 static SQLRETURN InputParameter(SQLHSTMT stmt,
179 SQLUSMALLINT column,
180 SqlGuid const& value,
181 SqlDataBinderCallback& cb) noexcept;
182
183 static SQLRETURN OutputColumn(
184 SQLHSTMT stmt, SQLUSMALLINT column, SqlGuid* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept;
185
186 static SQLRETURN GetColumn(
187 SQLHSTMT stmt, SQLUSMALLINT column, SqlGuid* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept;
188
189 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(SqlGuid const& value) noexcept
190 {
191 return std::format("{}", value);
192 }
193};
194
195} // namespace Lightweight
constexpr bool operator!=(SqlGuid const &other) const noexcept
Inequality comparison operator.
Definition SqlGuid.hpp:46
static SqlGuid constexpr UnsafeParse(std::string_view const &text) noexcept
Parses a GUID from a string. Use with caution and always prefer TryParse at all cost.
Definition SqlGuid.hpp:64
constexpr bool operator!() const noexcept
Tests if the GUID is empty.
Definition SqlGuid.hpp:58
static SqlGuid Create() noexcept
Creates a new non-empty GUID.