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 diff --git a/dev/alias.h b/dev/alias.h index bdc5b07f9..c34e72e77 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/cstring_literal.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..5bc230af4 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,13 +1,18 @@ #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 +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include // std::copy_constructible +#endif #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" #include "functional/cxx_type_traits_polyfill.h" +#include "functional/cstring_literal.h" #include "functional/function_traits.h" +#include "type_traits.h" #include "tags.h" namespace sqlite_orm { @@ -45,6 +50,78 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; + template + struct function; + } + +#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(); }) && + /*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; @@ -60,14 +137,72 @@ 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 {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a quoted user-defined function. + */ + template + struct udf_holder : 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 traditional sqlite_orm user-defined function. + */ + template + requires(requires { UDF::name(); }) + struct udf_holder +#else + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ + template + struct udf_holder +#endif + { + using udf_type = UDF; + + template>, bool> = true> + decltype(auto) operator()() const { + return UDF::name(); + } + + template::value, bool> = true> + std::string operator()() const { + return std::string{UDF::name()}; + } + }; + + /* + * Represents a call of a user-defined function. + */ template struct function_call { using udf_type = UDF; using args_tuple = std::tuple; + udf_holder name; args_tuple callArgs; }; @@ -162,74 +297,175 @@ namespace sqlite_orm { #endif } + template +#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; + 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. + * * 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. + */ 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->udf_holder(), {std::forward(callArgs)...}}; + } + + constexpr auto udf_holder() const { + return internal::udf_holder{}; + } + + // returns a character range + constexpr auto name() const { + return this->udf_holder()(); } }; - } #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; - }; + /* + * Generator of a user-defined function call in a sql query expression. + * + * 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. + * + * Internal note: + * 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 { + using udf_type = Sig; + using callable_type = F; + + /* + * Generates the SQL function call. + */ + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->udf_holder(), {std::forward(callArgs)...}}; + } - /** @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>; - }; + /* + * Return original `udf` if stateless or a copy of it otherwise + */ + constexpr decltype(auto) callable() const { + if constexpr(stateless) { + return (this->udf); + } else { + // non-const copy + return F(this->udf); + } + } - /** @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); + constexpr auto udf_holder() const { + return internal::udf_holder{this->name()}; + } - /** @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); + constexpr auto name() const { + return this->nme; + } + + template + consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : + udf(std::forward(constructorArgs)...) { + std::copy_n(name, N, this->nme); + } + + F udf; + char nme[N]; + }; + + template + struct quoted_function_builder : cstring_literal { + using cstring_literal::cstring_literal; + + /* + * From a freestanding function, possibly overloaded. + */ + template + [[nodiscard]] consteval auto quote(F* callable) const { + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a classic function object instance. + */ + template + requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[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())); + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a function object instance, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[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->cstr, std::move(callable)}; + } + + /* + * From a classic function object type. + */ + template + requires(stateless || std::copy_constructible) + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { + using Sig = function_signature_type_t; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; + } + + /* + * From a function object type, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[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->cstr, std::forward(constructorArgs)...}; + } + }; #endif + } - /** - * Call a user-defined function. + /** @short Call a user-defined function. * * Example: * struct IdFunc { int oeprator(int arg)() const { return arg; } }; @@ -245,4 +481,43 @@ 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 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.quote(std::clamp); + * // stateless lambda + * 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.quote(std::equal_to{}); + * // function object + * 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.quote(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() { + return builder; + } +#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/dev/functional/function_traits.h b/dev/functional/function_traits.h index 5668001b3..a5f6a642b 100644 --- a/dev/functional/function_traits.h +++ b/dev/functional/function_traits.h @@ -1,9 +1,16 @@ #pragma once + #include "cxx_type_traits_polyfill.h" #include "mpl.h" namespace sqlite_orm { namespace internal { + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + * - signature_type + */ template struct function_traits; @@ -23,36 +30,51 @@ 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 /* * Pick signature of function pointer */ - template - struct function_traits : function_traits {}; + template + struct function_traits : function_traits {}; + + /* + * Pick signature of function reference + */ + template + struct function_traits : function_traits {}; /* * Pick signature of pointer-to-member function 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..067b3123d 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 @@ -286,7 +287,48 @@ 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 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 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 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); + 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 +368,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 @@ -350,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 @@ -361,15 +401,27 @@ 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 + /** + * 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_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); } #endif @@ -380,15 +432,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 +703,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 +733,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 +810,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/type_traits.h b/dev/type_traits.h index 1316adda1..66477e1f7 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,19 @@ 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; +#endif } #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED 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..089babca4 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,19 @@ 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; +#endif } #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED @@ -4535,15 +4548,48 @@ 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/cstring_literal.h" + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::index_sequence #include // std::copy_n +#endif -// #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 { + /* + * 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 // #include "type_traits.h" @@ -4556,29 +4602,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 +4928,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 } @@ -10874,15 +10898,20 @@ 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 +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include // std::copy_constructible +#endif #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" // #include "functional/cxx_type_traits_polyfill.h" +// #include "functional/cstring_literal.h" + // #include "functional/function_traits.h" // #include "cxx_type_traits_polyfill.h" @@ -10891,6 +10920,12 @@ namespace sqlite_orm { namespace sqlite_orm { namespace internal { + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + * - signature_type + */ template struct function_traits; @@ -10910,36 +10945,51 @@ 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 /* * Pick signature of function pointer */ - template - struct function_traits : function_traits {}; + template + struct function_traits : function_traits {}; + + /* + * Pick signature of function reference + */ + template + struct function_traits : function_traits {}; /* * Pick signature of pointer-to-member function @@ -10949,6 +10999,8 @@ namespace sqlite_orm { } } +// #include "type_traits.h" + // #include "tags.h" namespace sqlite_orm { @@ -10986,6 +11038,78 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; + template + struct function; + } + +#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(); }) && + /*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; @@ -11001,14 +11125,72 @@ 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 {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a quoted user-defined function. + */ + template + struct udf_holder : 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 traditional sqlite_orm user-defined function. + */ + template + requires(requires { UDF::name(); }) + struct udf_holder +#else + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ + template + struct udf_holder +#endif + { + using udf_type = UDF; + + template>, bool> = true> + decltype(auto) operator()() const { + return UDF::name(); + } + + template::value, bool> = true> + std::string operator()() const { + return std::string{UDF::name()}; + } + }; + + /* + * Represents a call of a user-defined function. + */ template struct function_call { using udf_type = UDF; using args_tuple = std::tuple; + udf_holder name; args_tuple callArgs; }; @@ -11103,74 +11285,175 @@ namespace sqlite_orm { #endif } + template +#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; + 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. + * * 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. + */ 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->udf_holder(), {std::forward(callArgs)...}}; + } + + constexpr auto udf_holder() const { + return internal::udf_holder{}; + } + + // returns a character range + constexpr auto name() const { + return this->udf_holder()(); } }; - } #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; - }; + /* + * Generator of a user-defined function call in a sql query expression. + * + * 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. + * + * Internal note: + * 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 { + using udf_type = Sig; + using callable_type = F; - /** @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>; - }; + /* + * Generates the SQL function call. + */ + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->udf_holder(), {std::forward(callArgs)...}}; + } - /** @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); + /* + * Return original `udf` if stateless or a copy of it otherwise + */ + constexpr decltype(auto) callable() const { + if constexpr(stateless) { + return (this->udf); + } else { + // non-const copy + return F(this->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); + constexpr auto udf_holder() const { + return internal::udf_holder{this->name()}; + } + + constexpr auto name() const { + return this->nme; + } + + template + consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : + udf(std::forward(constructorArgs)...) { + std::copy_n(name, N, this->nme); + } + + F udf; + char nme[N]; + }; + + template + struct quoted_function_builder : cstring_literal { + using cstring_literal::cstring_literal; + + /* + * From a freestanding function, possibly overloaded. + */ + template + [[nodiscard]] consteval auto quote(F* callable) const { + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a classic function object instance. + */ + template + requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[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())); + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a function object instance, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[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->cstr, std::move(callable)}; + } + + /* + * From a classic function object type. + */ + template + requires(stateless || std::copy_constructible) + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { + using Sig = function_signature_type_t; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; + } + + /* + * From a function object type, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[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->cstr, std::forward(constructorArgs)...}; + } + }; #endif + } - /** - * Call a user-defined function. + /** @short Call a user-defined function. * * Example: * struct IdFunc { int oeprator(int arg)() const { return arg; } }; @@ -11186,6 +11469,45 @@ 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 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.quote(std::clamp); + * // stateless lambda + * 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.quote(std::equal_to{}); + * // function object + * 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.quote(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() { + return builder; + } +#endif } // #include "ast/special_keywords.h" @@ -14968,7 +15290,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 +15347,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 +15605,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 @@ -15296,7 +15627,48 @@ 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 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 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 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); + 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 +15708,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 @@ -15360,7 +15730,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 @@ -15371,15 +15741,27 @@ 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 + /** + * 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_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); } #endif @@ -15390,15 +15772,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 +16043,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 +16073,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 +16150,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 +17026,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..ee7c387c7 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,18 +232,21 @@ 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") { 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; }; @@ -256,6 +259,9 @@ TEST_CASE("function static") { STATIC_REQUIRE(std::is_same>::value); STATIC_REQUIRE(std::is_same>::value); + 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); STATIC_REQUIRE_FALSE(orm_aggregate_function); @@ -269,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 1b9e23f8a..c951537bb 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -396,3 +396,140 @@ 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 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; + + int operator()(int x) noexcept { + return offset += x; + } + + constexpr stateful_scalar(int offset = 0) : offset{offset} {} +}; +inline constexpr stateful_scalar offset0{}; + +TEST_CASE("generalized scalar udf") { + auto storage = make_storage(""); + storage.sync_schema(); + + SECTION("freestanding function") { + 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)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("stateless lambda") { + constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.quote([](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(); + } + SECTION("function object instance") { + 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)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("explicit function object type") { + constexpr auto equal_to_int_f = "equal_to"_scalar.quote>(); + 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(); + } + SECTION("'transparent' function object instance") { + 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)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("explicit 'transparent' function object type") { + constexpr auto equal_to_int_f = + "equal_to"_scalar.quote>(); + 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(); + } + SECTION("specialized template function") { + 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)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("overloaded template function") { + 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)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("non-copyable function object") { + constexpr auto idfunc_f = "idfunc"_scalar.quote(); + 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.quote(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(); + } +} +#endif