SQLon test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 85.0% 198 / 0 / 233
Functions: 89.2% 83 / 1 / 94
Branches: 66.0% 33 / 0 / 50

include/sqlon/parameter.h
Line Branch Exec Source
1 /******************************************************************************
2 **
3 ** Copyright (C) 2026 Ivan Pinezhaninov <ivan.pinezhaninov@gmail.com>
4 **
5 ** This file is part of the SQLon - which can be found at
6 ** https://github.com/IvanPinezhaninov/sqlon/.
7 **
8 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
12 ** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13 ** OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
14 ** THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 **
16 ******************************************************************************/
17
18 #ifndef SQLON_PARAMETER_H
19 #define SQLON_PARAMETER_H
20
21 #include <sqlon/error.h>
22 #include <sqlon/export.h>
23
24 #include <cstddef>
25 #include <cstdint>
26 #include <functional>
27 #include <memory>
28 #include <optional>
29 #include <string>
30 #include <type_traits>
31 #include <utility>
32 #include <variant>
33 #include <vector>
34
35 namespace sqlon {
36
37 /** @brief Owned binary parameter bytes. */
38 using binary = std::vector<std::byte>;
39
40 namespace detail {
41
42 template<typename T, typename = void>
43 struct is_equality_comparable : std::false_type {};
44
45 template<typename T>
46 struct is_equality_comparable<T, std::void_t<decltype(std::declval<const T&>() == std::declval<const T&>())>>
47 : std::is_convertible<decltype(std::declval<const T&>() == std::declval<const T&>()), bool> {};
48
49 template<typename T>
50 inline constexpr unsigned char parameter_type_token{};
51
52 template<typename T>
53 42 const void* parameter_type_id() noexcept
54 {
55 42 return &parameter_type_token<std::decay_t<T>>;
56 }
57
58 } // namespace detail
59
60 /** @brief Owned type-erased application-defined parameter value. */
61 class custom_parameter_value final {
62 public:
63 /** @brief Stores an application-defined value without converting it. */
64 template<typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, custom_parameter_value>, int> = 0>
65 13 explicit custom_parameter_value(T&& value)
66
6/6
sqlon::custom_parameter_value::custom_parameter_value<ClientUuid const&, 0>(ClientUuid const&):
✓ Branch 2 taken 4 times.
sqlon::custom_parameter_value::custom_parameter_value<PreparedDate, 0>(PreparedDate&&):
✓ Branch 2 taken 3 times.
sqlon::custom_parameter_value::custom_parameter_value<PreparedUuid const&, 0>(PreparedUuid const&):
✓ Branch 2 taken 1 time.
sqlon::custom_parameter_value::custom_parameter_value<PreparedUuid, 0>(PreparedUuid&&):
✓ Branch 2 taken 2 times.
sqlon::custom_parameter_value::custom_parameter_value<UnsupportedParameter, 0>(UnsupportedParameter&&):
✓ Branch 2 taken 2 times.
sqlon::custom_parameter_value::custom_parameter_value<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, 0>(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&):
✓ Branch 2 taken 1 time.
13 : m_holder(std::make_shared<holder<std::decay_t<T>>>(std::forward<T>(value)))
67 13 {}
68
69 /** @brief Returns the stored value when its application type is T. */
70 template<typename T>
71 14 const std::decay_t<T>* get_if() const noexcept
72 {
73 using value_type = std::decay_t<T>;
74
6/10
std::decay<ClientUuid>::type const* sqlon::custom_parameter_value::get_if<ClientUuid>() const:
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 4 times.
std::decay<PreparedDate>::type const* sqlon::custom_parameter_value::get_if<PreparedDate>() const:
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
std::decay<PreparedUuid>::type const* sqlon::custom_parameter_value::get_if<PreparedUuid>() const:
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 4 times.
std::decay<long>::type const* sqlon::custom_parameter_value::get_if<long>() const:
✗ Branch 3 not taken.
✗ Branch 4 not taken.
std::decay<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::type const* sqlon::custom_parameter_value::get_if<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >() const:
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
14 if (m_holder->type_id() != detail::parameter_type_id<value_type>()) return nullptr;
75 12 return static_cast<const value_type*>(m_holder->address());
76 }
77
78 /** @brief Compares values when their application type supports equality. */
79 4 friend bool operator==(const custom_parameter_value& left, const custom_parameter_value& right)
80 {
81 4 return left.m_holder->equals(*right.m_holder);
82 }
83
84 /** @brief Compares values when their application type supports equality. */
85 3 friend bool operator!=(const custom_parameter_value& left, const custom_parameter_value& right)
86 {
87 3 return !(left == right);
88 }
89
90 private:
91 class holder_base {
92 public:
93 13 virtual ~holder_base() = default;
94 virtual const void* type_id() const noexcept = 0;
95 virtual const void* address() const noexcept = 0;
96 virtual bool equals(const holder_base& other) const = 0;
97 };
98
99 template<typename T>
100 class holder final : public holder_base {
101 public:
102 template<typename U>
103 13 explicit holder(U&& value)
104
1/1
✓ Branch 3 taken 1 time.
13 : m_value(std::forward<U>(value))
105 13 {}
106
107 18 const void* type_id() const noexcept override
108 {
109 18 return detail::parameter_type_id<T>();
110 }
111
112 13 const void* address() const noexcept override
113 {
114 13 return &m_value;
115 }
116
117 4 bool equals(const holder_base& other) const override
118 {
119
3/10
sqlon::custom_parameter_value::holder<ClientUuid>::equals(sqlon::custom_parameter_value::holder_base const&) const:
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
sqlon::custom_parameter_value::holder<PreparedDate>::equals(sqlon::custom_parameter_value::holder_base const&) const:
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
sqlon::custom_parameter_value::holder<PreparedUuid>::equals(sqlon::custom_parameter_value::holder_base const&) const:
✗ Branch 2 not taken.
✗ Branch 3 not taken.
sqlon::custom_parameter_value::holder<UnsupportedParameter>::equals(sqlon::custom_parameter_value::holder_base const&) const:
✗ Branch 2 not taken.
✗ Branch 3 not taken.
sqlon::custom_parameter_value::holder<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::equals(sqlon::custom_parameter_value::holder_base const&) const:
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (other.type_id() != detail::parameter_type_id<T>()) return false;
120 if constexpr (detail::is_equality_comparable<T>::value)
121 1 return m_value == *static_cast<const T*>(other.address());
122 else
123 2 return this == &other;
124 }
125
126 private:
127 T m_value;
128 };
129
130 std::shared_ptr<const holder_base> m_holder;
131 };
132
133 /** @brief Storage for supported SQL parameter values. */
134 using parameter_storage = std::variant<std::nullptr_t, bool, std::int64_t, std::uint64_t, double, std::string, binary,
135 custom_parameter_value>;
136
137 /**
138 * @brief Application-defined parameter types accepted by a parameter visitor.
139 * @tparam Types Concrete application value types supported by the visitor.
140 */
141 template<typename... Types>
142 struct parameter_types final {};
143
144 namespace detail {
145
146 template<typename T, typename = void>
147 struct has_custom_parameter_types : std::false_type {};
148
149 template<typename T>
150 struct has_custom_parameter_types<T, std::void_t<typename std::decay_t<T>::custom_parameter_types>> : std::true_type {};
151
152 template<typename Visitor, typename... CustomTypes>
153 class parameter_visitor final {
154 public:
155 using result_type = std::invoke_result_t<Visitor&, const std::nullptr_t&>;
156
157 9 explicit parameter_visitor(Visitor& visitor)
158 9 : m_visitor(visitor)
159 9 {}
160
161 template<typename T>
162 7 result_type operator()(const T& value) const
163 {
164 7 return std::invoke(m_visitor, value);
165 }
166
167 2 result_type operator()(const custom_parameter_value& value) const
168 {
169 if constexpr (sizeof...(CustomTypes) != 0)
170 2 return visit_custom<CustomTypes...>(value);
171 else
172 throw parameter_error{"parameter visitor does not support the stored custom type"};
173 }
174
175 private:
176 template<typename CustomType, typename... RemainingTypes>
177 2 result_type visit_custom(const custom_parameter_value& custom) const
178 {
179
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (const std::decay_t<CustomType>* value = custom.template get_if<CustomType>())
180 1 return std::invoke(m_visitor, *value);
181 if constexpr (sizeof...(RemainingTypes) != 0)
182 return visit_custom<RemainingTypes...>(custom);
183 else
184
1/1
✓ Branch 2 taken 1 time.
1 throw parameter_error{"parameter visitor does not support the stored custom type"};
185 }
186
187 Visitor& m_visitor;
188 };
189
190 template<typename Visitor, typename... CustomTypes>
191 9 decltype(auto) visit_parameter(const parameter_storage& value, Visitor& visitor, parameter_types<CustomTypes...>)
192 {
193 9 parameter_visitor<Visitor, CustomTypes...> adapter{visitor};
194
1/1
✓ Branch 1 taken 8 times.
17 return std::visit(adapter, value);
195 }
196
197 } // namespace detail
198
199 /** @brief Runtime category of a parameter value or prepared slot. */
200 enum class parameter_kind : std::uint8_t {
201 null, ///< SQL NULL.
202 boolean, ///< Boolean value.
203 signed_integer, ///< Signed integer value.
204 unsigned_integer, ///< Unsigned integer value.
205 floating_point, ///< Floating-point value.
206 string, ///< String value.
207 binary, ///< Binary value.
208 custom ///< Application-defined value retained without conversion.
209 };
210
211 /** @brief Customizes SQLon parameter conversion for an application type. */
212 template<typename T>
213 struct parameter_traits;
214
215 /** @brief Owned type-erased SQL parameter value. */
216 class parameter_value final {
217 public:
218 /** @brief Creates a parameter value from supported storage. */
219 SQLON_API explicit parameter_value(parameter_storage value);
220
221 /** @brief Returns the underlying parameter storage. */
222 SQLON_API const parameter_storage& value() const noexcept;
223
224 /** @brief Returns the concrete stored value when its type is T. */
225 template<typename T>
226 12 const std::decay_t<T>* get_if() const noexcept
227 {
228 using value_type = std::decay_t<T>;
229
5/10
std::decay<ClientUuid>::type const* sqlon::parameter_value::get_if<ClientUuid>() const:
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
std::decay<PreparedDate>::type const* sqlon::parameter_value::get_if<PreparedDate>() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
std::decay<PreparedUuid>::type const* sqlon::parameter_value::get_if<PreparedUuid>() const:
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
std::decay<long>::type const* sqlon::parameter_value::get_if<long>() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
std::decay<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::type const* sqlon::parameter_value::get_if<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >() const:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
12 if (const custom_parameter_value* custom = std::get_if<custom_parameter_value>(&m_value))
230 9 return custom->template get_if<value_type>();
231
232 if constexpr (std::is_same_v<value_type, std::nullptr_t> || std::is_same_v<value_type, bool> ||
233 std::is_same_v<value_type, std::int64_t> || std::is_same_v<value_type, std::uint64_t> ||
234 std::is_same_v<value_type, double> || std::is_same_v<value_type, std::string> ||
235 std::is_same_v<value_type, binary>)
236 2 return std::get_if<value_type>(&m_value);
237 else
238 1 return nullptr;
239 }
240
241 /**
242 * @brief Visits the stored value and forwards optional context arguments before it.
243 *
244 * The visitor must expose a custom_parameter_types alias listing the application-defined values it accepts.
245 *
246 * @tparam Visitor Callable receiving the context arguments followed by the stored value.
247 * @tparam Arguments Optional context argument types.
248 * @param visitor Callable used for the active value type.
249 * @param arguments Context forwarded to the callable before the value.
250 * @throws parameter_error If the stored custom type is not accepted by the visitor.
251 */
252 template<typename Visitor, typename... Arguments>
253 9 decltype(auto) visit(Visitor&& visitor, Arguments&&... arguments) const
254 {
255 if constexpr (detail::has_custom_parameter_types<Visitor>::value) {
256 8 const auto invoke = [&](const auto& value) -> decltype(auto) {
257 8 return std::invoke(std::forward<Visitor>(visitor), std::forward<Arguments>(arguments)..., value);
258 };
259 using custom_types = typename std::decay_t<Visitor>::custom_parameter_types;
260
1/1
✓ Branch 1 taken 8 times.
17 return detail::visit_parameter(m_value, invoke, custom_types{});
261 } else {
262 static_assert(detail::has_custom_parameter_types<Visitor>::value,
263 "a parameter visitor must declare custom_parameter_types");
264 }
265 }
266
267 /**
268 * @brief Visits the raw storage alternative without unwrapping application-defined values.
269 * @tparam Visitor Callable receiving one parameter_storage alternative.
270 * @param visitor Callable used for the active storage alternative.
271 */
272 template<typename Visitor>
273 42 decltype(auto) visit_storage(Visitor&& visitor) const
274 {
275 42 return std::visit(std::forward<Visitor>(visitor), m_value);
276 }
277
278 private:
279 parameter_storage m_value;
280 };
281
282 /** @brief Parameter metadata and owned value produced in driver binding order. */
283 struct rendered_parameter final {
284 /** @brief Semantic name supplied through param(), independent of placeholder syntax. */
285 std::optional<std::string> name;
286
287 /** @brief Owned parameter value. */
288 parameter_value value;
289
290 /** @brief Exact SQL placeholder emitted for this binding, such as $1, ?, or :p1, if known. */
291 std::optional<std::string> placeholder{};
292 };
293
294 namespace detail {
295
296 template<typename>
297 struct is_parameter_binding : std::false_type {};
298
299 template<typename>
300 struct is_optional : std::false_type {};
301
302 template<typename T>
303 struct is_optional<std::optional<T>> : std::true_type {
304 using value_type = T;
305 };
306
307 template<typename T>
308 inline constexpr bool is_optional_v = is_optional<std::decay_t<T>>::value;
309
310 template<typename T>
311 inline constexpr bool is_nullable_parameter_v = is_optional_v<T>;
312
313 template<typename T, typename = void>
314 struct custom_parameter_expression_type {
315 using type = std::decay_t<T>;
316 };
317
318 template<typename T>
319 struct custom_parameter_expression_type<T, std::void_t<typename parameter_traits<std::decay_t<T>>::value_type>> {
320 using type = typename parameter_traits<std::decay_t<T>>::value_type;
321 };
322
323 template<typename T, bool = is_optional_v<T>>
324 struct parameter_expression_type {
325 using type = typename custom_parameter_expression_type<T>::type;
326 };
327
328 template<typename T>
329 struct parameter_expression_type<T, true> {
330 using type = typename parameter_expression_type<typename is_optional<std::decay_t<T>>::value_type>::type;
331 };
332
333 template<typename T>
334 using parameter_expression_type_t = typename parameter_expression_type<T>::type;
335
336 template<typename T, typename = void>
337 struct has_parameter_traits : std::false_type {};
338
339 template<typename T>
340 struct has_parameter_traits<T,
341 std::void_t<decltype(parameter_traits<T>::kind), typename parameter_traits<T>::value_type,
342 decltype(parameter_traits<T>::to_parameter(std::declval<const T&>()))>>
343 : std::true_type {};
344
345 template<typename T>
346 359 parameter_storage make_parameter_value(T&& value)
347 {
348 using value_type = std::decay_t<T>;
349 if constexpr (is_optional_v<value_type>) {
350
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
3 if (!value) return nullptr;
351 1 return make_parameter_value(*std::forward<T>(value));
352 } else if constexpr (std::is_same_v<value_type, std::nullptr_t>)
353 5 return nullptr;
354 else if constexpr (std::is_same_v<value_type, bool>)
355 47 return value;
356 else if constexpr (std::is_integral_v<value_type> && std::is_signed_v<value_type>)
357 175 return static_cast<std::int64_t>(value);
358 else if constexpr (std::is_integral_v<value_type> && std::is_unsigned_v<value_type>)
359 3 return static_cast<std::uint64_t>(value);
360 else if constexpr (std::is_floating_point_v<value_type>)
361 4 return static_cast<double>(value);
362 else if constexpr (std::is_same_v<value_type, std::string>)
363 54 return std::forward<T>(value);
364 else if constexpr (std::is_same_v<value_type, const char*> || std::is_same_v<value_type, char*>)
365
0/1
✗ Branch 1 not taken.
114 return std::string{value};
366 else if constexpr (std::is_same_v<value_type, binary>)
367 2 return std::forward<T>(value);
368 else if constexpr (has_parameter_traits<value_type>::value)
369 1 return parameter_traits<value_type>::to_parameter(std::forward<T>(value));
370 else
371
5/5
std::variant<decltype(nullptr), bool, long, unsigned long, double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::byte, std::allocator<std::byte> >, sqlon::custom_parameter_value> sqlon::detail::make_parameter_value<ClientUuid const&>(ClientUuid const&):
✓ Branch 2 taken 4 times.
std::variant<decltype(nullptr), bool, long, unsigned long, double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::byte, std::allocator<std::byte> >, sqlon::custom_parameter_value> sqlon::detail::make_parameter_value<PreparedDate>(PreparedDate&&):
✓ Branch 2 taken 1 time.
std::variant<decltype(nullptr), bool, long, unsigned long, double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::byte, std::allocator<std::byte> >, sqlon::custom_parameter_value> sqlon::detail::make_parameter_value<PreparedUuid const&>(PreparedUuid const&):
✓ Branch 2 taken 1 time.
std::variant<decltype(nullptr), bool, long, unsigned long, double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::byte, std::allocator<std::byte> >, sqlon::custom_parameter_value> sqlon::detail::make_parameter_value<PreparedUuid>(PreparedUuid&&):
✓ Branch 2 taken 1 time.
std::variant<decltype(nullptr), bool, long, unsigned long, double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::byte, std::allocator<std::byte> >, sqlon::custom_parameter_value> sqlon::detail::make_parameter_value<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&>(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&):
✓ Branch 2 taken 1 time.
8 return custom_parameter_value{std::forward<T>(value)};
372 }
373
374 template<typename T>
375 30 constexpr parameter_kind parameter_kind_of()
376 {
377 using value_type = std::decay_t<T>;
378 if constexpr (is_optional_v<value_type>)
379 3 return parameter_kind_of<typename is_optional<value_type>::value_type>();
380 else if constexpr (std::is_same_v<value_type, std::nullptr_t>)
381 return parameter_kind::null;
382 else if constexpr (std::is_same_v<value_type, bool>)
383 2 return parameter_kind::boolean;
384 else if constexpr (std::is_integral_v<value_type> && std::is_signed_v<value_type>)
385 9 return parameter_kind::signed_integer;
386 else if constexpr (std::is_integral_v<value_type> && std::is_unsigned_v<value_type>)
387 1 return parameter_kind::unsigned_integer;
388 else if constexpr (std::is_floating_point_v<value_type>)
389 return parameter_kind::floating_point;
390 else if constexpr (std::is_same_v<value_type, std::string> || std::is_same_v<value_type, const char*> ||
391 std::is_same_v<value_type, char*>) {
392 14 return parameter_kind::string;
393 } else if constexpr (std::is_same_v<value_type, binary>)
394 return parameter_kind::binary;
395 else if constexpr (has_parameter_traits<value_type>::value)
396 return parameter_traits<value_type>::kind;
397 else
398 1 return parameter_kind::custom;
399 }
400
401 template<typename T>
402 36 const void* custom_parameter_type_id() noexcept
403 {
404 using value_type = std::decay_t<T>;
405 if constexpr (is_optional_v<value_type>)
406 3 return custom_parameter_type_id<typename is_optional<value_type>::value_type>();
407 else if constexpr (parameter_kind_of<value_type>() == parameter_kind::custom)
408 6 return parameter_type_id<value_type>();
409 else
410 27 return nullptr;
411 }
412
413 } // namespace detail
414 } // namespace sqlon
415
416 #endif // SQLON_PARAMETER_H
417