From a44d14680b72d1bc27b683275f5e708cc1e2c540 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 22 Nov 2023 01:36:05 +0200 Subject: [PATCH 01/13] Draft of generalized application-defined functions --- dev/alias.h | 37 +- dev/function.h | 185 +++++++++- dev/functional/char_array_template.h | 31 ++ dev/statement_serializer.h | 2 +- dev/storage_base.h | 93 +++-- dev/udf_proxy.h | 10 +- include/sqlite_orm/sqlite_orm.h | 359 +++++++++++++++---- tests/static_tests/function_static_tests.cpp | 8 +- tests/user_defined_functions.cpp | 80 +++++ 9 files changed, 653 insertions(+), 152 deletions(-) create mode 100644 dev/functional/char_array_template.h diff --git a/dev/alias.h b/dev/alias.h index bdc5b07f9..63fc0176a 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -1,13 +1,12 @@ #pragma once -#include // std::enable_if -#include // std::index_sequence, std::make_index_sequence +#include // std::enable_if, std::is_same, std::conditional +#include // std::make_index_sequence, std::move #include // std::string #include // std::stringstream -#include // std::copy_n -#include "functional/cxx_universal.h" // ::size_t #include "functional/cxx_type_traits_polyfill.h" +#include "functional/char_array_template.h" #include "type_traits.h" #include "alias_traits.h" #include "table_type_of.h" @@ -16,29 +15,7 @@ namespace sqlite_orm { namespace internal { - #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* - * Helper class to facilitate user-defined string literal operator template - */ - template - struct string_identifier_template { - static constexpr size_t size() { - return N - 1; - } - - constexpr string_identifier_template(const char (&id)[N]) { - std::copy_n(id, N, this->id); - } - - char id[N]; - }; - - template class Alias, string_identifier_template t, size_t... Idx> - consteval auto to_alias(std::index_sequence) { - return Alias{}; - } - template inline constexpr bool is_operator_argument_v>> = true; #endif @@ -364,18 +341,18 @@ namespace sqlite_orm { * Examples: * constexpr auto z_alias = "z"_alias.for_(); */ - template + template [[nodiscard]] consteval auto operator"" _alias() { - return internal::to_alias(std::make_index_sequence{}); + return internal::explode_into(std::make_index_sequence{}); } /** @short Create a column alias. * column_alias<'a'[, ...]> from a string literal. * E.g. "a"_col, "b"_col */ - template + template [[nodiscard]] consteval auto operator"" _col() { - return internal::to_alias(std::make_index_sequence{}); + return internal::explode_into(std::make_index_sequence{}); } #endif } diff --git a/dev/function.h b/dev/function.h index 747860aea..2ea266ac3 100644 --- a/dev/function.h +++ b/dev/function.h @@ -7,6 +7,7 @@ #include "functional/cxx_universal.h" #include "functional/cxx_type_traits_polyfill.h" +#include "functional/char_array_template.h" #include "functional/function_traits.h" #include "tags.h" @@ -60,14 +61,62 @@ namespace sqlite_orm { using return_type = function_return_type_t>; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires(std::is_function_v) + struct callable_arguments_impl { + using args_tuple = function_arguments; + using return_type = std::decay_t>; + }; +#endif + template struct callable_arguments : callable_arguments_impl {}; + template + struct function; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct named_udf : private std::string { + using udf_type = UDF; + + using std::string::basic_string; + + const std::string& operator()() const { + return *this; + } + }; +#endif + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a user-defined function. + */ + template + requires(requires { UDF::name(); }) + struct named_udf +#else + template + struct named_udf +#endif + { + using udf_type = UDF; + + decltype(auto) operator()() const { + return UDF::name(); + } + }; + + /* + * Represents a call of a user-defined function. + */ template struct function_call { using udf_type = UDF; using args_tuple = std::tuple; + named_udf name; args_tuple callArgs; }; @@ -162,6 +211,20 @@ namespace sqlite_orm { #endif } + template + SQLITE_ORM_CONSTEVAL void check_function_call() { + using args_tuple = std::tuple; + using function_args_tuple = typename callable_arguments::args_tuple; + constexpr size_t argsCount = std::tuple_size::value; + constexpr size_t functionArgsCount = std::tuple_size::value; + static_assert((argsCount == functionArgsCount && + !std::is_same>::value && + validate_pointer_value_types( + polyfill::index_constant{})) || + std::is_same>::value, + "The number of arguments does not match"); + } + /* * Generator of a user-defined function call in a sql query expression. * Use the variable template `func<>` to instantiate. @@ -169,21 +232,100 @@ namespace sqlite_orm { */ template struct function : polyfill::type_identity { + using udf_type = UDF; + template function_call operator()(CallArgs... callArgs) const { - using args_tuple = std::tuple; - using function_args_tuple = typename callable_arguments::args_tuple; - constexpr size_t argsCount = std::tuple_size::value; - constexpr size_t functionArgsCount = std::tuple_size::value; - static_assert((argsCount == functionArgsCount && - !std::is_same>::value && - validate_pointer_value_types( - polyfill::index_constant{})) || - std::is_same>::value, - "The number of arguments does not match"); - return {{std::forward(callArgs)...}}; + check_function_call(); + return {this->named_udf(), {std::forward(callArgs)...}}; + } + + constexpr auto named_udf() const { + return internal::named_udf{}; + } + + constexpr auto name() const { + return UDF::name(); } }; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + concept stateless = std::is_empty_v; + + template + concept function_object = requires { &T::operator(); }; + + /* + * Captures and represents a notably side-effect free function [pointer or `constexpr` object]. + * If `udf` is a function object, we assume it is possibly not side-effect free + * and `quoted_scalar_function::callable()` returns a copy. + */ + template + requires(stateless || std::copy_constructible) + struct quoted_scalar_function : polyfill::type_identity> { + using type = typename quoted_scalar_function::type; + + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->named_udf(), {std::forward(callArgs)...}}; + } + + /* + * Return original `udf` if stateless or a copy of it otherwise + */ + constexpr decltype(auto) callable() const { + if constexpr(stateless) { + return (udf); + } else { + return udf; + } + } + + constexpr auto named_udf() const { + return internal::named_udf{this->name()}; + } + + constexpr auto name() const { + return this->nme; + } + + consteval quoted_scalar_function(const char (&name)[N]) { + std::copy_n(name, N, this->nme); + } + + quoted_scalar_function(const quoted_scalar_function&) = delete; + quoted_scalar_function& operator=(const quoted_scalar_function&) = delete; + + char nme[N]; + }; + + template + struct quoted_function_builder { + char nme[N]; + + consteval quoted_function_builder(const char (&name)[N]) { + std::copy_n(name, N, this->nme); + } + + // from function pointer or `constexpr` object + template + requires(std::is_function_v || + (function_object && (!requires { decltype(udf)::name(); }))) + [[nodiscard]] consteval quoted_scalar_function callable() const { + return {this->nme}; + } + + // from function object type + template + requires(function_object && (!requires { UDF::name(); })) + [[nodiscard]] consteval auto make(Args&&... constructorArgs) const { + constexpr UDF udf(std::forward(constructorArgs)...); + return quoted_scalar_function{this->nme}; + } + }; +#endif } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -226,6 +368,14 @@ namespace sqlite_orm { template concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && orm_aggregate_udf); + + /** @short Specifies that a type is a framed and quoted user-defined scalar function. + */ + template + concept orm_quoted_scalar_function = requires(const Q& quotedF) { + quotedF.name(); + quotedF.callable(); + }; #endif /** @@ -245,4 +395,17 @@ namespace sqlite_orm { requires(orm_scalar_udf || orm_aggregate_udf) #endif SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* @short Create a scalar function from a function object type, a function pointer or `constexpr` object. + * + * Examples: + * constexpr auto clamp_int_f = "clamp_int"_scalar.func>(); + * constexpr auto equal_to_int_f = "equal_to_int"_scalar.func>(); + */ + template + [[nodiscard]] consteval auto operator"" _scalar() { + return builder; + } +#endif } diff --git a/dev/functional/char_array_template.h b/dev/functional/char_array_template.h new file mode 100644 index 000000000..ec98abb2f --- /dev/null +++ b/dev/functional/char_array_template.h @@ -0,0 +1,31 @@ +#pragma once + +#include // std::index_sequence +#include // std::copy_n + +#include "cxx_universal.h" // ::size_t + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * Helper class to facilitate user-defined string literal operator template + */ + template + struct char_array_template { + static constexpr size_t size() { + return N - 1; + } + + constexpr char_array_template(const char (&charArray)[N]) { + std::copy_n(charArray, N, this->id); + } + + char id[N]; + }; + + template class Template, char_array_template chars, size_t... Idx> + consteval auto explode_into(std::index_sequence) { + return Template{}; + } +} +#endif diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index dc13a87b4..b0ea3882b 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -370,7 +370,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << F::name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; + ss << statement.name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; diff --git a/dev/storage_base.h b/dev/storage_base.h index a84c37515..d9adb9ce5 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -265,11 +265,12 @@ namespace sqlite_orm { void create_scalar_function(Args&&... constructorArgs) { static_assert(is_scalar_udf_v, "F must be a scalar function"); - this->create_scalar_function_impl(/* constructAt */ [constructorArgs...](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, (F*)location, constructorArgs...); - }); + this->create_scalar_function_impl(udf_holder{}, + /* constructAt */ [constructorArgs...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -288,6 +289,37 @@ namespace sqlite_orm { void create_scalar_function(Args&&... constructorArgs) { return this->create_scalar_function>(std::forward(constructorArgs)...); } + + template + void create_scalar_function() { + using F = auto_type_t; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; + constexpr auto argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); + this->scalarFunctions.emplace_back( + std::string{quotedF.name()}, + argsCount, + /* constructAt = */ + nullptr, + /* destroy = */ + nullptr, + /* call = */ + [](void* /*udfHandle*/, sqlite3_context* context, int argsCount, sqlite3_value** values) { + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(quotedF.callable(), std::move(argsTuple)); + statement_binder().result(context, result); + }, + /* finalCall = */ + nullptr, + std::pair{nullptr, null_xdestroy_f}); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_scalar_function(db, this->scalarFunctions.back()); + } + } #endif /** @@ -326,14 +358,12 @@ namespace sqlite_orm { void create_aggregate_function(Args&&... constructorArgs) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); - this->create_aggregate_function_impl(/* constructAt = */ - [constructorArgs...](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, - (F*)location, - constructorArgs...); - }); + this->create_aggregate_function_impl(udf_holder{}, /* constructAt = */ + [constructorArgs...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -361,15 +391,19 @@ namespace sqlite_orm { template void delete_scalar_function() { static_assert(is_scalar_udf_v, "F must be a scalar function"); - std::stringstream ss; - ss << F::name() << std::flush; - this->delete_function_impl(ss.str(), this->scalarFunctions); + udf_holder udfName; + this->delete_function_impl(udfName(), this->scalarFunctions); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template void delete_scalar_function() { - this->delete_scalar_function>(); + this->delete_function_impl(f.name(), this->scalarFunctions); + } + + template + void delete_scalar_function() { + this->delete_function_impl(quotedF.name(), this->scalarFunctions); } #endif @@ -380,15 +414,14 @@ namespace sqlite_orm { template void delete_aggregate_function() { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); - std::stringstream ss; - ss << F::name() << std::flush; - this->delete_function_impl(ss.str(), this->aggregateFunctions); + udf_holder udfName; + this->delete_function_impl(udfName(), this->aggregateFunctions); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template void delete_aggregate_function() { - this->delete_aggregate_function>(); + this->delete_function_impl(f.name(), this->aggregateFunctions); } #endif @@ -652,17 +685,14 @@ namespace sqlite_orm { } template - void create_scalar_function_impl(std::function constructAt) { - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); + void create_scalar_function_impl(udf_holder udfName, std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); this->scalarFunctions.emplace_back( - std::move(name), + udfName(), argsCount, std::move(constructAt), /* destroy = */ @@ -685,17 +715,15 @@ namespace sqlite_orm { } template - void create_aggregate_function_impl(std::function constructAt) { - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); + void create_aggregate_function_impl(udf_holder udfName, + std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); this->aggregateFunctions.emplace_back( - std::move(name), + udfName(), argsCount, std::move(constructAt), /* destroy = */ @@ -764,7 +792,8 @@ namespace sqlite_orm { udfProxy.argumentsCount, SQLITE_UTF8, &udfProxy, - scalar_function_callback, + udfProxy.constructAt ? scalar_function_callback + : quoted_scalar_function_callback, nullptr, nullptr, nullptr); diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index d97f7a985..591c7f3a9 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -78,7 +78,9 @@ namespace sqlite_orm { finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { - udfMemory.second(udfMemory.first); + if(udfMemory.second) { + udfMemory.second(udfMemory.first); + } } friend void* udfHandle(udf_proxy* proxy) { @@ -133,6 +135,12 @@ namespace sqlite_orm { proxy->func(udfHandle(proxy), context, argsCount, values); } + inline void quoted_scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + check_args_count(proxy, argsCount); + proxy->func(udfHandle(proxy), context, argsCount, values); + } + inline void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); ensure_udf(proxy, argsCount); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 465dc02f2..1fbf54168 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -4535,15 +4535,45 @@ namespace sqlite_orm { } #pragma once -#include // std::enable_if -#include // std::index_sequence, std::make_index_sequence +#include // std::enable_if, std::is_same, std::conditional +#include // std::make_index_sequence, std::move #include // std::string #include // std::stringstream + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "functional/char_array_template.h" + +#include // std::index_sequence #include // std::copy_n -// #include "functional/cxx_universal.h" +// #include "cxx_universal.h" // ::size_t -// #include "functional/cxx_type_traits_polyfill.h" + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * Helper class to facilitate user-defined string literal operator template + */ + template + struct char_array_template { + static constexpr size_t size() { + return N - 1; + } + + constexpr char_array_template(const char (&charArray)[N]) { + std::copy_n(charArray, N, this->id); + } + + char id[N]; + }; + + template class Template, char_array_template chars, size_t... Idx> + consteval auto explode_into(std::index_sequence) { + return Template{}; + } +} +#endif // #include "type_traits.h" @@ -4556,29 +4586,7 @@ namespace sqlite_orm { namespace sqlite_orm { namespace internal { - #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* - * Helper class to facilitate user-defined string literal operator template - */ - template - struct string_identifier_template { - static constexpr size_t size() { - return N - 1; - } - - constexpr string_identifier_template(const char (&id)[N]) { - std::copy_n(id, N, this->id); - } - - char id[N]; - }; - - template class Alias, string_identifier_template t, size_t... Idx> - consteval auto to_alias(std::index_sequence) { - return Alias{}; - } - template inline constexpr bool is_operator_argument_v>> = true; #endif @@ -4904,18 +4912,18 @@ namespace sqlite_orm { * Examples: * constexpr auto z_alias = "z"_alias.for_(); */ - template + template [[nodiscard]] consteval auto operator"" _alias() { - return internal::to_alias(std::make_index_sequence{}); + return internal::explode_into(std::make_index_sequence{}); } /** @short Create a column alias. * column_alias<'a'[, ...]> from a string literal. * E.g. "a"_col, "b"_col */ - template + template [[nodiscard]] consteval auto operator"" _col() { - return internal::to_alias(std::make_index_sequence{}); + return internal::explode_into(std::make_index_sequence{}); } #endif } @@ -10883,6 +10891,8 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" +// #include "functional/char_array_template.h" + // #include "functional/function_traits.h" // #include "cxx_type_traits_polyfill.h" @@ -11001,14 +11011,62 @@ namespace sqlite_orm { using return_type = function_return_type_t>; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires(std::is_function_v) + struct callable_arguments_impl { + using args_tuple = function_arguments; + using return_type = std::decay_t>; + }; +#endif + template struct callable_arguments : callable_arguments_impl {}; + template + struct function; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct named_udf : private std::string { + using udf_type = UDF; + + using std::string::basic_string; + + const std::string& operator()() const { + return *this; + } + }; +#endif + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a user-defined function. + */ + template + requires(requires { UDF::name(); }) + struct named_udf +#else + template + struct named_udf +#endif + { + using udf_type = UDF; + + decltype(auto) operator()() const { + return UDF::name(); + } + }; + + /* + * Represents a call of a user-defined function. + */ template struct function_call { using udf_type = UDF; using args_tuple = std::tuple; + named_udf name; args_tuple callArgs; }; @@ -11103,6 +11161,20 @@ namespace sqlite_orm { #endif } + template + SQLITE_ORM_CONSTEVAL void check_function_call() { + using args_tuple = std::tuple; + using function_args_tuple = typename callable_arguments::args_tuple; + constexpr size_t argsCount = std::tuple_size::value; + constexpr size_t functionArgsCount = std::tuple_size::value; + static_assert((argsCount == functionArgsCount && + !std::is_same>::value && + validate_pointer_value_types( + polyfill::index_constant{})) || + std::is_same>::value, + "The number of arguments does not match"); + } + /* * Generator of a user-defined function call in a sql query expression. * Use the variable template `func<>` to instantiate. @@ -11110,21 +11182,100 @@ namespace sqlite_orm { */ template struct function : polyfill::type_identity { + using udf_type = UDF; + template function_call operator()(CallArgs... callArgs) const { - using args_tuple = std::tuple; - using function_args_tuple = typename callable_arguments::args_tuple; - constexpr size_t argsCount = std::tuple_size::value; - constexpr size_t functionArgsCount = std::tuple_size::value; - static_assert((argsCount == functionArgsCount && - !std::is_same>::value && - validate_pointer_value_types( - polyfill::index_constant{})) || - std::is_same>::value, - "The number of arguments does not match"); - return {{std::forward(callArgs)...}}; + check_function_call(); + return {this->named_udf(), {std::forward(callArgs)...}}; + } + + constexpr auto named_udf() const { + return internal::named_udf{}; + } + + constexpr auto name() const { + return UDF::name(); + } + }; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + concept stateless = std::is_empty_v; + + template + concept function_object = requires { &T::operator(); }; + + /* + * Captures and represents a notably side-effect free function [pointer or `constexpr` object]. + * If `udf` is a function object, we assume it is possibly not side-effect free + * and `quoted_scalar_function::callable()` returns a copy. + */ + template + requires(stateless || std::copy_constructible) + struct quoted_scalar_function : polyfill::type_identity> { + using type = typename quoted_scalar_function::type; + + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->named_udf(), {std::forward(callArgs)...}}; + } + + /* + * Return original `udf` if stateless or a copy of it otherwise + */ + constexpr decltype(auto) callable() const { + if constexpr(stateless) { + return (udf); + } else { + return udf; + } + } + + constexpr auto named_udf() const { + return internal::named_udf{this->name()}; + } + + constexpr auto name() const { + return this->nme; + } + + consteval quoted_scalar_function(const char (&name)[N]) { + std::copy_n(name, N, this->nme); + } + + quoted_scalar_function(const quoted_scalar_function&) = delete; + quoted_scalar_function& operator=(const quoted_scalar_function&) = delete; + + char nme[N]; + }; + + template + struct quoted_function_builder { + char nme[N]; + + consteval quoted_function_builder(const char (&name)[N]) { + std::copy_n(name, N, this->nme); + } + + // from function pointer or `constexpr` object + template + requires(std::is_function_v || + (function_object && (!requires { decltype(udf)::name(); }))) + [[nodiscard]] consteval quoted_scalar_function callable() const { + return {this->nme}; + } + + // from function object type + template + requires(function_object && (!requires { UDF::name(); })) + [[nodiscard]] consteval auto make(Args&&... constructorArgs) const { + constexpr UDF udf(std::forward(constructorArgs)...); + return quoted_scalar_function{this->nme}; } }; +#endif } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -11167,6 +11318,14 @@ namespace sqlite_orm { template concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && orm_aggregate_udf); + + /** @short Specifies that a type is a framed and quoted user-defined scalar function. + */ + template + concept orm_quoted_scalar_function = requires(const Q& quotedF) { + quotedF.name(); + quotedF.callable(); + }; #endif /** @@ -11186,6 +11345,19 @@ namespace sqlite_orm { requires(orm_scalar_udf || orm_aggregate_udf) #endif SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* @short Create a scalar function from a function object type, a function pointer or `constexpr` object. + * + * Examples: + * constexpr auto clamp_int_f = "clamp_int"_scalar.func>(); + * constexpr auto equal_to_int_f = "equal_to_int"_scalar.func>(); + */ + template + [[nodiscard]] consteval auto operator"" _scalar() { + return builder; + } +#endif } // #include "ast/special_keywords.h" @@ -14968,7 +15140,9 @@ namespace sqlite_orm { finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { - udfMemory.second(udfMemory.first); + if(udfMemory.second) { + udfMemory.second(udfMemory.first); + } } friend void* udfHandle(udf_proxy* proxy) { @@ -15023,6 +15197,12 @@ namespace sqlite_orm { proxy->func(udfHandle(proxy), context, argsCount, values); } + inline void quoted_scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + check_args_count(proxy, argsCount); + proxy->func(udfHandle(proxy), context, argsCount, values); + } + inline void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); ensure_udf(proxy, argsCount); @@ -15275,11 +15455,12 @@ namespace sqlite_orm { void create_scalar_function(Args&&... constructorArgs) { static_assert(is_scalar_udf_v, "F must be a scalar function"); - this->create_scalar_function_impl(/* constructAt */ [constructorArgs...](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, (F*)location, constructorArgs...); - }); + this->create_scalar_function_impl(udf_holder{}, + /* constructAt */ [constructorArgs...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -15298,6 +15479,37 @@ namespace sqlite_orm { void create_scalar_function(Args&&... constructorArgs) { return this->create_scalar_function>(std::forward(constructorArgs)...); } + + template + void create_scalar_function() { + using F = auto_type_t; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; + constexpr auto argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); + this->scalarFunctions.emplace_back( + std::string{quotedF.name()}, + argsCount, + /* constructAt = */ + nullptr, + /* destroy = */ + nullptr, + /* call = */ + [](void* /*udfHandle*/, sqlite3_context* context, int argsCount, sqlite3_value** values) { + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(quotedF.callable(), std::move(argsTuple)); + statement_binder().result(context, result); + }, + /* finalCall = */ + nullptr, + std::pair{nullptr, null_xdestroy_f}); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_scalar_function(db, this->scalarFunctions.back()); + } + } #endif /** @@ -15336,14 +15548,12 @@ namespace sqlite_orm { void create_aggregate_function(Args&&... constructorArgs) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); - this->create_aggregate_function_impl(/* constructAt = */ - [constructorArgs...](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, - (F*)location, - constructorArgs...); - }); + this->create_aggregate_function_impl(udf_holder{}, /* constructAt = */ + [constructorArgs...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -15371,15 +15581,19 @@ namespace sqlite_orm { template void delete_scalar_function() { static_assert(is_scalar_udf_v, "F must be a scalar function"); - std::stringstream ss; - ss << F::name() << std::flush; - this->delete_function_impl(ss.str(), this->scalarFunctions); + udf_holder udfName; + this->delete_function_impl(udfName(), this->scalarFunctions); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template void delete_scalar_function() { - this->delete_scalar_function>(); + this->delete_function_impl(f.name(), this->scalarFunctions); + } + + template + void delete_scalar_function() { + this->delete_function_impl(quotedF.name(), this->scalarFunctions); } #endif @@ -15390,15 +15604,14 @@ namespace sqlite_orm { template void delete_aggregate_function() { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); - std::stringstream ss; - ss << F::name() << std::flush; - this->delete_function_impl(ss.str(), this->aggregateFunctions); + udf_holder udfName; + this->delete_function_impl(udfName(), this->aggregateFunctions); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template void delete_aggregate_function() { - this->delete_aggregate_function>(); + this->delete_function_impl(f.name(), this->aggregateFunctions); } #endif @@ -15662,17 +15875,14 @@ namespace sqlite_orm { } template - void create_scalar_function_impl(std::function constructAt) { - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); + void create_scalar_function_impl(udf_holder udfName, std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); this->scalarFunctions.emplace_back( - std::move(name), + udfName(), argsCount, std::move(constructAt), /* destroy = */ @@ -15695,17 +15905,15 @@ namespace sqlite_orm { } template - void create_aggregate_function_impl(std::function constructAt) { - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); + void create_aggregate_function_impl(udf_holder udfName, + std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); this->aggregateFunctions.emplace_back( - std::move(name), + udfName(), argsCount, std::move(constructAt), /* destroy = */ @@ -15774,7 +15982,8 @@ namespace sqlite_orm { udfProxy.argumentsCount, SQLITE_UTF8, &udfProxy, - scalar_function_callback, + udfProxy.constructAt ? scalar_function_callback + : quoted_scalar_function_callback, nullptr, nullptr, nullptr); @@ -16649,7 +16858,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << F::name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; + ss << statement.name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index da78a04d9..d6fa9f1bf 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -239,11 +239,15 @@ TEST_CASE("function static") { } SECTION("function call expressions") { struct SFunction { - static const char* name(); + static const char* name() { + return ""; + } int operator()(int) const; }; struct AFunction { - static const char* name(); + static const char* name() { + return ""; + } void step(int); int fin() const; }; diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 1b9e23f8a..9713e86bc 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -396,3 +396,83 @@ TEST_CASE("custom functions") { } storage.delete_aggregate_function(); } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline int ERR_FATAL_ERROR(unsigned long errcode) { + return errcode != 0; +} + +struct stateful_scalar { + int offset; + + int operator()(int x) noexcept { + return offset += x; + } + + constexpr stateful_scalar(int offset = 0) : offset{offset} {} + stateful_scalar(const stateful_scalar&) = default; +}; +inline constexpr stateful_scalar offset0{}; + +TEST_CASE("generalized udf") { + auto storage = make_storage(""); + storage.sync_schema(); + + constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.callable(); + storage.create_scalar_function(); + { + auto rows = storage.select(err_fatal_error_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + constexpr auto err_fatal_error_2_f = "ERR_FATAL_ERROR_2"_scalar.callable<[](unsigned long errcode) { + return errcode != 0; + }>(); + storage.create_scalar_function(); + { + auto rows = storage.select(err_fatal_error_2_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + constexpr auto equal_to_int_f = "equal_to"_scalar.callable{}>(); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + constexpr auto equal_to_int_2_f = "equal_to"_scalar.make>(); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_2_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + constexpr auto clamp_int_f = "clamp_int"_scalar.callable>(); + storage.create_scalar_function(); + { + auto rows = storage.select(clamp_int_f(0, 1, 1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + constexpr auto offset0_f = "offset0"_scalar.callable(); + storage.create_scalar_function(); + { + auto rows = storage.select(offset0_f(1)); + rows = storage.select(offset0_f(1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); +} +#endif From 80cd6c1b511ba36905aeb9ef67368bcef227e0cd Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 22 Nov 2023 21:18:14 +0200 Subject: [PATCH 02/13] Used runtime syntax for generalized application-defined functions --- dev/function.h | 235 ++++++++++++++++------------ dev/storage_base.h | 18 +++ dev/type_traits.h | 7 +- include/sqlite_orm/sqlite_orm.h | 261 +++++++++++++++++++------------ tests/user_defined_functions.cpp | 41 +++-- 5 files changed, 354 insertions(+), 208 deletions(-) diff --git a/dev/function.h b/dev/function.h index 2ea266ac3..bf4e86181 100644 --- a/dev/function.h +++ b/dev/function.h @@ -9,6 +9,7 @@ #include "functional/cxx_type_traits_polyfill.h" #include "functional/char_array_template.h" #include "functional/function_traits.h" +#include "type_traits.h" #include "tags.h" namespace sqlite_orm { @@ -46,6 +47,67 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; + template + struct function; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + concept orm_classic_function_object = + ((!requires { typename F::is_transparent; }) && (requires { &F::operator(); }) && + /*rule out sqlite_orm scalar function*/ + (!requires { F::name(); })); + + /** @short Specifies that a type is a user-defined scalar function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::operator()()` call operator + */ + template + concept orm_scalar_udf = requires { + UDF::name(); + typename internal::scalar_call_function_t; + }; + + /** @short Specifies that a type is a user-defined aggregate function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::step()` member function + * - `UDF::fin()` member function + */ + template + concept orm_aggregate_udf = requires { + UDF::name(); + typename internal::aggregate_step_function_t; + typename internal::aggregate_fin_function_t; + requires std::is_member_function_pointer_v>; + requires std::is_member_function_pointer_v>; + }; + + /** @short Specifies that a type is a framed user-defined scalar function. + */ + template + concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && + orm_scalar_udf); + + /** @short Specifies that a type is a framed user-defined aggregate function. + */ + template + concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && + orm_aggregate_udf); + + /** @short Specifies that a type is a framed and quoted user-defined scalar function. + */ + template + concept orm_quoted_scalar_function = requires(const Q& quotedF) { + quotedF.name(); + quotedF.callable(); + }; +#endif + + namespace internal { template struct callable_arguments_impl; @@ -73,12 +135,12 @@ namespace sqlite_orm { template struct callable_arguments : callable_arguments_impl {}; - template - struct function; - #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a quoted user-defined function. + */ template - struct named_udf : private std::string { + struct udf_holder : private std::string { using udf_type = UDF; using std::string::basic_string; @@ -91,21 +153,31 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /* - * Bundle of type and name of a user-defined function. + * Bundle of type and name of a traditional sqlite_orm user-defined function. */ template requires(requires { UDF::name(); }) - struct named_udf + struct udf_holder #else + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ template - struct named_udf + struct udf_holder #endif { using udf_type = UDF; + template>, bool> = true> decltype(auto) operator()() const { return UDF::name(); } + + template::value, bool> = true> + decltype(auto) operator()() const { + return std::string(UDF::name()); + } }; /* @@ -116,7 +188,7 @@ namespace sqlite_orm { using udf_type = UDF; using args_tuple = std::tuple; - named_udf name; + udf_holder name; args_tuple callArgs; }; @@ -212,7 +284,11 @@ namespace sqlite_orm { } template - SQLITE_ORM_CONSTEVAL void check_function_call() { +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + SQLITE_ORM_CONSTEVAL +#endif + void + check_function_call() { using args_tuple = std::tuple; using function_args_tuple = typename callable_arguments::args_tuple; constexpr size_t argsCount = std::tuple_size::value; @@ -227,7 +303,9 @@ namespace sqlite_orm { /* * Generator of a user-defined function call in a sql query expression. + * * Use the variable template `func<>` to instantiate. + * * Calling the function captures the parameters in a `function_call` node. */ template @@ -237,11 +315,11 @@ namespace sqlite_orm { template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->named_udf(), {std::forward(callArgs)...}}; + return {this->udf_holder(), {std::forward(callArgs)...}}; } - constexpr auto named_udf() const { - return internal::named_udf{}; + constexpr auto udf_holder() const { + return internal::udf_holder{}; } constexpr auto name() const { @@ -250,26 +328,27 @@ namespace sqlite_orm { }; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template - concept stateless = std::is_empty_v; - - template - concept function_object = requires { &T::operator(); }; - /* - * Captures and represents a notably side-effect free function [pointer or `constexpr` object]. - * If `udf` is a function object, we assume it is possibly not side-effect free - * and `quoted_scalar_function::callable()` returns a copy. + * Generator of a user-defined function call in a sql query expression. + * + * Use the string literal operator template `""_scalar.from()` to quote + * a freestanding function, stateless lambda or classic function object. + * + * Calling the function captures the parameters in a `function_call` node. + * + * Internal note: + * Captures and represents a function [pointer or object], especially one without side effects. + * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, + * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. */ - template - requires(stateless || std::copy_constructible) - struct quoted_scalar_function : polyfill::type_identity> { + template + struct quoted_scalar_function : polyfill::type_identity> { using type = typename quoted_scalar_function::type; template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->named_udf(), {std::forward(callArgs)...}}; + return {this->udf_holder(), {std::forward(callArgs)...}}; } /* @@ -277,27 +356,31 @@ namespace sqlite_orm { */ constexpr decltype(auto) callable() const { if constexpr(stateless) { - return (udf); + return (this->udf); } else { - return udf; + // non-const copy + return F(this->udf); } } - constexpr auto named_udf() const { - return internal::named_udf{this->name()}; + constexpr auto udf_holder() const { + return internal::udf_holder{this->name()}; } constexpr auto name() const { return this->nme; } - consteval quoted_scalar_function(const char (&name)[N]) { + template + consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : + udf(std::forward(constructorArgs)...) { std::copy_n(name, N, this->nme); } quoted_scalar_function(const quoted_scalar_function&) = delete; quoted_scalar_function& operator=(const quoted_scalar_function&) = delete; + F udf; char nme[N]; }; @@ -309,77 +392,29 @@ namespace sqlite_orm { std::copy_n(name, N, this->nme); } - // from function pointer or `constexpr` object - template - requires(std::is_function_v || - (function_object && (!requires { decltype(udf)::name(); }))) - [[nodiscard]] consteval quoted_scalar_function callable() const { - return {this->nme}; + /* + * From function pointer or object. + */ + template + requires(std::is_function_v> || + (orm_classic_function_object && (stateless || std::copy_constructible))) + [[nodiscard]] consteval auto from(F callable) const { + return quoted_scalar_function{this->nme, std::move(callable)}; } - // from function object type - template - requires(function_object && (!requires { UDF::name(); })) - [[nodiscard]] consteval auto make(Args&&... constructorArgs) const { - constexpr UDF udf(std::forward(constructorArgs)...); - return quoted_scalar_function{this->nme}; + /* + * From function object type. + */ + template + requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; } }; #endif } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /** @short Specifies that a type is a user-defined scalar function. - * - * `UDF` must meet the following requirements: - * - `UDF::name()` static function - * - `UDF::operator()()` call operator - */ - template - concept orm_scalar_udf = requires { - UDF::name(); - typename internal::scalar_call_function_t; - }; - - /** @short Specifies that a type is a user-defined aggregate function. - * - * `UDF` must meet the following requirements: - * - `UDF::name()` static function - * - `UDF::step()` member function - * - `UDF::fin()` member function - */ - template - concept orm_aggregate_udf = requires { - UDF::name(); - typename internal::aggregate_step_function_t; - typename internal::aggregate_fin_function_t; - requires std::is_member_function_pointer_v>; - requires std::is_member_function_pointer_v>; - }; - - /** @short Specifies that a type is a framed user-defined scalar function. - */ - template - concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && - orm_scalar_udf); - - /** @short Specifies that a type is a framed user-defined aggregate function. - */ - template - concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && - orm_aggregate_udf); - - /** @short Specifies that a type is a framed and quoted user-defined scalar function. - */ - template - concept orm_quoted_scalar_function = requires(const Q& quotedF) { - quotedF.name(); - quotedF.callable(); - }; -#endif - - /** - * Call a user-defined function. + /** @short Call a user-defined function. * * Example: * struct IdFunc { int oeprator(int arg)() const { return arg; } }; @@ -397,11 +432,17 @@ namespace sqlite_orm { SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* @short Create a scalar function from a function object type, a function pointer or `constexpr` object. + /* @short Create a scalar function from a freestanding function, stateless lambda or classic function object, + * and call such a user-defined function. * * Examples: - * constexpr auto clamp_int_f = "clamp_int"_scalar.func>(); - * constexpr auto equal_to_int_f = "equal_to_int"_scalar.func>(); + * constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); + * constexpr auto equal_to_int_f = "equal_to_int"_scalar.from>(); + * constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.from([](unsigned long errcode) { + * return errcode != 0; + * }); + * select(clamp_int_f(0, 1, 1)); + * select(is_fatal_error_f(1)); */ template [[nodiscard]] consteval auto operator"" _scalar() { diff --git a/dev/storage_base.h b/dev/storage_base.h index d9adb9ce5..e4df54ec1 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -290,6 +290,16 @@ namespace sqlite_orm { return this->create_scalar_function>(std::forward(constructorArgs)...); } + /** + * Create an application-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * If `quotedF` contains a freestanding function, stateless lambda or stateless classic function object, + * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; + * otherwise, it repeatedly uses a copy of the contained classic function object, assuming possible side effects. + * + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + */ template void create_scalar_function() { using F = auto_type_t; @@ -396,11 +406,19 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Delete a scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. + */ template void delete_scalar_function() { this->delete_function_impl(f.name(), this->scalarFunctions); } + /** + * Delete a quoted scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. + */ template void delete_scalar_function() { this->delete_function_impl(quotedF.name(), this->scalarFunctions); diff --git a/dev/type_traits.h b/dev/type_traits.h index 1316adda1..9d3b9ae35 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -1,6 +1,6 @@ #pragma once -#include // std::enable_if, std::is_same +#include // std::enable_if, std::is_same, std::is_empty #include "functional/cxx_type_traits_polyfill.h" @@ -71,6 +71,11 @@ namespace sqlite_orm { template using on_type_t = typename T::on_type; + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept stateless = std::is_empty_v; +#endif } #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 1fbf54168..b44f697b5 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -220,7 +220,7 @@ using std::nullptr_t; #endif #pragma once -#include // std::enable_if, std::is_same +#include // std::enable_if, std::is_same, std::is_empty // #include "functional/cxx_type_traits_polyfill.h" @@ -436,6 +436,11 @@ namespace sqlite_orm { template using on_type_t = typename T::on_type; + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept stateless = std::is_empty_v; +#endif } #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED @@ -10959,6 +10964,8 @@ namespace sqlite_orm { } } +// #include "type_traits.h" + // #include "tags.h" namespace sqlite_orm { @@ -10996,6 +11003,67 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; + template + struct function; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + concept orm_classic_function_object = + ((!requires { typename F::is_transparent; }) && (requires { &F::operator(); }) && + /*rule out sqlite_orm scalar function*/ + (!requires { F::name(); })); + + /** @short Specifies that a type is a user-defined scalar function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::operator()()` call operator + */ + template + concept orm_scalar_udf = requires { + UDF::name(); + typename internal::scalar_call_function_t; + }; + + /** @short Specifies that a type is a user-defined aggregate function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::step()` member function + * - `UDF::fin()` member function + */ + template + concept orm_aggregate_udf = requires { + UDF::name(); + typename internal::aggregate_step_function_t; + typename internal::aggregate_fin_function_t; + requires std::is_member_function_pointer_v>; + requires std::is_member_function_pointer_v>; + }; + + /** @short Specifies that a type is a framed user-defined scalar function. + */ + template + concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && + orm_scalar_udf); + + /** @short Specifies that a type is a framed user-defined aggregate function. + */ + template + concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && + orm_aggregate_udf); + + /** @short Specifies that a type is a framed and quoted user-defined scalar function. + */ + template + concept orm_quoted_scalar_function = requires(const Q& quotedF) { + quotedF.name(); + quotedF.callable(); + }; +#endif + + namespace internal { template struct callable_arguments_impl; @@ -11023,12 +11091,12 @@ namespace sqlite_orm { template struct callable_arguments : callable_arguments_impl {}; - template - struct function; - #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a quoted user-defined function. + */ template - struct named_udf : private std::string { + struct udf_holder : private std::string { using udf_type = UDF; using std::string::basic_string; @@ -11041,21 +11109,31 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /* - * Bundle of type and name of a user-defined function. + * Bundle of type and name of a traditional sqlite_orm user-defined function. */ template requires(requires { UDF::name(); }) - struct named_udf + struct udf_holder #else + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ template - struct named_udf + struct udf_holder #endif { using udf_type = UDF; + template>, bool> = true> decltype(auto) operator()() const { return UDF::name(); } + + template::value, bool> = true> + decltype(auto) operator()() const { + return std::string(UDF::name()); + } }; /* @@ -11066,7 +11144,7 @@ namespace sqlite_orm { using udf_type = UDF; using args_tuple = std::tuple; - named_udf name; + udf_holder name; args_tuple callArgs; }; @@ -11162,7 +11240,11 @@ namespace sqlite_orm { } template - SQLITE_ORM_CONSTEVAL void check_function_call() { +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + SQLITE_ORM_CONSTEVAL +#endif + void + check_function_call() { using args_tuple = std::tuple; using function_args_tuple = typename callable_arguments::args_tuple; constexpr size_t argsCount = std::tuple_size::value; @@ -11177,7 +11259,9 @@ namespace sqlite_orm { /* * Generator of a user-defined function call in a sql query expression. + * * Use the variable template `func<>` to instantiate. + * * Calling the function captures the parameters in a `function_call` node. */ template @@ -11187,11 +11271,11 @@ namespace sqlite_orm { template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->named_udf(), {std::forward(callArgs)...}}; + return {this->udf_holder(), {std::forward(callArgs)...}}; } - constexpr auto named_udf() const { - return internal::named_udf{}; + constexpr auto udf_holder() const { + return internal::udf_holder{}; } constexpr auto name() const { @@ -11200,26 +11284,27 @@ namespace sqlite_orm { }; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template - concept stateless = std::is_empty_v; - - template - concept function_object = requires { &T::operator(); }; - /* - * Captures and represents a notably side-effect free function [pointer or `constexpr` object]. - * If `udf` is a function object, we assume it is possibly not side-effect free - * and `quoted_scalar_function::callable()` returns a copy. + * Generator of a user-defined function call in a sql query expression. + * + * Use the string literal operator template `""_scalar.from()` to quote + * a freestanding function, stateless lambda or classic function object. + * + * Calling the function captures the parameters in a `function_call` node. + * + * Internal note: + * Captures and represents a function [pointer or object], especially one without side effects. + * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, + * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. */ - template - requires(stateless || std::copy_constructible) - struct quoted_scalar_function : polyfill::type_identity> { + template + struct quoted_scalar_function : polyfill::type_identity> { using type = typename quoted_scalar_function::type; template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->named_udf(), {std::forward(callArgs)...}}; + return {this->udf_holder(), {std::forward(callArgs)...}}; } /* @@ -11227,27 +11312,31 @@ namespace sqlite_orm { */ constexpr decltype(auto) callable() const { if constexpr(stateless) { - return (udf); + return (this->udf); } else { - return udf; + // non-const copy + return F(this->udf); } } - constexpr auto named_udf() const { - return internal::named_udf{this->name()}; + constexpr auto udf_holder() const { + return internal::udf_holder{this->name()}; } constexpr auto name() const { return this->nme; } - consteval quoted_scalar_function(const char (&name)[N]) { + template + consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : + udf(std::forward(constructorArgs)...) { std::copy_n(name, N, this->nme); } quoted_scalar_function(const quoted_scalar_function&) = delete; quoted_scalar_function& operator=(const quoted_scalar_function&) = delete; + F udf; char nme[N]; }; @@ -11259,77 +11348,29 @@ namespace sqlite_orm { std::copy_n(name, N, this->nme); } - // from function pointer or `constexpr` object - template - requires(std::is_function_v || - (function_object && (!requires { decltype(udf)::name(); }))) - [[nodiscard]] consteval quoted_scalar_function callable() const { - return {this->nme}; + /* + * From function pointer or object. + */ + template + requires(std::is_function_v> || + (orm_classic_function_object && (stateless || std::copy_constructible))) + [[nodiscard]] consteval auto from(F callable) const { + return quoted_scalar_function{this->nme, std::move(callable)}; } - // from function object type - template - requires(function_object && (!requires { UDF::name(); })) - [[nodiscard]] consteval auto make(Args&&... constructorArgs) const { - constexpr UDF udf(std::forward(constructorArgs)...); - return quoted_scalar_function{this->nme}; + /* + * From function object type. + */ + template + requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; } }; #endif } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /** @short Specifies that a type is a user-defined scalar function. - * - * `UDF` must meet the following requirements: - * - `UDF::name()` static function - * - `UDF::operator()()` call operator - */ - template - concept orm_scalar_udf = requires { - UDF::name(); - typename internal::scalar_call_function_t; - }; - - /** @short Specifies that a type is a user-defined aggregate function. - * - * `UDF` must meet the following requirements: - * - `UDF::name()` static function - * - `UDF::step()` member function - * - `UDF::fin()` member function - */ - template - concept orm_aggregate_udf = requires { - UDF::name(); - typename internal::aggregate_step_function_t; - typename internal::aggregate_fin_function_t; - requires std::is_member_function_pointer_v>; - requires std::is_member_function_pointer_v>; - }; - - /** @short Specifies that a type is a framed user-defined scalar function. - */ - template - concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && - orm_scalar_udf); - - /** @short Specifies that a type is a framed user-defined aggregate function. - */ - template - concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && - orm_aggregate_udf); - - /** @short Specifies that a type is a framed and quoted user-defined scalar function. - */ - template - concept orm_quoted_scalar_function = requires(const Q& quotedF) { - quotedF.name(); - quotedF.callable(); - }; -#endif - - /** - * Call a user-defined function. + /** @short Call a user-defined function. * * Example: * struct IdFunc { int oeprator(int arg)() const { return arg; } }; @@ -11347,11 +11388,17 @@ namespace sqlite_orm { SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* @short Create a scalar function from a function object type, a function pointer or `constexpr` object. + /* @short Create a scalar function from a freestanding function, stateless lambda or classic function object, + * and call such a user-defined function. * * Examples: - * constexpr auto clamp_int_f = "clamp_int"_scalar.func>(); - * constexpr auto equal_to_int_f = "equal_to_int"_scalar.func>(); + * constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); + * constexpr auto equal_to_int_f = "equal_to_int"_scalar.from>(); + * constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.from([](unsigned long errcode) { + * return errcode != 0; + * }); + * select(clamp_int_f(0, 1, 1)); + * select(is_fatal_error_f(1)); */ template [[nodiscard]] consteval auto operator"" _scalar() { @@ -15480,6 +15527,16 @@ namespace sqlite_orm { return this->create_scalar_function>(std::forward(constructorArgs)...); } + /** + * Create an application-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * If `quotedF` contains a freestanding function, stateless lambda or stateless classic function object, + * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; + * otherwise, it repeatedly uses a copy of the contained classic function object, assuming possible side effects. + * + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + */ template void create_scalar_function() { using F = auto_type_t; @@ -15586,11 +15643,19 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Delete a scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. + */ template void delete_scalar_function() { this->delete_function_impl(f.name(), this->scalarFunctions); } + /** + * Delete a quoted scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. + */ template void delete_scalar_function() { this->delete_function_impl(quotedF.name(), this->scalarFunctions); diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 9713e86bc..a11128dce 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -402,6 +402,15 @@ inline int ERR_FATAL_ERROR(unsigned long errcode) { return errcode != 0; } +struct noncopyable_scalar { + int operator()(int x) const noexcept { + return x; + } + + constexpr noncopyable_scalar() = default; + noncopyable_scalar(const noncopyable_scalar&) = delete; +}; + struct stateful_scalar { int offset; @@ -410,15 +419,14 @@ struct stateful_scalar { } constexpr stateful_scalar(int offset = 0) : offset{offset} {} - stateful_scalar(const stateful_scalar&) = default; }; inline constexpr stateful_scalar offset0{}; -TEST_CASE("generalized udf") { +TEST_CASE("generalized scalar udf") { auto storage = make_storage(""); storage.sync_schema(); - constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.callable(); + constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.from(ERR_FATAL_ERROR); storage.create_scalar_function(); { auto rows = storage.select(err_fatal_error_f(1)); @@ -427,18 +435,18 @@ TEST_CASE("generalized udf") { } storage.delete_scalar_function(); - constexpr auto err_fatal_error_2_f = "ERR_FATAL_ERROR_2"_scalar.callable<[](unsigned long errcode) { + constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.from([](unsigned long errcode) { return errcode != 0; - }>(); - storage.create_scalar_function(); + }); + storage.create_scalar_function(); { - auto rows = storage.select(err_fatal_error_2_f(1)); + auto rows = storage.select(is_fatal_error_f(1)); decltype(rows) expected{true}; REQUIRE(rows == expected); } - storage.delete_scalar_function(); + storage.delete_scalar_function(); - constexpr auto equal_to_int_f = "equal_to"_scalar.callable{}>(); + constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); storage.create_scalar_function(); { auto rows = storage.select(equal_to_int_f(1, 1)); @@ -447,7 +455,7 @@ TEST_CASE("generalized udf") { } storage.delete_scalar_function(); - constexpr auto equal_to_int_2_f = "equal_to"_scalar.make>(); + constexpr auto equal_to_int_2_f = "equal_to"_scalar.from>(); storage.create_scalar_function(); { auto rows = storage.select(equal_to_int_2_f(1, 1)); @@ -456,7 +464,7 @@ TEST_CASE("generalized udf") { } storage.delete_scalar_function(); - constexpr auto clamp_int_f = "clamp_int"_scalar.callable>(); + constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); storage.create_scalar_function(); { auto rows = storage.select(clamp_int_f(0, 1, 1)); @@ -465,7 +473,16 @@ TEST_CASE("generalized udf") { } storage.delete_scalar_function(); - constexpr auto offset0_f = "offset0"_scalar.callable(); + constexpr auto idfunc_f = "idfunc"_scalar.from(); + storage.create_scalar_function(); + { + auto rows = storage.select(idfunc_f(1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + constexpr auto offset0_f = "offset0"_scalar.from(offset0); storage.create_scalar_function(); { auto rows = storage.select(offset0_f(1)); From 3a9149887c2af41f56e7d56b0512b6f51a2b565d Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 22 Nov 2023 22:33:33 +0200 Subject: [PATCH 03/13] appveyor: Updated vcpkg environment to 2023.11.20 --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6bc22fb89..21f2654b5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -107,7 +107,7 @@ for: install: - |- cd C:\Tools\vcpkg - git fetch --tags && git checkout 2023.10.19 + git fetch --tags && git checkout 2023.11.20 cd %APPVEYOR_BUILD_FOLDER% C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics C:\Tools\vcpkg\vcpkg integrate install @@ -140,7 +140,7 @@ for: install: - |- pushd $HOME/vcpkg - git fetch --tags && git checkout 2023.10.19 + git fetch --tags && git checkout 2023.11.20 popd $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets @@ -168,7 +168,7 @@ for: # using custom vcpkg triplets for building and linking dynamic dependent libraries install: - |- - git clone --depth 1 --branch 2023.10.19 https://github.com/microsoft/vcpkg.git $HOME/vcpkg + git clone --depth 1 --branch 2023.11.20 https://github.com/microsoft/vcpkg.git $HOME/vcpkg $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets vcpkg install sqlite3[core,dbstat,math,json1,fts5] catch2 --overlay-triplets=vcpkg/triplets From 2a030793492625b82c84b975acf5fe3671fb1628 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 23 Nov 2023 10:04:31 +0200 Subject: [PATCH 04/13] Took care of user-defined function `name()` returning a character --- dev/function.h | 11 ++++++----- include/sqlite_orm/sqlite_orm.h | 11 ++++++----- tests/user_defined_functions.cpp | 5 ++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/dev/function.h b/dev/function.h index bf4e86181..cd5bd8e51 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,8 +1,9 @@ #pragma once -#include // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type +#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::remove_pointer, std::decay, std::is_same, std::false_type, std::true_type +#include // std::copy_constructible #include // std::tuple, std::tuple_size, std::tuple_element -#include // std::min +#include // std::min, std::copy_n #include // std::move, std::forward #include "functional/cxx_universal.h" @@ -175,8 +176,8 @@ namespace sqlite_orm { } template::value, bool> = true> - decltype(auto) operator()() const { - return std::string(UDF::name()); + std::string operator()() const { + return std::string{UDF::name()}; } }; @@ -323,7 +324,7 @@ namespace sqlite_orm { } constexpr auto name() const { - return UDF::name(); + return this->udf_holder()(); } }; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index b44f697b5..a16642fd6 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10887,9 +10887,10 @@ namespace sqlite_orm { // #include "function.h" -#include // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type +#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::remove_pointer, std::decay, std::is_same, std::false_type, std::true_type +#include // std::copy_constructible #include // std::tuple, std::tuple_size, std::tuple_element -#include // std::min +#include // std::min, std::copy_n #include // std::move, std::forward // #include "functional/cxx_universal.h" @@ -11131,8 +11132,8 @@ namespace sqlite_orm { } template::value, bool> = true> - decltype(auto) operator()() const { - return std::string(UDF::name()); + std::string operator()() const { + return std::string{UDF::name()}; } }; @@ -11279,7 +11280,7 @@ namespace sqlite_orm { } constexpr auto name() const { - return UDF::name(); + return this->udf_holder()(); } }; diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index a11128dce..e25aa0c2f 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -485,9 +485,8 @@ TEST_CASE("generalized scalar udf") { constexpr auto offset0_f = "offset0"_scalar.from(offset0); storage.create_scalar_function(); { - auto rows = storage.select(offset0_f(1)); - rows = storage.select(offset0_f(1)); - decltype(rows) expected{1}; + auto rows = storage.select(columns(offset0_f(1), offset0_f(1))); + decltype(rows) expected{{1, 1}}; REQUIRE(rows == expected); } storage.delete_scalar_function(); From b3cd397235f80b1a1aeda7b918cc88fbdc8fc043 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 23 Nov 2023 15:44:23 +0200 Subject: [PATCH 05/13] Cleanup --- dev/function.h | 12 +++++++----- dev/functional/function_traits.h | 1 + include/sqlite_orm/sqlite_orm.h | 12 +++++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dev/function.h b/dev/function.h index cd5bd8e51..49737d77b 100644 --- a/dev/function.h +++ b/dev/function.h @@ -311,8 +311,9 @@ namespace sqlite_orm { */ template struct function : polyfill::type_identity { - using udf_type = UDF; - + /* + * Generates the SQL function call. + */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); @@ -323,6 +324,7 @@ namespace sqlite_orm { return internal::udf_holder{}; } + // returns a character range constexpr auto name() const { return this->udf_holder()(); } @@ -346,6 +348,9 @@ namespace sqlite_orm { struct quoted_scalar_function : polyfill::type_identity> { using type = typename quoted_scalar_function::type; + /* + * Generates the SQL function call. + */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); @@ -378,9 +383,6 @@ namespace sqlite_orm { std::copy_n(name, N, this->nme); } - quoted_scalar_function(const quoted_scalar_function&) = delete; - quoted_scalar_function& operator=(const quoted_scalar_function&) = delete; - F udf; char nme[N]; }; diff --git a/dev/functional/function_traits.h b/dev/functional/function_traits.h index 5668001b3..54c5bf4c3 100644 --- a/dev/functional/function_traits.h +++ b/dev/functional/function_traits.h @@ -1,4 +1,5 @@ #pragma once + #include "cxx_type_traits_polyfill.h" #include "mpl.h" diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index a16642fd6..04050733d 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -11267,8 +11267,9 @@ namespace sqlite_orm { */ template struct function : polyfill::type_identity { - using udf_type = UDF; - + /* + * Generates the SQL function call. + */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); @@ -11279,6 +11280,7 @@ namespace sqlite_orm { return internal::udf_holder{}; } + // returns a character range constexpr auto name() const { return this->udf_holder()(); } @@ -11302,6 +11304,9 @@ namespace sqlite_orm { struct quoted_scalar_function : polyfill::type_identity> { using type = typename quoted_scalar_function::type; + /* + * Generates the SQL function call. + */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); @@ -11334,9 +11339,6 @@ namespace sqlite_orm { std::copy_n(name, N, this->nme); } - quoted_scalar_function(const quoted_scalar_function&) = delete; - quoted_scalar_function& operator=(const quoted_scalar_function&) = delete; - F udf; char nme[N]; }; From 0a9bec5fe0ffa898c0b12ca5b66f8ae79f0a42d1 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 23 Nov 2023 16:11:02 +0200 Subject: [PATCH 06/13] Guarded inclusion of `` header file --- dev/function.h | 2 ++ include/sqlite_orm/sqlite_orm.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev/function.h b/dev/function.h index 49737d77b..0bc13a540 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,7 +1,9 @@ #pragma once #include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::remove_pointer, std::decay, std::is_same, std::false_type, std::true_type +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include // std::copy_constructible +#endif #include // std::tuple, std::tuple_size, std::tuple_element #include // std::min, std::copy_n #include // std::move, std::forward diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 04050733d..d8882eb36 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10888,7 +10888,9 @@ namespace sqlite_orm { // #include "function.h" #include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::remove_pointer, std::decay, std::is_same, std::false_type, std::true_type +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include // std::copy_constructible +#endif #include // std::tuple, std::tuple_size, std::tuple_element #include // std::min, std::copy_n #include // std::move, std::forward From 436775d7f85d778c3cb3c89347d5eee62710ebcb Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 23 Nov 2023 20:17:16 +0200 Subject: [PATCH 07/13] Corrected stateless test in `quoted_scalar_function::callable()` --- dev/function.h | 2 +- include/sqlite_orm/sqlite_orm.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/function.h b/dev/function.h index 0bc13a540..16bd7958f 100644 --- a/dev/function.h +++ b/dev/function.h @@ -363,7 +363,7 @@ namespace sqlite_orm { * Return original `udf` if stateless or a copy of it otherwise */ constexpr decltype(auto) callable() const { - if constexpr(stateless) { + if constexpr(stateless) { return (this->udf); } else { // non-const copy diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index d8882eb36..793f3f601 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -11319,7 +11319,7 @@ namespace sqlite_orm { * Return original `udf` if stateless or a copy of it otherwise */ constexpr decltype(auto) callable() const { - if constexpr(stateless) { + if constexpr(stateless) { return (this->udf); } else { // non-const copy From cc7c4f64e377ed8ad9ec5f4fc65d94e9b4282b98 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 23 Nov 2023 20:52:53 +0200 Subject: [PATCH 08/13] Ability to quote an overloaded function as a scalar function --- dev/function.h | 126 +++++++++++++++++---- dev/functional/function_traits.h | 34 +++++- dev/storage_base.h | 14 +-- dev/type_traits.h | 8 ++ include/sqlite_orm/sqlite_orm.h | 182 +++++++++++++++++++++++++------ tests/user_defined_functions.cpp | 152 +++++++++++++++++--------- 6 files changed, 393 insertions(+), 123 deletions(-) diff --git a/dev/function.h b/dev/function.h index 16bd7958f..bc6280fe1 100644 --- a/dev/function.h +++ b/dev/function.h @@ -55,6 +55,17 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Specifies that a type is a function signature (i.e. a function in the C++ type system). + */ + template + concept orm_function_sig = std::is_function_v; + + /** @short Specifies that a type is a classic function object. + * + * A classic function object meets the following requirements: + * - defines a single call operator `F::operator()` + * - isn't a traditional sqlite_orm scalar function (having a static `F::name()` function + */ template concept orm_classic_function_object = ((!requires { typename F::is_transparent; }) && (requires { &F::operator(); }) && @@ -93,13 +104,13 @@ namespace sqlite_orm { */ template concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && - orm_scalar_udf); + orm_scalar_udf); /** @short Specifies that a type is a framed user-defined aggregate function. */ template concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && - orm_aggregate_udf); + orm_aggregate_udf); /** @short Specifies that a type is a framed and quoted user-defined scalar function. */ @@ -312,7 +323,10 @@ namespace sqlite_orm { * Calling the function captures the parameters in a `function_call` node. */ template - struct function : polyfill::type_identity { + struct function { + using udf_type = UDF; + using callable_type = UDF; + /* * Generates the SQL function call. */ @@ -337,25 +351,28 @@ namespace sqlite_orm { * Generator of a user-defined function call in a sql query expression. * * Use the string literal operator template `""_scalar.from()` to quote - * a freestanding function, stateless lambda or classic function object. + * a freestanding function, stateless lambda or function object. * * Calling the function captures the parameters in a `function_call` node. * * Internal note: - * Captures and represents a function [pointer or object], especially one without side effects. + * 1. Captures and represents a function [pointer or object], especially one without side effects. * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. + * 2. The nested `udf_type` typename is deliberately chosen to be the function signature, + * and will be the abstracted version of the user-defined function. */ - template - struct quoted_scalar_function : polyfill::type_identity> { - using type = typename quoted_scalar_function::type; + template + struct quoted_scalar_function { + using udf_type = Sig; + using callabe_type = F; /* * Generates the SQL function call. */ template - function_call operator()(CallArgs... callArgs) const { - check_function_call(); + function_call operator()(CallArgs... callArgs) const { + check_function_call(); return {this->udf_holder(), {std::forward(callArgs)...}}; } @@ -372,7 +389,7 @@ namespace sqlite_orm { } constexpr auto udf_holder() const { - return internal::udf_holder{this->name()}; + return internal::udf_holder{this->name()}; } constexpr auto name() const { @@ -398,22 +415,65 @@ namespace sqlite_orm { } /* - * From function pointer or object. + * From a freestanding function. */ template - requires(std::is_function_v> || - (orm_classic_function_object && (stateless || std::copy_constructible))) + requires(std::is_function_v>) [[nodiscard]] consteval auto from(F callable) const { - return quoted_scalar_function{this->nme, std::move(callable)}; + using Sig = function_signature_type_t; + return quoted_scalar_function{this->nme, std::move(callable)}; + } + + /* + * From an overloaded freestanding function. + */ + template + [[nodiscard]] consteval auto from(F* callable) const { + return quoted_scalar_function{this->nme, std::move(callable)}; } /* - * From function object type. + * From a classic function object instance. */ - template + template requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(F callable) const { + using Sig = function_signature_type_t; + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->nme, std::move(callable)}; + } + + /* + * From a function object instance, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(F callable) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->nme, std::move(callable)}; + } + + /* + * From a classic function object type. + */ + template + requires(stateless || std::copy_constructible) [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { - return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + using Sig = function_signature_type_t; + return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + } + + /* + * From a function object type, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; } }; #endif @@ -437,17 +497,37 @@ namespace sqlite_orm { SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* @short Create a scalar function from a freestanding function, stateless lambda or classic function object, + /* @short Create a scalar function from a freestanding function, stateless lambda or function object, * and call such a user-defined function. * + * If you need to pick a function or method from an overload set, or pick a template function you can + * specify an explicit function signature in the call to `from()`. + * * Examples: + * // freestanding function from a library * constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); - * constexpr auto equal_to_int_f = "equal_to_int"_scalar.from>(); - * constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.from([](unsigned long errcode) { + * // stateless lambda + * constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.from([](unsigned long errcode) { * return errcode != 0; * }); - * select(clamp_int_f(0, 1, 1)); - * select(is_fatal_error_f(1)); + * // function object instance + * constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); + * // function object + * constexpr auto equal_to_int_2_f = "equal_to"_scalar.from>(); + * // pick function object's template call operator + * constexpr auto equal_to_int_3_f = "equal_to"_scalar.from(std::equal_to{}); + * + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * + * auto rows = storage.select(clamp_int_f(0, 1, 1)); + * auto rows = storage.select(is_fatal_error_f(1)); + * auto rows = storage.select(equal_to_int_f(1, 1)); + * auto rows = storage.select(equal_to_int_2_f(1, 1)); + * auto rows = storage.select(equal_to_int_3_f(1, 1)); */ template [[nodiscard]] consteval auto operator"" _scalar() { diff --git a/dev/functional/function_traits.h b/dev/functional/function_traits.h index 54c5bf4c3..043e55726 100644 --- a/dev/functional/function_traits.h +++ b/dev/functional/function_traits.h @@ -5,6 +5,12 @@ namespace sqlite_orm { namespace internal { + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + * - signature_type + */ template struct function_traits; @@ -24,29 +30,38 @@ namespace sqlite_orm { using function_arguments = typename function_traits::template arguments_tuple; /* - * Define nested typenames: - * - return_type - * - arguments_tuple + * A function's signature */ + template + using function_signature_type_t = typename function_traits::signature_type; + template struct function_traits { using return_type = R; template class Tuple, template class ProjectOp> using arguments_tuple = Tuple...>; + + using signature_type = R(Args...); }; // non-exhaustive partial specializations of `function_traits` template - struct function_traits : function_traits {}; + struct function_traits : function_traits { + using signature_type = R(Args...) const; + }; #ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED template - struct function_traits : function_traits {}; + struct function_traits : function_traits { + using signature_type = R(Args...) noexcept; + }; template - struct function_traits : function_traits {}; + struct function_traits : function_traits { + using signature_type = R(Args...) const noexcept; + }; #endif /* @@ -55,6 +70,13 @@ namespace sqlite_orm { template struct function_traits : function_traits {}; +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct function_traits : function_traits { + using signature_type = R(Args...) noexcept; + }; +#endif + /* * Pick signature of pointer-to-member function */ diff --git a/dev/storage_base.h b/dev/storage_base.h index e4df54ec1..067b3123d 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -287,24 +287,24 @@ namespace sqlite_orm { */ template void create_scalar_function(Args&&... constructorArgs) { - return this->create_scalar_function>(std::forward(constructorArgs)...); + return this->create_scalar_function>(std::forward(constructorArgs)...); } /** * Create an application-defined scalar function. * Can be called at any time no matter whether the database connection is opened or not. * - * If `quotedF` contains a freestanding function, stateless lambda or stateless classic function object, + * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; - * otherwise, it repeatedly uses a copy of the contained classic function object, assuming possible side effects. + * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function() { - using F = auto_type_t; - using args_tuple = typename callable_arguments::args_tuple; - using return_type = typename callable_arguments::return_type; + using Sig = auto_udf_type_t; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); @@ -390,7 +390,7 @@ namespace sqlite_orm { */ template void create_aggregate_function(Args&&... constructorArgs) { - return this->create_aggregate_function>(std::forward(constructorArgs)...); + return this->create_aggregate_function>(std::forward(constructorArgs)...); } #endif diff --git a/dev/type_traits.h b/dev/type_traits.h index 9d3b9ae35..66477e1f7 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -72,6 +72,14 @@ namespace sqlite_orm { template using on_type_t = typename T::on_type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using udf_type_t = typename T::udf_type; + + template + using auto_udf_type_t = typename decltype(a)::udf_type; +#endif + #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template concept stateless = std::is_empty_v; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 793f3f601..6cf5637f9 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -437,6 +437,14 @@ namespace sqlite_orm { template using on_type_t = typename T::on_type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using udf_type_t = typename T::udf_type; + + template + using auto_udf_type_t = typename decltype(a)::udf_type; +#endif + #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template concept stateless = std::is_empty_v; @@ -10909,6 +10917,12 @@ namespace sqlite_orm { namespace sqlite_orm { namespace internal { + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + * - signature_type + */ template struct function_traits; @@ -10928,29 +10942,38 @@ namespace sqlite_orm { using function_arguments = typename function_traits::template arguments_tuple; /* - * Define nested typenames: - * - return_type - * - arguments_tuple + * A function's signature */ + template + using function_signature_type_t = typename function_traits::signature_type; + template struct function_traits { using return_type = R; template class Tuple, template class ProjectOp> using arguments_tuple = Tuple...>; + + using signature_type = R(Args...); }; // non-exhaustive partial specializations of `function_traits` template - struct function_traits : function_traits {}; + struct function_traits : function_traits { + using signature_type = R(Args...) const; + }; #ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED template - struct function_traits : function_traits {}; + struct function_traits : function_traits { + using signature_type = R(Args...) noexcept; + }; template - struct function_traits : function_traits {}; + struct function_traits : function_traits { + using signature_type = R(Args...) const noexcept; + }; #endif /* @@ -10959,6 +10982,13 @@ namespace sqlite_orm { template struct function_traits : function_traits {}; +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct function_traits : function_traits { + using signature_type = R(Args...) noexcept; + }; +#endif + /* * Pick signature of pointer-to-member function */ @@ -11011,6 +11041,17 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Specifies that a type is a function signature (i.e. a function in the C++ type system). + */ + template + concept orm_function_sig = std::is_function_v; + + /** @short Specifies that a type is a classic function object. + * + * A classic function object meets the following requirements: + * - defines a single call operator `F::operator()` + * - isn't a traditional sqlite_orm scalar function (having a static `F::name()` function + */ template concept orm_classic_function_object = ((!requires { typename F::is_transparent; }) && (requires { &F::operator(); }) && @@ -11049,13 +11090,13 @@ namespace sqlite_orm { */ template concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && - orm_scalar_udf); + orm_scalar_udf); /** @short Specifies that a type is a framed user-defined aggregate function. */ template concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && - orm_aggregate_udf); + orm_aggregate_udf); /** @short Specifies that a type is a framed and quoted user-defined scalar function. */ @@ -11268,7 +11309,10 @@ namespace sqlite_orm { * Calling the function captures the parameters in a `function_call` node. */ template - struct function : polyfill::type_identity { + struct function { + using udf_type = UDF; + using callable_type = UDF; + /* * Generates the SQL function call. */ @@ -11293,25 +11337,28 @@ namespace sqlite_orm { * Generator of a user-defined function call in a sql query expression. * * Use the string literal operator template `""_scalar.from()` to quote - * a freestanding function, stateless lambda or classic function object. + * a freestanding function, stateless lambda or function object. * * Calling the function captures the parameters in a `function_call` node. * * Internal note: - * Captures and represents a function [pointer or object], especially one without side effects. + * 1. Captures and represents a function [pointer or object], especially one without side effects. * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. + * 2. The nested `udf_type` typename is deliberately chosen to be the function signature, + * and will be the abstracted version of the user-defined function. */ - template - struct quoted_scalar_function : polyfill::type_identity> { - using type = typename quoted_scalar_function::type; + template + struct quoted_scalar_function { + using udf_type = Sig; + using callabe_type = F; /* * Generates the SQL function call. */ template - function_call operator()(CallArgs... callArgs) const { - check_function_call(); + function_call operator()(CallArgs... callArgs) const { + check_function_call(); return {this->udf_holder(), {std::forward(callArgs)...}}; } @@ -11328,7 +11375,7 @@ namespace sqlite_orm { } constexpr auto udf_holder() const { - return internal::udf_holder{this->name()}; + return internal::udf_holder{this->name()}; } constexpr auto name() const { @@ -11354,22 +11401,65 @@ namespace sqlite_orm { } /* - * From function pointer or object. + * From a freestanding function. */ template - requires(std::is_function_v> || - (orm_classic_function_object && (stateless || std::copy_constructible))) + requires(std::is_function_v>) [[nodiscard]] consteval auto from(F callable) const { - return quoted_scalar_function{this->nme, std::move(callable)}; + using Sig = function_signature_type_t; + return quoted_scalar_function{this->nme, std::move(callable)}; } /* - * From function object type. + * From an overloaded freestanding function. */ - template + template + [[nodiscard]] consteval auto from(F* callable) const { + return quoted_scalar_function{this->nme, std::move(callable)}; + } + + /* + * From a classic function object instance. + */ + template requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(F callable) const { + using Sig = function_signature_type_t; + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->nme, std::move(callable)}; + } + + /* + * From a function object instance, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto from(F callable) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->nme, std::move(callable)}; + } + + /* + * From a classic function object type. + */ + template + requires(stateless || std::copy_constructible) + [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + using Sig = function_signature_type_t; + return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + } + + /* + * From a function object type, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { - return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; } }; #endif @@ -11393,17 +11483,37 @@ namespace sqlite_orm { SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* @short Create a scalar function from a freestanding function, stateless lambda or classic function object, + /* @short Create a scalar function from a freestanding function, stateless lambda or function object, * and call such a user-defined function. * + * If you need to pick a function or method from an overload set, or pick a template function you can + * specify an explicit function signature in the call to `from()`. + * * Examples: + * // freestanding function from a library * constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); - * constexpr auto equal_to_int_f = "equal_to_int"_scalar.from>(); - * constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.from([](unsigned long errcode) { + * // stateless lambda + * constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.from([](unsigned long errcode) { * return errcode != 0; * }); - * select(clamp_int_f(0, 1, 1)); - * select(is_fatal_error_f(1)); + * // function object instance + * constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); + * // function object + * constexpr auto equal_to_int_2_f = "equal_to"_scalar.from>(); + * // pick function object's template call operator + * constexpr auto equal_to_int_3_f = "equal_to"_scalar.from(std::equal_to{}); + * + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * + * auto rows = storage.select(clamp_int_f(0, 1, 1)); + * auto rows = storage.select(is_fatal_error_f(1)); + * auto rows = storage.select(equal_to_int_f(1, 1)); + * auto rows = storage.select(equal_to_int_2_f(1, 1)); + * auto rows = storage.select(equal_to_int_3_f(1, 1)); */ template [[nodiscard]] consteval auto operator"" _scalar() { @@ -15529,24 +15639,24 @@ namespace sqlite_orm { */ template void create_scalar_function(Args&&... constructorArgs) { - return this->create_scalar_function>(std::forward(constructorArgs)...); + return this->create_scalar_function>(std::forward(constructorArgs)...); } /** * Create an application-defined scalar function. * Can be called at any time no matter whether the database connection is opened or not. * - * If `quotedF` contains a freestanding function, stateless lambda or stateless classic function object, + * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; - * otherwise, it repeatedly uses a copy of the contained classic function object, assuming possible side effects. + * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function() { - using F = auto_type_t; - using args_tuple = typename callable_arguments::args_tuple; - using return_type = typename callable_arguments::return_type; + using Sig = auto_udf_type_t; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); @@ -15632,7 +15742,7 @@ namespace sqlite_orm { */ template void create_aggregate_function(Args&&... constructorArgs) { - return this->create_aggregate_function>(std::forward(constructorArgs)...); + return this->create_aggregate_function>(std::forward(constructorArgs)...); } #endif diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index e25aa0c2f..54558e912 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -426,69 +426,119 @@ TEST_CASE("generalized scalar udf") { auto storage = make_storage(""); storage.sync_schema(); - constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.from(ERR_FATAL_ERROR); - storage.create_scalar_function(); - { - auto rows = storage.select(err_fatal_error_f(1)); - decltype(rows) expected{true}; - REQUIRE(rows == expected); + SECTION("freestanding function") { + constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.from(ERR_FATAL_ERROR); + storage.create_scalar_function(); + { + auto rows = storage.select(err_fatal_error_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + + SECTION("stateless lambda") { + constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.from([](unsigned long errcode) { + return errcode != 0; + }); + storage.create_scalar_function(); + { + auto rows = storage.select(is_fatal_error_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); - constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.from([](unsigned long errcode) { - return errcode != 0; - }); - storage.create_scalar_function(); - { - auto rows = storage.select(is_fatal_error_f(1)); - decltype(rows) expected{true}; - REQUIRE(rows == expected); + SECTION("function object instance") { + constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); - constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); - storage.create_scalar_function(); - { - auto rows = storage.select(equal_to_int_f(1, 1)); - decltype(rows) expected{true}; - REQUIRE(rows == expected); + SECTION("explicit function object type") { + constexpr auto equal_to_int_f = "equal_to"_scalar.from>(); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); - constexpr auto equal_to_int_2_f = "equal_to"_scalar.from>(); - storage.create_scalar_function(); - { - auto rows = storage.select(equal_to_int_2_f(1, 1)); - decltype(rows) expected{true}; - REQUIRE(rows == expected); + SECTION("'transparent' function object instance") { + constexpr auto equal_to_int_f = + "equal_to"_scalar.from(std::equal_to{}); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); - constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); - storage.create_scalar_function(); - { - auto rows = storage.select(clamp_int_f(0, 1, 1)); - decltype(rows) expected{1}; - REQUIRE(rows == expected); + SECTION("explicit 'transparent' function object type") { + constexpr auto equal_to_int_f = + "equal_to"_scalar.from>(); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); - constexpr auto idfunc_f = "idfunc"_scalar.from(); - storage.create_scalar_function(); - { - auto rows = storage.select(idfunc_f(1)); - decltype(rows) expected{1}; - REQUIRE(rows == expected); + SECTION("specialized template function") { + constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); + storage.create_scalar_function(); + { + auto rows = storage.select(clamp_int_f(0, 1, 1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); - constexpr auto offset0_f = "offset0"_scalar.from(offset0); - storage.create_scalar_function(); - { - auto rows = storage.select(columns(offset0_f(1), offset0_f(1))); - decltype(rows) expected{{1, 1}}; - REQUIRE(rows == expected); + SECTION("overloaded template function") { + constexpr auto clamp_int_f = + "clamp_int"_scalar.from(std::clamp); + storage.create_scalar_function(); + { + auto rows = storage.select(clamp_int_f(0, 1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + + SECTION("non-copyable function object") { + constexpr auto idfunc_f = "idfunc"_scalar.from(); + storage.create_scalar_function(); + { + auto rows = storage.select(idfunc_f(1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + + SECTION("stateful function object") { + constexpr auto offset0_f = "offset0"_scalar.from(offset0); + storage.create_scalar_function(); + { + auto rows = storage.select(columns(offset0_f(1), offset0_f(1))); + decltype(rows) expected{{1, 1}}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); } - storage.delete_scalar_function(); } #endif From 5377c090999af8e077f47f9cf0a7e58a422357c4 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 23 Nov 2023 20:55:55 +0200 Subject: [PATCH 09/13] Renamed `quoted_function_builder::from()` to `quote()` --- dev/function.h | 24 ++++++++++++------------ include/sqlite_orm/sqlite_orm.h | 24 ++++++++++++------------ tests/user_defined_functions.cpp | 20 ++++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/dev/function.h b/dev/function.h index bc6280fe1..56585e8fd 100644 --- a/dev/function.h +++ b/dev/function.h @@ -350,7 +350,7 @@ namespace sqlite_orm { /* * Generator of a user-defined function call in a sql query expression. * - * Use the string literal operator template `""_scalar.from()` to quote + * Use the string literal operator template `""_scalar.quote()` to quote * a freestanding function, stateless lambda or function object. * * Calling the function captures the parameters in a `function_call` node. @@ -419,7 +419,7 @@ namespace sqlite_orm { */ template requires(std::is_function_v>) - [[nodiscard]] consteval auto from(F callable) const { + [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; return quoted_scalar_function{this->nme, std::move(callable)}; } @@ -428,7 +428,7 @@ namespace sqlite_orm { * From an overloaded freestanding function. */ template - [[nodiscard]] consteval auto from(F* callable) const { + [[nodiscard]] consteval auto quote(F* callable) const { return quoted_scalar_function{this->nme, std::move(callable)}; } @@ -437,7 +437,7 @@ namespace sqlite_orm { */ template requires(orm_classic_function_object && (stateless || std::copy_constructible)) - [[nodiscard]] consteval auto from(F callable) const { + [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); @@ -449,7 +449,7 @@ namespace sqlite_orm { */ template requires((stateless || std::copy_constructible)) - [[nodiscard]] consteval auto from(F callable) const { + [[nodiscard]] consteval auto quote(F callable) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); return quoted_scalar_function{this->nme, std::move(callable)}; @@ -460,7 +460,7 @@ namespace sqlite_orm { */ template requires(stateless || std::copy_constructible) - [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { using Sig = function_signature_type_t; return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; } @@ -470,7 +470,7 @@ namespace sqlite_orm { */ template requires((stateless || std::copy_constructible)) - [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; @@ -505,17 +505,17 @@ namespace sqlite_orm { * * Examples: * // freestanding function from a library - * constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); + * constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); * // stateless lambda - * constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.from([](unsigned long errcode) { + * constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) { * return errcode != 0; * }); * // function object instance - * constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); + * constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); * // function object - * constexpr auto equal_to_int_2_f = "equal_to"_scalar.from>(); + * constexpr auto equal_to_int_2_f = "equal_to"_scalar.quote>(); * // pick function object's template call operator - * constexpr auto equal_to_int_3_f = "equal_to"_scalar.from(std::equal_to{}); + * constexpr auto equal_to_int_3_f = "equal_to"_scalar.quote(std::equal_to{}); * * storage.create_scalar_function(); * storage.create_scalar_function(); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 6cf5637f9..530c1a2fd 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -11336,7 +11336,7 @@ namespace sqlite_orm { /* * Generator of a user-defined function call in a sql query expression. * - * Use the string literal operator template `""_scalar.from()` to quote + * Use the string literal operator template `""_scalar.quote()` to quote * a freestanding function, stateless lambda or function object. * * Calling the function captures the parameters in a `function_call` node. @@ -11405,7 +11405,7 @@ namespace sqlite_orm { */ template requires(std::is_function_v>) - [[nodiscard]] consteval auto from(F callable) const { + [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; return quoted_scalar_function{this->nme, std::move(callable)}; } @@ -11414,7 +11414,7 @@ namespace sqlite_orm { * From an overloaded freestanding function. */ template - [[nodiscard]] consteval auto from(F* callable) const { + [[nodiscard]] consteval auto quote(F* callable) const { return quoted_scalar_function{this->nme, std::move(callable)}; } @@ -11423,7 +11423,7 @@ namespace sqlite_orm { */ template requires(orm_classic_function_object && (stateless || std::copy_constructible)) - [[nodiscard]] consteval auto from(F callable) const { + [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); @@ -11435,7 +11435,7 @@ namespace sqlite_orm { */ template requires((stateless || std::copy_constructible)) - [[nodiscard]] consteval auto from(F callable) const { + [[nodiscard]] consteval auto quote(F callable) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); return quoted_scalar_function{this->nme, std::move(callable)}; @@ -11446,7 +11446,7 @@ namespace sqlite_orm { */ template requires(stateless || std::copy_constructible) - [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { using Sig = function_signature_type_t; return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; } @@ -11456,7 +11456,7 @@ namespace sqlite_orm { */ template requires((stateless || std::copy_constructible)) - [[nodiscard]] consteval auto from(Args&&... constructorArgs) const { + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; @@ -11491,17 +11491,17 @@ namespace sqlite_orm { * * Examples: * // freestanding function from a library - * constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); + * constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); * // stateless lambda - * constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.from([](unsigned long errcode) { + * constexpr auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) { * return errcode != 0; * }); * // function object instance - * constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); + * constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); * // function object - * constexpr auto equal_to_int_2_f = "equal_to"_scalar.from>(); + * constexpr auto equal_to_int_2_f = "equal_to"_scalar.quote>(); * // pick function object's template call operator - * constexpr auto equal_to_int_3_f = "equal_to"_scalar.from(std::equal_to{}); + * constexpr auto equal_to_int_3_f = "equal_to"_scalar.quote(std::equal_to{}); * * storage.create_scalar_function(); * storage.create_scalar_function(); diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 54558e912..1b0b946f0 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -427,7 +427,7 @@ TEST_CASE("generalized scalar udf") { storage.sync_schema(); SECTION("freestanding function") { - constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.from(ERR_FATAL_ERROR); + constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.quote(ERR_FATAL_ERROR); storage.create_scalar_function(); { auto rows = storage.select(err_fatal_error_f(1)); @@ -438,7 +438,7 @@ TEST_CASE("generalized scalar udf") { } SECTION("stateless lambda") { - constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.from([](unsigned long errcode) { + constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.quote([](unsigned long errcode) { return errcode != 0; }); storage.create_scalar_function(); @@ -451,7 +451,7 @@ TEST_CASE("generalized scalar udf") { } SECTION("function object instance") { - constexpr auto equal_to_int_f = "equal_to"_scalar.from(std::equal_to{}); + constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); storage.create_scalar_function(); { auto rows = storage.select(equal_to_int_f(1, 1)); @@ -462,7 +462,7 @@ TEST_CASE("generalized scalar udf") { } SECTION("explicit function object type") { - constexpr auto equal_to_int_f = "equal_to"_scalar.from>(); + constexpr auto equal_to_int_f = "equal_to"_scalar.quote>(); storage.create_scalar_function(); { auto rows = storage.select(equal_to_int_f(1, 1)); @@ -474,7 +474,7 @@ TEST_CASE("generalized scalar udf") { SECTION("'transparent' function object instance") { constexpr auto equal_to_int_f = - "equal_to"_scalar.from(std::equal_to{}); + "equal_to"_scalar.quote(std::equal_to{}); storage.create_scalar_function(); { auto rows = storage.select(equal_to_int_f(1, 1)); @@ -486,7 +486,7 @@ TEST_CASE("generalized scalar udf") { SECTION("explicit 'transparent' function object type") { constexpr auto equal_to_int_f = - "equal_to"_scalar.from>(); + "equal_to"_scalar.quote>(); storage.create_scalar_function(); { auto rows = storage.select(equal_to_int_f(1, 1)); @@ -497,7 +497,7 @@ TEST_CASE("generalized scalar udf") { } SECTION("specialized template function") { - constexpr auto clamp_int_f = "clamp_int"_scalar.from(std::clamp); + constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); storage.create_scalar_function(); { auto rows = storage.select(clamp_int_f(0, 1, 1)); @@ -509,7 +509,7 @@ TEST_CASE("generalized scalar udf") { SECTION("overloaded template function") { constexpr auto clamp_int_f = - "clamp_int"_scalar.from(std::clamp); + "clamp_int"_scalar.quote(std::clamp); storage.create_scalar_function(); { auto rows = storage.select(clamp_int_f(0, 1, 1)); @@ -520,7 +520,7 @@ TEST_CASE("generalized scalar udf") { } SECTION("non-copyable function object") { - constexpr auto idfunc_f = "idfunc"_scalar.from(); + constexpr auto idfunc_f = "idfunc"_scalar.quote(); storage.create_scalar_function(); { auto rows = storage.select(idfunc_f(1)); @@ -531,7 +531,7 @@ TEST_CASE("generalized scalar udf") { } SECTION("stateful function object") { - constexpr auto offset0_f = "offset0"_scalar.from(offset0); + constexpr auto offset0_f = "offset0"_scalar.quote(offset0); storage.create_scalar_function(); { auto rows = storage.select(columns(offset0_f(1), offset0_f(1))); From 7c6bec71956acbbb4f9880961230a94c806b1545 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 24 Nov 2023 21:29:40 +0200 Subject: [PATCH 10/13] Renamed string literal template class --- dev/alias.h | 6 ++-- dev/function.h | 22 ++++++-------- dev/functional/char_array_template.h | 31 ------------------- dev/functional/cstring_literal.h | 34 +++++++++++++++++++++ include/sqlite_orm/sqlite_orm.h | 45 ++++++++++++++-------------- 5 files changed, 68 insertions(+), 70 deletions(-) delete mode 100644 dev/functional/char_array_template.h create mode 100644 dev/functional/cstring_literal.h diff --git a/dev/alias.h b/dev/alias.h index 63fc0176a..c34e72e77 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -6,7 +6,7 @@ #include // std::stringstream #include "functional/cxx_type_traits_polyfill.h" -#include "functional/char_array_template.h" +#include "functional/cstring_literal.h" #include "type_traits.h" #include "alias_traits.h" #include "table_type_of.h" @@ -341,7 +341,7 @@ namespace sqlite_orm { * Examples: * constexpr auto z_alias = "z"_alias.for_(); */ - template + template [[nodiscard]] consteval auto operator"" _alias() { return internal::explode_into(std::make_index_sequence{}); } @@ -350,7 +350,7 @@ namespace sqlite_orm { * column_alias<'a'[, ...]> from a string literal. * E.g. "a"_col, "b"_col */ - template + template [[nodiscard]] consteval auto operator"" _col() { return internal::explode_into(std::make_index_sequence{}); } diff --git a/dev/function.h b/dev/function.h index 56585e8fd..fbf37dd22 100644 --- a/dev/function.h +++ b/dev/function.h @@ -10,7 +10,7 @@ #include "functional/cxx_universal.h" #include "functional/cxx_type_traits_polyfill.h" -#include "functional/char_array_template.h" +#include "functional/cstring_literal.h" #include "functional/function_traits.h" #include "type_traits.h" #include "tags.h" @@ -407,12 +407,8 @@ namespace sqlite_orm { }; template - struct quoted_function_builder { - char nme[N]; - - consteval quoted_function_builder(const char (&name)[N]) { - std::copy_n(name, N, this->nme); - } + struct quoted_function_builder : cstring_literal { + using cstring_literal::cstring_literal; /* * From a freestanding function. @@ -421,7 +417,7 @@ namespace sqlite_orm { requires(std::is_function_v>) [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -429,7 +425,7 @@ namespace sqlite_orm { */ template [[nodiscard]] consteval auto quote(F* callable) const { - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -441,7 +437,7 @@ namespace sqlite_orm { using Sig = function_signature_type_t; // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -452,7 +448,7 @@ namespace sqlite_orm { [[nodiscard]] consteval auto quote(F callable) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -462,7 +458,7 @@ namespace sqlite_orm { requires(stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { using Sig = function_signature_type_t; - return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; } /* @@ -473,7 +469,7 @@ namespace sqlite_orm { [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); - return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; } }; #endif diff --git a/dev/functional/char_array_template.h b/dev/functional/char_array_template.h deleted file mode 100644 index ec98abb2f..000000000 --- a/dev/functional/char_array_template.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include // std::index_sequence -#include // std::copy_n - -#include "cxx_universal.h" // ::size_t - -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES -namespace sqlite_orm::internal { - /* - * Helper class to facilitate user-defined string literal operator template - */ - template - struct char_array_template { - static constexpr size_t size() { - return N - 1; - } - - constexpr char_array_template(const char (&charArray)[N]) { - std::copy_n(charArray, N, this->id); - } - - char id[N]; - }; - - template class Template, char_array_template chars, size_t... Idx> - consteval auto explode_into(std::index_sequence) { - return Template{}; - } -} -#endif diff --git a/dev/functional/cstring_literal.h b/dev/functional/cstring_literal.h new file mode 100644 index 000000000..db1d8471f --- /dev/null +++ b/dev/functional/cstring_literal.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::index_sequence +#include // std::copy_n +#endif + +#include "cxx_universal.h" // ::size_t + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * Wraps a C string of fixed size. + * Its main purpose is to enable the user-defined string literal operator template. + */ + template + struct cstring_literal { + static constexpr size_t size() { + return N - 1; + } + + constexpr cstring_literal(const char (&cstr)[N]) { + std::copy_n(cstr, N, this->cstr); + } + + char cstr[N]; + }; + + template class Template, cstring_literal literal, size_t... Idx> + consteval auto explode_into(std::index_sequence) { + return Template{}; + } +} +#endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 530c1a2fd..24ba44971 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -4555,10 +4555,12 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" -// #include "functional/char_array_template.h" +// #include "functional/cstring_literal.h" +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES #include // std::index_sequence #include // std::copy_n +#endif // #include "cxx_universal.h" // ::size_t @@ -4566,24 +4568,25 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES namespace sqlite_orm::internal { /* - * Helper class to facilitate user-defined string literal operator template + * Wraps a C string of fixed size. + * Its main purpose is to enable the user-defined string literal operator template. */ template - struct char_array_template { + struct cstring_literal { static constexpr size_t size() { return N - 1; } - constexpr char_array_template(const char (&charArray)[N]) { - std::copy_n(charArray, N, this->id); + constexpr cstring_literal(const char (&cstr)[N]) { + std::copy_n(cstr, N, this->cstr); } - char id[N]; + char cstr[N]; }; - template class Template, char_array_template chars, size_t... Idx> + template class Template, cstring_literal literal, size_t... Idx> consteval auto explode_into(std::index_sequence) { - return Template{}; + return Template{}; } } #endif @@ -4925,7 +4928,7 @@ namespace sqlite_orm { * Examples: * constexpr auto z_alias = "z"_alias.for_(); */ - template + template [[nodiscard]] consteval auto operator"" _alias() { return internal::explode_into(std::make_index_sequence{}); } @@ -4934,7 +4937,7 @@ namespace sqlite_orm { * column_alias<'a'[, ...]> from a string literal. * E.g. "a"_col, "b"_col */ - template + template [[nodiscard]] consteval auto operator"" _col() { return internal::explode_into(std::make_index_sequence{}); } @@ -10907,7 +10910,7 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" -// #include "functional/char_array_template.h" +// #include "functional/cstring_literal.h" // #include "functional/function_traits.h" @@ -11393,12 +11396,8 @@ namespace sqlite_orm { }; template - struct quoted_function_builder { - char nme[N]; - - consteval quoted_function_builder(const char (&name)[N]) { - std::copy_n(name, N, this->nme); - } + struct quoted_function_builder : cstring_literal { + using cstring_literal::cstring_literal; /* * From a freestanding function. @@ -11407,7 +11406,7 @@ namespace sqlite_orm { requires(std::is_function_v>) [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -11415,7 +11414,7 @@ namespace sqlite_orm { */ template [[nodiscard]] consteval auto quote(F* callable) const { - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -11427,7 +11426,7 @@ namespace sqlite_orm { using Sig = function_signature_type_t; // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -11438,7 +11437,7 @@ namespace sqlite_orm { [[nodiscard]] consteval auto quote(F callable) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); - return quoted_scalar_function{this->nme, std::move(callable)}; + return quoted_scalar_function{this->cstr, std::move(callable)}; } /* @@ -11448,7 +11447,7 @@ namespace sqlite_orm { requires(stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { using Sig = function_signature_type_t; - return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; } /* @@ -11459,7 +11458,7 @@ namespace sqlite_orm { [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { // detect whether overloaded call operator can be picked using `Sig` using call_operator_type = decltype(static_cast(&F::operator())); - return quoted_scalar_function{this->nme, std::forward(constructorArgs)...}; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; } }; #endif From e6f6293f67fc876d7676513e97f27bbb1bb4d5b7 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 24 Nov 2023 21:35:27 +0200 Subject: [PATCH 11/13] A few more unit tests for quoted scalar functions --- dev/function.h | 2 +- dev/functional/function_traits.h | 15 ++-- include/sqlite_orm/sqlite_orm.h | 17 +++-- tests/static_tests/function_static_tests.cpp | 72 ++++++++++++++------ tests/user_defined_functions.cpp | 9 --- 5 files changed, 67 insertions(+), 48 deletions(-) diff --git a/dev/function.h b/dev/function.h index fbf37dd22..697ae94b8 100644 --- a/dev/function.h +++ b/dev/function.h @@ -365,7 +365,7 @@ namespace sqlite_orm { template struct quoted_scalar_function { using udf_type = Sig; - using callabe_type = F; + using callable_type = F; /* * Generates the SQL function call. diff --git a/dev/functional/function_traits.h b/dev/functional/function_traits.h index 043e55726..a5f6a642b 100644 --- a/dev/functional/function_traits.h +++ b/dev/functional/function_traits.h @@ -67,15 +67,14 @@ namespace sqlite_orm { /* * Pick signature of function pointer */ - template - struct function_traits : function_traits {}; + template + struct function_traits : function_traits {}; -#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED - template - struct function_traits : function_traits { - using signature_type = R(Args...) noexcept; - }; -#endif + /* + * Pick signature of function reference + */ + template + struct function_traits : function_traits {}; /* * Pick signature of pointer-to-member function diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 24ba44971..734bd1d7f 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10982,15 +10982,14 @@ namespace sqlite_orm { /* * Pick signature of function pointer */ - template - struct function_traits : function_traits {}; + template + struct function_traits : function_traits {}; -#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED - template - struct function_traits : function_traits { - using signature_type = R(Args...) noexcept; - }; -#endif + /* + * Pick signature of function reference + */ + template + struct function_traits : function_traits {}; /* * Pick signature of pointer-to-member function @@ -11354,7 +11353,7 @@ namespace sqlite_orm { template struct quoted_scalar_function { using udf_type = Sig; - using callabe_type = F; + using callable_type = F; /* * Generates the SQL function call. diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index d6fa9f1bf..5fd646434 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -3,10 +3,14 @@ #include // std::is_same using namespace sqlite_orm; +using internal::callable_arguments; using internal::function; using internal::function_call; using internal::is_aggregate_udf_v; using internal::is_scalar_udf_v; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +using internal::quoted_scalar_function; +#endif #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template @@ -62,9 +66,8 @@ TEST_CASE("function static") { using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, double>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, double>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("double(double)") { struct Function { @@ -84,9 +87,8 @@ TEST_CASE("function static") { using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, double>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, double>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("int(std::string) const") { struct Function { @@ -106,9 +108,8 @@ TEST_CASE("function static") { using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, int>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, int>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("int(std::string)") { struct Function { @@ -128,9 +129,8 @@ TEST_CASE("function static") { using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, int>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, int>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("std::string(const std::string &, const std::string &) const") { struct Function { @@ -150,8 +150,8 @@ TEST_CASE("function static") { using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, std::string>::value); - STATIC_REQUIRE(std::is_same::args_tuple, + STATIC_REQUIRE(std::is_same::return_type, std::string>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("std::string(const std::string &, const std::string &)") { @@ -172,8 +172,8 @@ TEST_CASE("function static") { using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, std::string>::value); - STATIC_REQUIRE(std::is_same::args_tuple, + STATIC_REQUIRE(std::is_same::return_type, std::string>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } } @@ -205,8 +205,8 @@ TEST_CASE("function static") { using ExpectedFinType = int (Function::*)() const; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, int>::value); - STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, int>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("void(std::string) const & std::string()") { struct Function { @@ -232,9 +232,8 @@ TEST_CASE("function static") { using ExpectedFinType = std::string (Function::*)(); STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, std::string>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, std::string>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } } SECTION("function call expressions") { @@ -260,6 +259,9 @@ TEST_CASE("function static") { STATIC_REQUIRE(std::is_same>::value); STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same_v::callable_type, SFunction>); + STATIC_REQUIRE(std::is_same_v::udf_type, SFunction>); + #ifdef SQLITE_ORM_WITH_CPP20_ALIASES STATIC_REQUIRE(orm_scalar_function); STATIC_REQUIRE_FALSE(orm_aggregate_function); @@ -273,4 +275,32 @@ TEST_CASE("function static") { STATIC_REQUIRE_FALSE(storage_scalar_callable); #endif } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("quoted") { + constexpr auto quotedScalar = "f"_scalar.quote(std::clamp); + using quoted_type = decltype("f"_scalar.quote(std::clamp)); + + STATIC_REQUIRE(quotedScalar.nme[0] == 'f' && quotedScalar.nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v)*, + const int&(const int&, const int&, const int&), + 2>>); + + STATIC_REQUIRE(std::is_same_v)*>); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, int>); + STATIC_REQUIRE( + std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE(std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } +#endif } diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 1b0b946f0..c951537bb 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -436,7 +436,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("stateless lambda") { constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.quote([](unsigned long errcode) { return errcode != 0; @@ -449,7 +448,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("function object instance") { constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); storage.create_scalar_function(); @@ -460,7 +458,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("explicit function object type") { constexpr auto equal_to_int_f = "equal_to"_scalar.quote>(); storage.create_scalar_function(); @@ -471,7 +468,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("'transparent' function object instance") { constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); @@ -483,7 +479,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("explicit 'transparent' function object type") { constexpr auto equal_to_int_f = "equal_to"_scalar.quote>(); @@ -495,7 +490,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("specialized template function") { constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); storage.create_scalar_function(); @@ -506,7 +500,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("overloaded template function") { constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); @@ -518,7 +511,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("non-copyable function object") { constexpr auto idfunc_f = "idfunc"_scalar.quote(); storage.create_scalar_function(); @@ -529,7 +521,6 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } - SECTION("stateful function object") { constexpr auto offset0_f = "offset0"_scalar.quote(offset0); storage.create_scalar_function(); From 35dceeb5bd864973ea1f21ed1c7db36c0a2c579c Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 24 Nov 2023 21:57:52 +0200 Subject: [PATCH 12/13] Simplified code for quoting a freestanding function as a scalar --- dev/function.h | 12 +----------- include/sqlite_orm/sqlite_orm.h | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/dev/function.h b/dev/function.h index 697ae94b8..5bc230af4 100644 --- a/dev/function.h +++ b/dev/function.h @@ -411,17 +411,7 @@ namespace sqlite_orm { using cstring_literal::cstring_literal; /* - * From a freestanding function. - */ - template - requires(std::is_function_v>) - [[nodiscard]] consteval auto quote(F callable) const { - using Sig = function_signature_type_t; - return quoted_scalar_function{this->cstr, std::move(callable)}; - } - - /* - * From an overloaded freestanding function. + * From a freestanding function, possibly overloaded. */ template [[nodiscard]] consteval auto quote(F* callable) const { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 734bd1d7f..089babca4 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -11399,17 +11399,7 @@ namespace sqlite_orm { using cstring_literal::cstring_literal; /* - * From a freestanding function. - */ - template - requires(std::is_function_v>) - [[nodiscard]] consteval auto quote(F callable) const { - using Sig = function_signature_type_t; - return quoted_scalar_function{this->cstr, std::move(callable)}; - } - - /* - * From an overloaded freestanding function. + * From a freestanding function, possibly overloaded. */ template [[nodiscard]] consteval auto quote(F* callable) const { From ae8e3dfba8749aa0ebcbbaf2bf7e2b1021052191 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 24 Nov 2023 22:29:20 +0200 Subject: [PATCH 13/13] Corrected unit tests --- tests/static_tests/function_static_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index 5fd646434..ee7c387c7 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -259,8 +259,8 @@ TEST_CASE("function static") { STATIC_REQUIRE(std::is_same>::value); STATIC_REQUIRE(std::is_same>::value); - STATIC_REQUIRE(std::is_same_v::callable_type, SFunction>); - STATIC_REQUIRE(std::is_same_v::udf_type, SFunction>); + STATIC_REQUIRE(std::is_same::callable_type, SFunction>::value); + STATIC_REQUIRE(std::is_same::udf_type, SFunction>::value); #ifdef SQLITE_ORM_WITH_CPP20_ALIASES STATIC_REQUIRE(orm_scalar_function);