Lightweight 0.20260617.0
Loading...
Searching...
No Matches
StdOptional.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Core.hpp"
6#include "SqlNullValue.hpp"
7
8#include <cstddef>
9#include <memory>
10#include <optional>
11#include <ranges>
12#include <string>
13
14namespace Lightweight
15{
16
17namespace detail
18{
19 /// Whether @p T's binder exposes an optional-aware row-wise batch binder
20 /// (@c BatchRowWiseInputParameterOptional). True for length-bearing inline inner types
21 /// (fixed-capacity strings), which need a per-row length indicator; false for plain fixed-width inner
22 /// types (primitives, date/time), which carry only a NULL indicator and bind via
23 /// @c BatchInputParameter.
24 template <typename T>
25 concept SqlHasOptionalRowWiseBatchBinder = requires(
26 SQLHSTMT stmt, SQLUSMALLINT column, std::optional<T> const* elem0, std::size_t rowCount, SqlDataBinderCallback& cb) {
27 SqlDataBinder<T>::BatchRowWiseInputParameterOptional(stmt, column, elem0, rowCount, rowCount, cb);
28 };
29} // namespace detail
30
31template <typename T>
32struct SqlDataBinder<std::optional<T>>
33{
34 using OptionalValue = std::optional<T>;
35
36 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
37
38 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
39 SQLUSMALLINT column,
40 OptionalValue const& value,
41 SqlDataBinderCallback& cb) noexcept
42 {
43 if (value.has_value())
44 return SqlDataBinder<T>::InputParameter(stmt, column, *value, cb);
45 else
46 return SqlDataBinder<SqlNullType>::InputParameter(stmt, column, SqlNullValue, cb);
47 }
48
49 /// Binds an optional column of a native row-wise batch zero-copy, supplying per-row NULL-ness via a
50 /// temporary row-strided indicator buffer.
51 ///
52 /// Dispatches on the inner type @p T:
53 /// - Length-bearing inline types (fixed-capacity strings): delegates to @p T's binder, which fills
54 /// the per-row length-or-@c SQL_NULL_DATA indicator and binds the inline character buffers.
55 /// - Plain fixed-width types (primitives, date/time): the contained value of each row is bound in
56 /// place (no copy); the driver strides by the statement's @c SQL_ATTR_PARAM_BIND_TYPE (== @p
57 /// rowStride). For rows whose optional is disengaged the indicator is @c SQL_NULL_DATA so the
58 /// (indeterminate) value bytes are ignored — the value storage is never dereferenced as a @c T,
59 /// only its address is taken.
60 ///
61 /// @note PRE (guaranteed by the caller): the statement is in row-wise binding mode with
62 /// @c SQL_ATTR_PARAM_BIND_TYPE == @p rowStride, and @c rowStride % alignof(SQLLEN) == 0 so the
63 /// indicator slots at @c i*rowStride stay aligned and non-overlapping.
64 ///
65 /// @param stmt The ODBC statement handle.
66 /// @param column The 1-based parameter column index.
67 /// @param elem0 Address of the @c std::optional<T> within row 0 of the contiguous record array.
68 /// @param rowStride The row-wise bind stride (== sizeof of the row element).
69 /// @param rowCount The number of rows in the batch.
70 /// @param cb The data binder callback (provides the temporary indicator buffer).
71 /// @return The ODBC return code of the underlying bind.
72 static SQLRETURN BatchRowWiseInputParameter(SQLHSTMT stmt,
73 SQLUSMALLINT column,
74 OptionalValue const* elem0,
75 std::size_t rowStride,
76 std::size_t rowCount,
77 SqlDataBinderCallback& cb) noexcept
78 {
79 if constexpr (detail::SqlHasOptionalRowWiseBatchBinder<T>)
80 {
81 // Length-bearing inline inner type (fixed-capacity string): its binder owns the
82 // NULL-or-length indicator fill and the zero-copy bind.
83 return SqlDataBinder<T>::BatchRowWiseInputParameterOptional(stmt, column, elem0, rowStride, rowCount, cb);
84 }
85 else
86 {
87 // Offset of the contained value within std::optional<T> (see detail::OptionalValueOffset).
88 auto const valueOffset = detail::OptionalValueOffset<T>();
89
90 // Row-strided indicator buffer: ODBC reads the indicator for row i at base + i*rowStride. The
91 // optionals themselves are embedded in the row structs, so they are also addressed at
92 // sourceBytes + i*rowStride (NOT elem0[i], which would stride by sizeof(std::optional<T>)).
93 auto const* sourceBytes = reinterpret_cast<std::byte const*>(elem0);
94 auto* const indicatorBytes = cb.ProvideBatchStagingBuffer(((rowCount - 1) * rowStride) + sizeof(SQLLEN));
95 for (auto const i: std::views::iota(std::size_t { 0 }, rowCount))
96 {
97 // The optional and its indicator slot for row i both live at offset i*rowStride (row-wise).
98 auto const& optional = *reinterpret_cast<OptionalValue const*>(sourceBytes + (i * rowStride));
99 auto* const indicatorSlot = reinterpret_cast<SQLLEN*>(indicatorBytes + (i * rowStride));
100 *indicatorSlot = optional.has_value() ? SQLLEN { 0 } : SQLLEN { SQL_NULL_DATA };
101 }
102
103 // Zero-copy value bind: point at row 0's contained value; T's batch binder issues the
104 // SQLBindParameter (taking only the address, never forming a T& to possibly-disengaged storage).
105 auto const* valueBase = reinterpret_cast<T const*>(reinterpret_cast<std::byte const*>(elem0) + valueOffset);
106 return SqlDataBinder<T>::BatchInputParameter(
107 stmt, column, valueBase, rowCount, cb, reinterpret_cast<SQLLEN*>(indicatorBytes));
108 }
109 }
110
111 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
112 SQLHSTMT stmt, SQLUSMALLINT column, OptionalValue* result, SQLLEN* indicator, SqlDataBinderCallback& cb) noexcept
113 {
114 if (!result)
115 return SQL_ERROR;
116
117 auto const sqlReturn = SqlDataBinder<T>::OutputColumn(stmt, column, &result->emplace(), indicator, cb);
118 cb.PlanPostProcessOutputColumn([result, indicator]() {
119 if (indicator && *indicator == SQL_NULL_DATA)
120 *result = std::nullopt;
121 });
122 return sqlReturn;
123 }
124
125 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
126 SQLUSMALLINT column,
127 OptionalValue* result,
128 SQLLEN* indicator,
129 SqlDataBinderCallback const& cb) noexcept
130 {
131 auto const sqlReturn = SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
132 if (indicator && *indicator == SQL_NULL_DATA)
133 *result = std::nullopt;
134 return sqlReturn;
135 }
136
137 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(OptionalValue const& value) noexcept
138 {
139 if (!value)
140 return "NULL";
141 else
142 return std::string(SqlDataBinder<T>::Inspect(*value));
143 }
144};
145
146} // namespace Lightweight
constexpr auto SqlNullValue