From f1314469a184156fbcbe7ddc3715f1ef7d5120d0 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 22 Sep 2025 09:01:44 +0300 Subject: [PATCH 1/4] Corrected column result for pointer binding Pointers to objects (as part of SQLite's pointer-passing interface) always yield NULL when used as a column result. --- dev/column_result.h | 5 +++++ include/sqlite_orm/sqlite_orm.h | 5 +++++ tests/statement_serializer_tests/bindables.cpp | 2 +- tests/static_tests/column_result_t.cpp | 6 ++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/dev/column_result.h b/dev/column_result.h index fe502c6ba..3657e0fc1 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -127,6 +127,11 @@ namespace sqlite_orm { using type = std::unique_ptr>; }; + template + struct column_result_t, void> { + using type = std::nullptr_t; + }; + template struct column_result_t, void> { using type = int; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index dc2375cb1..246d4058c 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -11847,6 +11847,11 @@ namespace sqlite_orm { using type = std::unique_ptr>; }; + template + struct column_result_t, void> { + using type = std::nullptr_t; + }; + template struct column_result_t, void> { using type = int; diff --git a/tests/statement_serializer_tests/bindables.cpp b/tests/statement_serializer_tests/bindables.cpp index e7608b17e..27eec9f7f 100644 --- a/tests/statement_serializer_tests/bindables.cpp +++ b/tests/statement_serializer_tests/bindables.cpp @@ -260,7 +260,7 @@ TEST_CASE("bindables") { } #if SQLITE_VERSION_NUMBER >= 3020000 - SECTION("bindable_pointer") { + SECTION("pointer binding") { string value, expected; context.replace_bindable_with_question = false; diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 035f80385..510745209 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -80,6 +80,7 @@ TEST_CASE("column_result_of_t") { runTest>(min(&User::name)); runTest(count()); runTest(count()); + // function call { struct RandomFunc { static const char* name(); @@ -89,6 +90,11 @@ TEST_CASE("column_result_of_t") { }; runTest(func()); } + // pointer binding + { + const int64 value = 0; + runTest(bind_carray_pointer_statically(&value)); + } runTest(distinct(&User::id)); runTest(distinct(&User::name)); runTest(all(&User::id)); From e92fe7414f69f35da5e72b631b4112726c10c8ad Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 22 Sep 2025 09:04:11 +0300 Subject: [PATCH 2/4] Labeled bindables and named parameters --- dev/ast/labeled_bindable.h | 84 ++++ dev/ast/named_parameter.h | 87 ++++ dev/ast_iterator.h | 12 + dev/column_result.h | 14 + dev/get_prepared_statement.h | 103 ++++- dev/node_tuple.h | 6 - dev/statement_binder.h | 55 ++- dev/statement_binding_traits.h | 31 ++ dev/statement_serializer.h | 42 ++ dev/type_traits.h | 5 +- include/sqlite_orm/sqlite_orm.h | 424 ++++++++++++++++-- tests/ast_iterator_tests.cpp | 22 +- tests/prepared_statement_tests/select.cpp | 51 ++- .../statement_serializer_tests/bindables.cpp | 20 + tests/static_tests/column_result_t.cpp | 12 + tests/static_tests/node_tuple.cpp | 23 +- tests/static_tests/operators_adl.cpp | 3 + 17 files changed, 886 insertions(+), 108 deletions(-) create mode 100644 dev/ast/labeled_bindable.h create mode 100644 dev/ast/named_parameter.h create mode 100644 dev/statement_binding_traits.h diff --git a/dev/ast/labeled_bindable.h b/dev/ast/labeled_bindable.h new file mode 100644 index 000000000..e5a2b3814 --- /dev/null +++ b/dev/ast/labeled_bindable.h @@ -0,0 +1,84 @@ +#pragma once +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif +#endif + +#include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/cstring_literal.h" +#include "../statement_binding_traits.h" + +namespace sqlite_orm::internal { + template + using access_bindable_t = polyfill::detected_or_t; +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + template + struct labeled_bindable { + using name_constant_type = Moniker; + using type = T; + + type value; + }; + + template + struct bindable_label : Moniker { + using name_constant_type = Moniker; + }; + + // note: not in use because AST iteration walks through to the value leaf + template + T& access_bindable(const labeled_bindable& t) = delete; + + template + using name_constant_type_t = typename T::name_constant_type; + + template + using name_constant_type_or_none_t = polyfill::detected_t; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace + // to facilitate ADL (Argument Dependent Lookup) + namespace internal { + /* + Associates a bindable value with a moniker. + */ + template + requires is_bindable_v + constexpr labeled_bindable operator>>=(T bindable, const bindable_label&) { + return {std::move(bindable)}; + } + + /* + Associates a referenced bindable value with a moniker. + */ + template + requires is_bindable_v + constexpr labeled_bindable operator>>=(std::reference_wrapper bindable, + const bindable_label&) { + return {bindable}; + } + } + + inline namespace literals { + /* + * Make a label for a bindable from a string literal. + * E.g. "myparam"_bindable + */ + template + [[nodiscard]] consteval auto operator"" _bindable() { + using name_constant_type = std::integral_constant; + return internal::bindable_label{}; + } + } + + /** @short Specifies that a type is an integral constant C-string usable as a label for a bindable. + */ + template + concept orm_bindable_label = polyfill::is_specialization_of_v; +} +#endif diff --git a/dev/ast/named_parameter.h b/dev/ast/named_parameter.h new file mode 100644 index 000000000..7bfb9fd00 --- /dev/null +++ b/dev/ast/named_parameter.h @@ -0,0 +1,87 @@ +#pragma once +#pragma once +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#include +#include +#endif +#endif + +#include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/cstring_literal.h" +#include "../statement_binding_traits.h" + +namespace sqlite_orm::internal { + template + using access_bindable_t = polyfill::detected_or_t; + + template + T& access_bindable(T& t) { + return t; + } +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * @note Named SQL parameters can be used multiple times in an SQL statement, which is the reason that we use shared_ptr here. + */ + template + struct named_bindable { + using name_constant_type = Moniker; + using type = T; + + std::shared_ptr value; + + template + void operator=(U&& other) { + *value = std::forward(other); + } + }; + + template + struct parameter_moniker : Moniker { + using name_constant_type = Moniker; + + /* + * Create a named SQL parameter argument of type T. + */ + template + [[nodiscard]] constexpr named_bindable create(Args&&... args) const { + return {std::make_shared(std::forward(args)...)}; + } + }; + + template + T& access_bindable(const named_bindable& bindable) { + return *bindable.value; + } + + template + using name_constant_type_t = typename T::name_constant_type; + + template + using name_constant_type_or_none_t = polyfill::detected_t; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + inline namespace literals { + /* + * Make a moniker for a named parameter from a string literal. + * E.g. "myparam"_param + */ + template + [[nodiscard]] consteval auto operator"" _param() { + static_assert(moniker.cstr[0] == ':' || moniker.cstr[0] == '@'); + using name_constant_type = std::integral_constant; + return internal::parameter_moniker{}; + } + } + + /** @short Specifies that a type is an integral constant C-string usable for a named parameter. + */ + template + concept orm_parameter_moniker = polyfill::is_specialization_of_v; +} +#endif diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index 9eac1ab02..fd8e75cdd 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -514,6 +514,18 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct ast_iterator> { + using node_type = T; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& param, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(param.value, lambda); + } + }; +#endif + template struct ast_iterator, void> { using node_type = function_call; diff --git a/dev/column_result.h b/dev/column_result.h index 3657e0fc1..5b2b1902f 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -25,6 +25,8 @@ #include "cte_types.h" #include "storage_traits.h" #include "function.h" +#include "ast/labeled_bindable.h" +#include "ast/named_parameter.h" #include "ast/special_keywords.h" #include "ast/cast.h" @@ -132,6 +134,18 @@ namespace sqlite_orm { using type = std::nullptr_t; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct column_result_t> { + using type = typename T::type; + }; + + template + struct column_result_t> { + using type = typename T::type; + }; +#endif + template struct column_result_t, void> { using type = int; diff --git a/dev/get_prepared_statement.h b/dev/get_prepared_statement.h index 89a90305f..7825f6e50 100644 --- a/dev/get_prepared_statement.h +++ b/dev/get_prepared_statement.h @@ -6,6 +6,8 @@ #endif #include "functional/cxx_type_traits_polyfill.h" +#include "ast/labeled_bindable.h" +#include "ast/named_parameter.h" #include "type_traits.h" #include "prepared_statement.h" #include "ast_iterator.h" @@ -126,19 +128,22 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template const auto& get(const internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; using statement_type = polyfill::remove_cvref_t; - using expression_type = internal::expression_type_t; - using node_tuple = internal::node_tuple_t; - using bind_tuple = internal::bindable_filter_t; - using result_type = std::tuple_element_t(N), bind_tuple>; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using bound_type = std::tuple_element_t(N), bind_tuple>; + using result_type = access_bindable_t; + const result_type* result = nullptr; - internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { - using node_type = polyfill::remove_cvref_t; - if constexpr (internal::is_bindable::value) { + iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { + using leaf_type = polyfill::remove_cvref_t; + if constexpr (is_sql_parameter::value) { ++index; - if constexpr (std::is_same::value) { + if constexpr (std::is_same>::value) { if (index == N) { - result = &node; + result = &access_bindable(node); } } } @@ -148,24 +153,86 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template auto& get(internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; using statement_type = std::remove_reference_t; - using expression_type = internal::expression_type_t; - using node_tuple = internal::node_tuple_t; - using bind_tuple = internal::bindable_filter_t; - using result_type = std::tuple_element_t(N), bind_tuple>; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using bound_type = std::tuple_element_t(N), bind_tuple>; + using result_type = access_bindable_t; + result_type* result = nullptr; + iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { + using leaf_type = polyfill::remove_cvref_t; + if constexpr (is_sql_parameter::value) { + ++index; + if constexpr (std::is_same>::value) { + if (index == N) { + result = const_cast(&access_bindable(node)); + } + } + } + }); + return internal::get_ref(*result); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires (orm_parameter_moniker || orm_bindable_label) + const auto& access(const internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; + using statement_type = std::remove_cvref_t; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using index_type = + find_tuple_type, name_constant_type_or_none_t>; + constexpr size_t N = index_type::value; + static_assert(N < std::tuple_size_v, "No such named bindable found in prepared statement"); + using bound_type = std::tuple_element_t; + using result_type = access_bindable_t; + + const result_type* result = nullptr; + iterate_ast(statement.expression, [&result, index = -1](const leaf_type& node) mutable { + if constexpr (is_sql_parameter::value) { + ++index; + if constexpr (std::is_same_v>) { + if (index == N) { + result = &access_bindable(node); + } + } + } + }); + return internal::get_ref(*result); + } - internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { - using node_type = polyfill::remove_cvref_t; - if constexpr (internal::is_bindable::value) { + template + requires (orm_parameter_moniker || orm_bindable_label) + auto& access(internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; + using statement_type = std::remove_cvref_t; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using index_type = + find_tuple_type, name_constant_type_or_none_t>; + constexpr size_t N = index_type::value; + static_assert(N < std::tuple_size_v, "No such named bindable found in prepared statement"); + using bound_type = std::tuple_element_t; + using result_type = access_bindable_t; + + result_type* result = nullptr; + iterate_ast(statement.expression, [&result, index = -1](const leaf_type& node) mutable { + if constexpr (is_sql_parameter::value) { ++index; - if constexpr (std::is_same::value) { + if constexpr (std::is_same_v>) { if (index == N) { - result = const_cast(&node); + result = const_cast(&access_bindable(node)); } } } }); return internal::get_ref(*result); } +#endif } diff --git a/dev/node_tuple.h b/dev/node_tuple.h index b3fd9d576..e05a36fdd 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -98,12 +98,6 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; - /** - * Literal - */ - template - struct node_tuple, void> : node_tuple {}; - template struct node_tuple, void> : node_tuple {}; diff --git a/dev/statement_binder.h b/dev/statement_binder.h index 0ea57f03a..25b3363b9 100644 --- a/dev/statement_binder.h +++ b/dev/statement_binder.h @@ -26,36 +26,11 @@ #include "arithmetic_tag.h" #include "xdestroy_handling.h" #include "pointer_value.h" - -SQLITE_ORM_EXPORT namespace sqlite_orm { - - /** - * Helper class used for binding fields to sqlite3 statements. - */ - template - struct statement_binder; -} +#include "ast/labeled_bindable.h" +#include "ast/named_parameter.h" +#include "statement_binding_traits.h" namespace sqlite_orm { - namespace internal { - /* - * Implementation note: the technique of indirect expression testing is because - * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. - * It must also be a type that differs from those for `is_printable_v`, `is_preparable_v`. - */ - template - struct indirectly_test_bindable; - - template - inline constexpr bool is_bindable_v = false; - template - inline constexpr bool - is_bindable_v{})>>> = true; - - template - struct is_bindable : polyfill::bool_constant> {}; - } - #if SQLITE_VERSION_NUMBER >= 3020000 /** * Specialization for pointer bindings (part of the 'pointer-passing interface'). @@ -296,6 +271,18 @@ namespace sqlite_orm { explicit conditional_binder(sqlite3_stmt* stmt) : stmt{stmt} {} +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void operator()(const named_bindable& bindable) { + const int nth = sqlite3_bind_parameter_index(stmt, Moniker::value.cstr); + // nothing to do if this named parameter was already bound + if (nth < this->nthSqlParameter) + return; + + this->operator()(*bindable.value); + } +#endif + template = true> void operator()(const T& t) { int rc = statement_binder{}.bind(this->stmt, ++this->nthSqlParameter, t); @@ -369,7 +356,17 @@ namespace sqlite_orm { } }; + template + using is_sql_parameter = mpl::invoke_t +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + , + check_if_is_template, + check_if_is_template +#endif + >, + T>; + template - using bindable_filter_t = filter_tuple_t; + using bindable_filter_t = filter_tuple_t; } } diff --git a/dev/statement_binding_traits.h b/dev/statement_binding_traits.h new file mode 100644 index 000000000..6151f2dcc --- /dev/null +++ b/dev/statement_binding_traits.h @@ -0,0 +1,31 @@ +#pragma once + +#include "functional/cxx_type_traits_polyfill.h" + +SQLITE_ORM_EXPORT namespace sqlite_orm { + + /** + * Helper class used for binding fields to sqlite3 statements. + */ + template + struct statement_binder; +} + +namespace sqlite_orm::internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_preparable_v`. + */ + template + struct indirectly_test_bindable; + + template + inline constexpr bool is_bindable_v = false; + template + inline constexpr bool + is_bindable_v{})>>> = true; + + template + struct is_bindable : polyfill::bool_constant> {}; +} diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index 1a0d32252..83b7cb5c5 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -30,6 +30,8 @@ #include "ast/rank.h" #include "ast/special_keywords.h" #include "ast/limit.h" +#include "ast/labeled_bindable.h" +#include "ast/named_parameter.h" #include "core_functions.h" #include "constraints.h" #include "conditions.h" @@ -241,6 +243,46 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Serializer for labeled bindables. + */ + template + struct statement_serializer> { + using statement_type = T; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& labeled, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + static_assert(is_bindable_v>, "Value must be bindable"); + + statement_serializer> serializer{}; + return serializer(labeled.value, context); + } + }; + + /** + * Serializer for named parameters. + */ + template + struct statement_serializer> { + using statement_type = T; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& param, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + static_assert(is_bindable_v>, "Value must be bindable"); + + if (context.replace_bindable_with_question) { + return std::string(statement_type::name_constant_type::value.cstr); + } else { + statement_serializer> serializer{}; + return serializer(*param.value, context); + } + } + }; +#endif + template struct statement_serializer, void> { using statement_type = filtered_aggregate_function; diff --git a/dev/type_traits.h b/dev/type_traits.h index f4e9a3103..12bb5f0f4 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -127,10 +127,7 @@ namespace sqlite_orm { // T::alias_type or nonesuch template - using alias_holder_type_or_none = polyfill::detected; - - template - using alias_holder_type_or_none_t = typename alias_holder_type_or_none::type; + using alias_holder_type_or_none_t = polyfill::detected_t; #endif #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 246d4058c..296b3f271 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1941,10 +1941,7 @@ namespace sqlite_orm { // T::alias_type or nonesuch template - using alias_holder_type_or_none = polyfill::detected; - - template - using alias_holder_type_or_none_t = typename alias_holder_type_or_none::type; + using alias_holder_type_or_none_t = polyfill::detected_t; #endif #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED @@ -10231,35 +10228,217 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } #endif +// #include "ast/labeled_bindable.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif +#endif + +// #include "../functional/cxx_type_traits_polyfill.h" + +// #include "../functional/cstring_literal.h" + +// #include "../statement_binding_traits.h" + +// #include "functional/cxx_type_traits_polyfill.h" + SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Helper class used for binding fields to sqlite3 statements. */ - template + template struct statement_binder; } -namespace sqlite_orm { +namespace sqlite_orm::internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_preparable_v`. + */ + template + struct indirectly_test_bindable; + + template + inline constexpr bool is_bindable_v = false; + template + inline constexpr bool + is_bindable_v{})>>> = true; + + template + struct is_bindable : polyfill::bool_constant> {}; +} + +namespace sqlite_orm::internal { + template + using access_bindable_t = polyfill::detected_or_t; +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + template + struct labeled_bindable { + using name_constant_type = Moniker; + using type = T; + + type value; + }; + + template + struct bindable_label : Moniker { + using name_constant_type = Moniker; + }; + + // note: not in use because AST iteration walks through to the value leaf + template + T& access_bindable(const labeled_bindable& t) = delete; + + template + using name_constant_type_t = typename T::name_constant_type; + + template + using name_constant_type_or_none_t = polyfill::detected_t; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace + // to facilitate ADL (Argument Dependent Lookup) namespace internal { - /* - * Implementation note: the technique of indirect expression testing is because - * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. - * It must also be a type that differs from those for `is_printable_v`, `is_preparable_v`. + /* + Associates a bindable value with a moniker. */ - template - struct indirectly_test_bindable; + template + requires is_bindable_v + constexpr labeled_bindable operator>>=(T bindable, const bindable_label&) { + return {std::move(bindable)}; + } - template - inline constexpr bool is_bindable_v = false; - template - inline constexpr bool - is_bindable_v{})>>> = true; + /* + Associates a referenced bindable value with a moniker. + */ + template + requires is_bindable_v + constexpr labeled_bindable operator>>=(std::reference_wrapper bindable, + const bindable_label&) { + return {bindable}; + } + } - template - struct is_bindable : polyfill::bool_constant> {}; + inline namespace literals { + /* + * Make a label for a bindable from a string literal. + * E.g. "myparam"_bindable + */ + template + [[nodiscard]] consteval auto operator"" _bindable() { + using name_constant_type = std::integral_constant; + return internal::bindable_label{}; + } + } + + /** @short Specifies that a type is an integral constant C-string usable as a label for a bindable. + */ + template + concept orm_bindable_label = polyfill::is_specialization_of_v; +} +#endif + +// #include "ast/named_parameter.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#include +#include +#endif +#endif + +// #include "../functional/cxx_type_traits_polyfill.h" + +// #include "../functional/cstring_literal.h" + +// #include "../statement_binding_traits.h" + +namespace sqlite_orm::internal { + template + using access_bindable_t = polyfill::detected_or_t; + + template + T& access_bindable(T& t) { + return t; + } +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * @note Named SQL parameters can be used multiple times in an SQL statement, which is the reason that we use shared_ptr here. + */ + template + struct named_bindable { + using name_constant_type = Moniker; + using type = T; + + std::shared_ptr value; + + template + void operator=(U&& other) { + *value = std::forward(other); + } + }; + + template + struct parameter_moniker : Moniker { + using name_constant_type = Moniker; + + /* + * Create a named SQL parameter argument of type T. + */ + template + [[nodiscard]] constexpr named_bindable create(Args&&... args) const { + return {std::make_shared(std::forward(args)...)}; + } + }; + + template + T& access_bindable(const named_bindable& bindable) { + return *bindable.value; + } + + template + using name_constant_type_t = typename T::name_constant_type; + + template + using name_constant_type_or_none_t = polyfill::detected_t; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + inline namespace literals { + /* + * Make a moniker for a named parameter from a string literal. + * E.g. "myparam"_param + */ + template + [[nodiscard]] consteval auto operator"" _param() { + static_assert(moniker.cstr[0] == ':' || moniker.cstr[0] == '@'); + using name_constant_type = std::integral_constant; + return internal::parameter_moniker{}; + } } + /** @short Specifies that a type is an integral constant C-string usable for a named parameter. + */ + template + concept orm_parameter_moniker = polyfill::is_specialization_of_v; +} +#endif + +// #include "statement_binding_traits.h" + +namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3020000 /** * Specialization for pointer bindings (part of the 'pointer-passing interface'). @@ -10500,6 +10679,18 @@ namespace sqlite_orm { explicit conditional_binder(sqlite3_stmt* stmt) : stmt{stmt} {} +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void operator()(const named_bindable& bindable) { + const int nth = sqlite3_bind_parameter_index(stmt, Moniker::value.cstr); + // nothing to do if this named parameter was already bound + if (nth < this->nthSqlParameter) + return; + + this->operator()(*bindable.value); + } +#endif + template = true> void operator()(const T& t) { int rc = statement_binder{}.bind(this->stmt, ++this->nthSqlParameter, t); @@ -10573,8 +10764,18 @@ namespace sqlite_orm { } }; + template + using is_sql_parameter = mpl::invoke_t +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + , + check_if_is_template, + check_if_is_template +#endif + >, + T>; + template - using bindable_filter_t = filter_tuple_t; + using bindable_filter_t = filter_tuple_t; } } @@ -11688,6 +11889,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif } +// #include "ast/labeled_bindable.h" + +// #include "ast/named_parameter.h" + // #include "ast/special_keywords.h" namespace sqlite_orm { @@ -11852,6 +12057,18 @@ namespace sqlite_orm { using type = std::nullptr_t; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct column_result_t> { + using type = typename T::type; + }; + + template + struct column_result_t> { + using type = typename T::type; + }; +#endif + template struct column_result_t, void> { using type = int; @@ -16120,6 +16337,18 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct ast_iterator> { + using node_type = T; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& param, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(param.value, lambda); + } + }; +#endif + template struct ast_iterator, void> { using node_type = function_call; @@ -19433,6 +19662,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "ast/limit.h" +// #include "ast/labeled_bindable.h" + +// #include "ast/named_parameter.h" + // #include "core_functions.h" // #include "constraints.h" @@ -20402,6 +20635,46 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Serializer for labeled bindables. + */ + template + struct statement_serializer> { + using statement_type = T; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& labeled, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + static_assert(is_bindable_v>, "Value must be bindable"); + + statement_serializer> serializer{}; + return serializer(labeled.value, context); + } + }; + + /** + * Serializer for named parameters. + */ + template + struct statement_serializer> { + using statement_type = T; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& param, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + static_assert(is_bindable_v>, "Value must be bindable"); + + if (context.replace_bindable_with_question) { + return std::string(statement_type::name_constant_type::value.cstr); + } else { + statement_serializer> serializer{}; + return serializer(*param.value, context); + } + } + }; +#endif + template struct statement_serializer, void> { using statement_type = filtered_aggregate_function; @@ -25570,6 +25843,10 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" +// #include "ast/labeled_bindable.h" + +// #include "ast/named_parameter.h" + // #include "type_traits.h" // #include "prepared_statement.h" @@ -25694,12 +25971,6 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; - /** - * Literal - */ - template - struct node_tuple, void> : node_tuple {}; - template struct node_tuple, void> : node_tuple {}; @@ -25976,19 +26247,22 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template const auto& get(const internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; using statement_type = polyfill::remove_cvref_t; - using expression_type = internal::expression_type_t; - using node_tuple = internal::node_tuple_t; - using bind_tuple = internal::bindable_filter_t; - using result_type = std::tuple_element_t(N), bind_tuple>; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using bound_type = std::tuple_element_t(N), bind_tuple>; + using result_type = access_bindable_t; + const result_type* result = nullptr; - internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { - using node_type = polyfill::remove_cvref_t; - if constexpr (internal::is_bindable::value) { + iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { + using leaf_type = polyfill::remove_cvref_t; + if constexpr (is_sql_parameter::value) { ++index; - if constexpr (std::is_same::value) { + if constexpr (std::is_same>::value) { if (index == N) { - result = &node; + result = &access_bindable(node); } } } @@ -25998,26 +26272,88 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template auto& get(internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; using statement_type = std::remove_reference_t; - using expression_type = internal::expression_type_t; - using node_tuple = internal::node_tuple_t; - using bind_tuple = internal::bindable_filter_t; - using result_type = std::tuple_element_t(N), bind_tuple>; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using bound_type = std::tuple_element_t(N), bind_tuple>; + using result_type = access_bindable_t; + result_type* result = nullptr; + iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { + using leaf_type = polyfill::remove_cvref_t; + if constexpr (is_sql_parameter::value) { + ++index; + if constexpr (std::is_same>::value) { + if (index == N) { + result = const_cast(&access_bindable(node)); + } + } + } + }); + return internal::get_ref(*result); + } - internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { - using node_type = polyfill::remove_cvref_t; - if constexpr (internal::is_bindable::value) { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires (orm_parameter_moniker || orm_bindable_label) + const auto& access(const internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; + using statement_type = std::remove_cvref_t; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using index_type = + find_tuple_type, name_constant_type_or_none_t>; + constexpr size_t N = index_type::value; + static_assert(N < std::tuple_size_v, "No such named bindable found in prepared statement"); + using bound_type = std::tuple_element_t; + using result_type = access_bindable_t; + + const result_type* result = nullptr; + iterate_ast(statement.expression, [&result, index = -1](const leaf_type& node) mutable { + if constexpr (is_sql_parameter::value) { ++index; - if constexpr (std::is_same::value) { + if constexpr (std::is_same_v>) { if (index == N) { - result = const_cast(&node); + result = &access_bindable(node); } } } }); return internal::get_ref(*result); } + + template + requires (orm_parameter_moniker || orm_bindable_label) + auto& access(internal::prepared_statement_t& statement) { + using namespace ::sqlite_orm::internal; + using statement_type = std::remove_cvref_t; + using expression_type = expression_type_t; + using node_tuple = node_tuple_t; + using bind_tuple = bindable_filter_t; + using index_type = + find_tuple_type, name_constant_type_or_none_t>; + constexpr size_t N = index_type::value; + static_assert(N < std::tuple_size_v, "No such named bindable found in prepared statement"); + using bound_type = std::tuple_element_t; + using result_type = access_bindable_t; + + result_type* result = nullptr; + iterate_ast(statement.expression, [&result, index = -1](const leaf_type& node) mutable { + if constexpr (is_sql_parameter::value) { + ++index; + if constexpr (std::is_same_v>) { + if (index == N) { + result = const_cast(&access_bindable(node)); + } + } + } + }); + return internal::get_ref(*result); + } +#endif } #pragma once diff --git a/tests/ast_iterator_tests.cpp b/tests/ast_iterator_tests.cpp index 3e197f8ca..74e56dd0f 100644 --- a/tests/ast_iterator_tests.cpp +++ b/tests/ast_iterator_tests.cpp @@ -50,9 +50,25 @@ TEST_CASE("ast_iterator") { #endif SECTION("bindables") { - auto node = select(1); - expected.push_back(typeid(int)); - iterate_ast(node, lambda); + SECTION("normal") { + constexpr auto node = select(1); + expected.push_back(typeid(int)); + iterate_ast(node, lambda); + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("labeled") { + constexpr auto label = "l"_bindable; + constexpr auto node = select(1 >>= label); + expected.push_back(typeid(int)); + iterate_ast(node, lambda); + } + SECTION("named") { + auto bindable = "@p"_param.create(); + constexpr auto node = select(bindable); + expected.push_back(typeid(int)); + iterate_ast(node, lambda); + } +#endif } SECTION("aggregate functions") { SECTION("avg") { diff --git a/tests/prepared_statement_tests/select.cpp b/tests/prepared_statement_tests/select.cpp index ad19fea1b..475fe110f 100644 --- a/tests/prepared_statement_tests/select.cpp +++ b/tests/prepared_statement_tests/select.cpp @@ -39,9 +39,56 @@ TEST_CASE("Prepared select") { storage.replace(UserAndVisit{3, 1, "Shine on"}); SECTION("const access to bindable") { - auto statement = storage.prepare(select(10)); - REQUIRE(get<0>(static_cast(statement)) == 10); + const auto statement = storage.prepare(select(10)); + REQUIRE(get<0>(statement) == 10); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("const access to labeled bindable") { + constexpr orm_bindable_label auto l = "l"_bindable; + const auto statement = storage.prepare(select(10 >>= l)); + REQUIRE(get<0>(statement) == 10); + REQUIRE(access(statement) == 10); + } + SECTION("const access to named bindable") { + constexpr orm_parameter_moniker auto n1_param = "@n1"_param; + auto bindable = n1_param.create(10); + const auto statement = storage.prepare(select(bindable)); + REQUIRE(get<0>(statement) == 10); + REQUIRE(access(statement) == 10); + } + SECTION("mixed labeled and indexed bindables") { + constexpr orm_bindable_label auto l1 = "l1"_bindable; + constexpr orm_bindable_label auto l2 = "l2"_bindable; + int id = 4; + auto statement = storage.prepare(select(columns(10, 11 >>= l1, std::ref(id), std::ref(id) >>= l2))); + REQUIRE(get<0>(statement) == 10); + REQUIRE(get<1>(statement) == 11); + REQUIRE(get<2>(statement) == 4); + REQUIRE(get<3>(statement) == 4); + REQUIRE(access(statement) == 11); + REQUIRE(access(statement) == 4); + auto rows = storage.execute(statement); + REQUIRE(rows == decltype(rows){{10, 11, 4, 4}}); + } + SECTION("mixed named and indexed bindables") { + constexpr orm_parameter_moniker auto n1_param = "@n1"_param; + auto bindable1 = n1_param.create(11); + auto bindable2 = ":n2"_param.create(5); + auto statement = storage.prepare(select(columns(10, bindable1, 4, bindable1, bindable2, 6))); + REQUIRE(get<0>(statement) == 10); + REQUIRE(get<1>(statement) == 11); + REQUIRE(get<2>(statement) == 4); + REQUIRE(get<3>(statement) == 11); + REQUIRE(get<4>(statement) == 5); + REQUIRE(get<5>(statement) == 6); + REQUIRE(access(statement) == 11); + auto rows = storage.execute(statement); + REQUIRE(rows == decltype(rows){{10, 11, 4, 11, 5, 6}}); + bindable1 = 15; + rows = storage.execute(statement); + REQUIRE(rows == decltype(rows){{10, 15, 4, 15, 5, 6}}); + } +#endif SECTION("one simple argument") { SECTION("by val") { SECTION("int") { diff --git a/tests/statement_serializer_tests/bindables.cpp b/tests/statement_serializer_tests/bindables.cpp index 27eec9f7f..b34565935 100644 --- a/tests/statement_serializer_tests/bindables.cpp +++ b/tests/statement_serializer_tests/bindables.cpp @@ -259,6 +259,26 @@ TEST_CASE("bindables") { } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("wrapped bindables") { + string value, expected; + context.replace_bindable_with_question = true; + + SECTION("labeled") { + constexpr orm_bindable_label auto label = "l"_bindable; + value = serialize(42 >>= label, context); + expected = "?"; + } + SECTION("named") { + auto bindable = "@p"_param.create(); + value = serialize(bindable, context); + expected = "@p"; + } + + REQUIRE(value == expected); + } +#endif + #if SQLITE_VERSION_NUMBER >= 3020000 SECTION("pointer binding") { string value, expected; diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 510745209..f18897967 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -95,6 +95,18 @@ TEST_CASE("column_result_of_t") { const int64 value = 0; runTest(bind_carray_pointer_statically(&value)); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + // 'bindable' label + { + constexpr orm_bindable_label auto label = "l"_bindable; + runTest(42 >>= label); + } + // named parameter + { + auto bindable = "@p"_param.create(); + runTest(bindable); + } +#endif runTest(distinct(&User::id)); runTest(distinct(&User::name)); runTest(all(&User::id)); diff --git a/tests/static_tests/node_tuple.cpp b/tests/static_tests/node_tuple.cpp index 89cf2894a..8b53a2ce5 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -6,6 +6,7 @@ using namespace sqlite_orm; using internal::alias_holder; using internal::column_alias; using internal::column_pointer; +using internal::literal_holder; template struct is_pair : std::false_type {}; @@ -58,11 +59,29 @@ TEST_CASE("Node tuple") { static_assert(is_same::value, "bindable float"); STATIC_REQUIRE(is_same, tuple>::value); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("labeled") { + constexpr orm_bindable_label auto label = "l"_bindable; + using Node = decltype(42 >>= label); + using Tuple = node_tuple_t; + using Expected = tuple; + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v, tuple>); + } + SECTION("named") { + auto bindable = "@p"_param.create(); + using Node = decltype(bindable); + using Tuple = node_tuple_t; + using Expected = tuple; + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v, tuple>); + } +#endif } SECTION("non-bindable literals") { using namespace internal; using Tuple = node_tuple_t>; - using Expected = tuple<>; + using Expected = tuple>; static_assert(is_same::value, "literal int"); STATIC_REQUIRE(is_same, tuple<>>::value); } @@ -622,7 +641,7 @@ TEST_CASE("Node tuple") { STATIC_REQUIRE(is_same, tuple>::value); } SECTION("positional ordinal") { - STATIC_REQUIRE(is_same, tuple<>>::value); + STATIC_REQUIRE(is_same, tuple>>::value); } SECTION("sole column alias") { STATIC_REQUIRE(is_same()))>, tuple<>>::value); diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index 300d50ce0..6859f4408 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -136,6 +136,9 @@ TEST_CASE("inline namespace literals expressions") { constexpr auto u_alias_builder = "u"_alias; constexpr auto c_col = "c"_col; constexpr auto f_scalar_builder = "f"_scalar; + constexpr auto n_label = "n"_bindable; + constexpr auto p1_param = ":p"_param; + constexpr auto p2_param = "@p"_param; #if SQLITE_VERSION_NUMBER >= 3020000 constexpr auto domain_ptr_tag = "domain"_pointer_type; #endif From bf3c9a8c021660f6cb9ad6ec45ded3852d70bb40 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 22 Sep 2025 12:53:52 +0300 Subject: [PATCH 3/4] Corrected AST iteration unit test for named parameter --- dev/ast_iterator.h | 1 + include/sqlite_orm/sqlite_orm.h | 2 ++ tests/ast_iterator_tests.cpp | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index fd8e75cdd..178b509da 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -27,6 +27,7 @@ #include "ast/match.h" #include "ast/cast.h" #include "ast/limit.h" +#include "ast/labeled_bindable.h" namespace sqlite_orm { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index f6f3470db..60a62162d 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15851,6 +15851,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "ast/limit.h" +// #include "ast/labeled_bindable.h" + namespace sqlite_orm { namespace internal { diff --git a/tests/ast_iterator_tests.cpp b/tests/ast_iterator_tests.cpp index 405a6cf8d..a85062ab1 100644 --- a/tests/ast_iterator_tests.cpp +++ b/tests/ast_iterator_tests.cpp @@ -65,7 +65,7 @@ TEST_CASE("ast_iterator") { SECTION("named") { auto bindable = "@p"_param.create(); auto node = select(bindable); - expected.push_back(typeid(int)); + expected.push_back(typeid(bindable)); iterate_ast(node, lambda); } #endif From ef0ec0960b86bc1b68098d69041f73f2aec13dfd Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 22 Sep 2025 15:17:59 +0300 Subject: [PATCH 4/4] Make gcc <= 13.2 happy gcc had the habit to const-qualify an auto-deduced non-type template parameter. --- dev/ast/labeled_bindable.h | 3 ++- dev/ast/named_parameter.h | 4 +++- include/sqlite_orm/sqlite_orm.h | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dev/ast/labeled_bindable.h b/dev/ast/labeled_bindable.h index e5a2b3814..843a47aa9 100644 --- a/dev/ast/labeled_bindable.h +++ b/dev/ast/labeled_bindable.h @@ -1,6 +1,7 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::remove_const #include #endif #endif @@ -79,6 +80,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** @short Specifies that a type is an integral constant C-string usable as a label for a bindable. */ template - concept orm_bindable_label = polyfill::is_specialization_of_v; + concept orm_bindable_label = polyfill::is_specialization_of_v, internal::bindable_label>; } #endif diff --git a/dev/ast/named_parameter.h b/dev/ast/named_parameter.h index 7bfb9fd00..81ba6def9 100644 --- a/dev/ast/named_parameter.h +++ b/dev/ast/named_parameter.h @@ -2,6 +2,7 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::remove_const #include #include #include @@ -82,6 +83,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** @short Specifies that a type is an integral constant C-string usable for a named parameter. */ template - concept orm_parameter_moniker = polyfill::is_specialization_of_v; + concept orm_parameter_moniker = + polyfill::is_specialization_of_v, internal::parameter_moniker>; } #endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 60a62162d..f68d64b88 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10232,6 +10232,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::remove_const #include #endif #endif @@ -10342,7 +10343,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** @short Specifies that a type is an integral constant C-string usable as a label for a bindable. */ template - concept orm_bindable_label = polyfill::is_specialization_of_v; + concept orm_bindable_label = polyfill::is_specialization_of_v, internal::bindable_label>; } #endif @@ -10350,6 +10351,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::remove_const #include #include #include @@ -10432,7 +10434,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** @short Specifies that a type is an integral constant C-string usable for a named parameter. */ template - concept orm_parameter_moniker = polyfill::is_specialization_of_v; + concept orm_parameter_moniker = + polyfill::is_specialization_of_v, internal::parameter_moniker>; } #endif