From 35eebef10e0f6c3ac0551880b2a573c938de7795 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 02:02:26 +0200 Subject: [PATCH 01/50] Variable template for framing and calling user-defined functions --- dev/alias.h | 8 +- dev/alias_traits.h | 14 +- dev/column_pointer.h | 5 +- dev/function.h | 148 +++++++--- dev/storage_base.h | 111 ++++--- dev/type_traits.h | 8 +- examples/user_defined_functions.cpp | 53 ++++ include/sqlite_orm/sqlite_orm.h | 294 ++++++++++++------- tests/ast_iterator_tests.cpp | 1 + tests/static_tests/alias.cpp | 9 +- tests/static_tests/column_result_t.cpp | 1 + tests/static_tests/function_static_tests.cpp | 84 ++++-- tests/static_tests/node_tuple.cpp | 1 + 13 files changed, 501 insertions(+), 236 deletions(-) diff --git a/dev/alias.h b/dev/alias.h index 4511a5a3a..bdc5b07f9 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -1,6 +1,6 @@ #pragma once -#include // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const +#include // std::enable_if #include // std::index_sequence, std::make_index_sequence #include // std::string #include // std::stringstream @@ -219,7 +219,7 @@ namespace sqlite_orm { template constexpr auto alias_column(C field) { using namespace ::sqlite_orm::internal; - using A = std::remove_const_t; + using A = decltype(als); using aliased_type = type_t; static_assert(is_field_of_v, "Column must be from aliased table"); @@ -263,7 +263,7 @@ namespace sqlite_orm { */ template auto as(E expression) { - return internal::as_t, E>{std::move(expression)}; + return internal::as_t{std::move(expression)}; } /** @@ -283,7 +283,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto get() { - return internal::alias_holder>{}; + return internal::alias_holder{}; } #endif diff --git a/dev/alias_traits.h b/dev/alias_traits.h index 489bbba86..3fe7f63fb 100644 --- a/dev/alias_traits.h +++ b/dev/alias_traits.h @@ -74,7 +74,7 @@ namespace sqlite_orm { template concept orm_alias = std::derived_from; - /** @short Alias of a column in a record set. + /** @short Specifies that a type is an alias of a column in a record set. * * A column alias has the following traits: * - is derived from `alias_tag` @@ -83,7 +83,7 @@ namespace sqlite_orm { template concept orm_column_alias = (orm_alias && !orm_names_type); - /** @short Alias of any type of record set. + /** @short Specifies that a type is an alias of any type of record set. * * A record set alias has the following traits: * - is derived from `alias_tag`. @@ -92,7 +92,7 @@ namespace sqlite_orm { template concept orm_recordset_alias = (orm_alias && orm_names_type); - /** @short Alias of a concrete table. + /** @short Specifies that a type is an alias of a concrete table. * * A concrete table alias has the following traits: * - is derived from `alias_tag`. @@ -101,7 +101,7 @@ namespace sqlite_orm { template concept orm_table_alias = (orm_recordset_alias && !std::same_as>); - /** @short Reference of a concrete table, especially of a derived class. + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. * * A concrete table reference has the following traits: * - specialization of `table_reference`, whose `type` typename references a mapped object. @@ -109,12 +109,18 @@ namespace sqlite_orm { template concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + /** @short Specifies that a type refers to a mapped table (possibly aliased). + */ template concept orm_refers_to_table = (orm_table_reference || orm_table_alias); + /** @short Specifies that a type refers to a recordset. + */ template concept orm_refers_to_recordset = (orm_table_reference || orm_recordset_alias); + /** @short Specifies that a type is a mapped recordset (table reference). + */ template concept orm_mapped_recordset = (orm_table_reference); #endif diff --git a/dev/column_pointer.h b/dev/column_pointer.h index bb8d5af76..044856b7f 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -1,6 +1,6 @@ #pragma once -#include // std::enable_if, std::remove_const +#include // std::enable_if #include // std::move #include "functional/cxx_type_traits_polyfill.h" @@ -48,8 +48,7 @@ namespace sqlite_orm { */ template constexpr auto column(F O::*field) { - using R = std::remove_const_t; - return column(field); + return column>(field); } /** diff --git a/dev/function.h b/dev/function.h index 8ffb8f775..e46581594 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type #include // std::string -#include // std::tuple +#include // std::tuple, std::tuple_size, std::tuple_element #include // std::function #include // std::min #include // std::move, std::forward @@ -22,7 +22,7 @@ namespace sqlite_orm { namespace internal { - struct user_defined_function_base { + struct udf_proxy_base { using func_call = std::function< void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>; using final_call = std::function; @@ -33,38 +33,38 @@ namespace sqlite_orm { void (*destroy)(int*) = nullptr; #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - user_defined_function_base(decltype(name) name_, - decltype(argumentsCount) argumentsCount_, - decltype(create) create_, - decltype(destroy) destroy_) : + udf_proxy_base(decltype(name) name_, + decltype(argumentsCount) argumentsCount_, + decltype(create) create_, + decltype(destroy) destroy_) : name(std::move(name_)), argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} #endif }; - struct user_defined_scalar_function_t : user_defined_function_base { + struct scalar_udf_proxy : udf_proxy_base { func_call run; - user_defined_scalar_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(run) run_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, + scalar_udf_proxy(decltype(name) name_, + int argumentsCount_, + decltype(create) create_, + decltype(run) run_, + decltype(destroy) destroy_) : + udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, run(std::move(run_)) {} }; - struct user_defined_aggregate_function_t : user_defined_function_base { + struct aggregate_udf_proxy : udf_proxy_base { func_call step; final_call finalCall; - user_defined_aggregate_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(step) step_, - decltype(finalCall) finalCall_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, + aggregate_udf_proxy(decltype(name) name_, + int argumentsCount_, + decltype(create) create_, + decltype(step) step_, + decltype(finalCall) finalCall_, + decltype(destroy) destroy_) : + udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, step(std::move(step_)), finalCall(std::move(finalCall_)) {} }; @@ -78,15 +78,14 @@ namespace sqlite_orm { using aggregate_fin_function_t = decltype(&F::fin); template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v = false; + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v>> = - true; + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v>> = true; template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v = false; + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v< + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v< F, polyfill::void_t, aggregate_fin_function_t, @@ -115,13 +114,13 @@ namespace sqlite_orm { struct callable_arguments_impl; template - struct callable_arguments_impl>> { + struct callable_arguments_impl>> { using args_tuple = typename member_function_arguments>::tuple_type; using return_type = typename member_function_arguments>::return_type; }; template - struct callable_arguments_impl>> { + struct callable_arguments_impl>> { using args_tuple = typename member_function_arguments>::tuple_type; using return_type = typename member_function_arguments>::return_type; }; @@ -129,9 +128,9 @@ namespace sqlite_orm { template struct callable_arguments : callable_arguments_impl {}; - template + template struct function_call { - using function_type = F; + using udf_type = UDF; using args_tuple = std::tuple; args_tuple args; @@ -223,24 +222,79 @@ namespace sqlite_orm { (polyfill::is_specialization_of_v) > {}); #endif } + + /* + * 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 { + template + function_call operator()(Args... args) 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(args)...}}; + } + }; } - /** - * Used to call user defined function: `func(...);` +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Specifies that a type is a user-defined scalar function. */ - template - internal::function_call func(Args... args) { - using args_tuple = std::tuple; - using function_args_tuple = typename internal::callable_arguments::args_tuple; - constexpr auto argsCount = std::tuple_size::value; - constexpr auto functionArgsCount = std::tuple_size::value; - static_assert((argsCount == functionArgsCount && - !std::is_same>::value && - internal::validate_pointer_value_types( - polyfill::index_constant(functionArgsCount, argsCount) - 1>{})) || - std::is_same>::value, - "Number of arguments does not match"); - return {std::make_tuple(std::forward(args)...)}; - } + 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. + */ + 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); +#endif + + /** + * Call a user-defined function. + * + * Example: + * struct IdFunc { int oeprator(int arg)() const { return arg; } }; + * // inline: + * select(func(42)); + * // As this is a variable template, you can frame the user-defined function and define a variable for syntactic sugar and legibility: + * inline constexpr auto idfunc = func; + * select(idfunc(42)); + * + */ + template +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + requires(orm_scalar_udf || orm_aggregate_udf) +#endif + SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; } diff --git a/dev/storage_base.h b/dev/storage_base.h index 4ce2be4c4..0701a6057 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -251,7 +251,7 @@ namespace sqlite_orm { */ template void create_scalar_function() { - static_assert(is_scalar_function_v, "F can't be an aggregate function"); + static_assert(is_scalar_udf_v, "F cannot be an aggregate function"); std::stringstream ss; ss << F::name() << std::flush; @@ -261,30 +261,35 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.emplace_back(new user_defined_scalar_function_t{ + this->scalarFunctions.push_back(std::make_unique( std::move(name), argsCount, []() -> int* { return (int*)(new F()); }, /* call = */ - [](sqlite3_context* context, void* functionVoidPointer, int argsCount, sqlite3_value** values) { - auto& function = *static_cast(functionVoidPointer); + [](sqlite3_context* context, void* udfVoidPointer, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfVoidPointer); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); auto result = call(function, std::move(argsTuple)); statement_binder().result(context, result); }, - delete_function_callback, - }); + delete_function_callback)); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, - static_cast(*this->scalarFunctions.back())); + try_to_create_function(db, static_cast(*this->scalarFunctions.back())); } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void create_scalar_function() { + return this->create_scalar_function>(); + } +#endif + /** * Call this to create user defined aggregate function. Can be called at any time no matter connection is opened or no. * T - function class. T must have step member function, fin member function and static name function like this: @@ -312,7 +317,7 @@ namespace sqlite_orm { */ template void create_aggregate_function() { - static_assert(is_aggregate_function_v, "F can't be a scalar function"); + static_assert(is_aggregate_udf_v, "F cannot be a scalar function"); std::stringstream ss; ss << F::name() << std::flush; @@ -322,7 +327,7 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.emplace_back(new user_defined_aggregate_function_t{ + this->aggregateFunctions.push_back(std::make_unique( std::move(name), argsCount, /* create = */ @@ -330,51 +335,69 @@ namespace sqlite_orm { return (int*)(new F()); }, /* step = */ - [](sqlite3_context*, void* functionVoidPointer, int argsCount, sqlite3_value** values) { - auto& function = *static_cast(functionVoidPointer); + [](sqlite3_context*, void* udfVoidPointer, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfVoidPointer); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); call(function, &F::step, std::move(argsTuple)); }, /* finalCall = */ - [](sqlite3_context* context, void* functionVoidPointer) { - auto& function = *static_cast(functionVoidPointer); + [](sqlite3_context* context, void* udfVoidPointer) { + F& function = *static_cast(udfVoidPointer); auto result = function.fin(); statement_binder().result(context, result); }, - delete_function_callback, - }); + delete_function_callback)); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function( - db, - static_cast(*this->aggregateFunctions.back())); + try_to_create_function(db, static_cast(*this->aggregateFunctions.back())); } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void create_aggregate_function() { + return this->create_aggregate_function>(); + } +#endif + /** * Use it to delete scalar function you created before. Can be called at any time no matter connection is open or no. */ template void delete_scalar_function() { - static_assert(is_scalar_function_v, "F cannot be an aggregate function"); + static_assert(is_scalar_udf_v, "F cannot be an aggregate function"); std::stringstream ss; ss << F::name() << std::flush; this->delete_function_impl(ss.str(), this->scalarFunctions); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void delete_scalar_function() { + this->delete_scalar_function>(); + } +#endif + /** * Use it to delete aggregate function you created before. Can be called at any time no matter connection is open or no. */ template void delete_aggregate_function() { - static_assert(is_aggregate_function_v, "F cannot be a scalar function"); + static_assert(is_aggregate_udf_v, "F cannot be a scalar function"); std::stringstream ss; ss << F::name() << std::flush; this->delete_function_impl(ss.str(), this->aggregateFunctions); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void delete_aggregate_function() { + this->delete_aggregate_function>(); + } +#endif + template void create_collation() { collating_function func = [](int leftLength, const void* lhs, int rightLength, const void* rhs) { @@ -620,12 +643,12 @@ namespace sqlite_orm { sqlite3_busy_handler(this->connection->get(), busy_handler_callback, this); } - for(auto& functionPointer: this->scalarFunctions) { - try_to_create_function(db, static_cast(*functionPointer)); + for(auto& udfProxy: this->scalarFunctions) { + try_to_create_function(db, static_cast(*udfProxy)); } - for(auto& functionPointer: this->aggregateFunctions) { - try_to_create_function(db, static_cast(*functionPointer)); + for(auto& udfProxy: this->aggregateFunctions) { + try_to_create_function(db, static_cast(*udfProxy)); } if(this->on_open) { @@ -634,9 +657,9 @@ namespace sqlite_orm { } void delete_function_impl(const std::string& name, - std::vector>& functionsVector) const { - auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& functionPointer) { - return functionPointer->name == name; + std::vector>& functionsVector) const { + auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& udfProxy) { + return udfProxy->name == name; }); if(it != functionsVector.end()) { functionsVector.erase(it); @@ -662,7 +685,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, user_defined_scalar_function_t& function) { + void try_to_create_function(sqlite3* db, scalar_udf_proxy& function) { auto resultCode = sqlite3_create_function_v2(db, function.name.c_str(), function.argumentsCount, @@ -677,7 +700,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, user_defined_aggregate_function_t& function) { + void try_to_create_function(sqlite3* db, aggregate_udf_proxy& function) { auto resultCode = sqlite3_create_function(db, function.name.c_str(), function.argumentsCount, @@ -693,34 +716,30 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); + auto udfProxy = static_cast(sqlite3_user_data(context)); auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); if(*aggregateContextIntPointer == nullptr) { - *aggregateContextIntPointer = functionPointer->create(); + *aggregateContextIntPointer = udfProxy->create(); } - functionPointer->step(context, *aggregateContextIntPointer, argsCount, values); + udfProxy->step(context, *aggregateContextIntPointer, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); + auto udfProxy = static_cast(sqlite3_user_data(context)); auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - functionPointer->finalCall(context, *aggregateContextIntPointer); - functionPointer->destroy(*aggregateContextIntPointer); + udfProxy->finalCall(context, *aggregateContextIntPointer); + udfProxy->destroy(*aggregateContextIntPointer); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); - std::unique_ptr callablePointer(functionPointer->create(), - functionPointer->destroy); - if(functionPointer->argumentsCount != -1 && functionPointer->argumentsCount != argsCount) { + auto udfProxy = static_cast(sqlite3_user_data(context)); + const std::unique_ptr callableGuard(udfProxy->create(), udfProxy->destroy); + if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - functionPointer->run(context, functionPointer, argsCount, values); + udfProxy->run(context, udfProxy, argsCount, values); } template @@ -814,8 +833,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::vector> scalarFunctions; + std::vector> aggregateFunctions; }; } } diff --git a/dev/type_traits.h b/dev/type_traits.h index cc7da32ee..1316adda1 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -1,9 +1,6 @@ #pragma once #include // std::enable_if, std::is_same -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES -#include -#endif #include "functional/cxx_type_traits_polyfill.h" @@ -43,6 +40,11 @@ namespace sqlite_orm { template using type_t = typename T::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_type_t = typename decltype(a)::type; +#endif + template using field_type_t = typename T::field_type; diff --git a/examples/user_defined_functions.cpp b/examples/user_defined_functions.cpp index 34bb30c33..f329a93d2 100644 --- a/examples/user_defined_functions.cpp +++ b/examples/user_defined_functions.cpp @@ -34,6 +34,9 @@ struct SignFunction { return "SIGN"; } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr auto sign = func; +#endif /** * Aggregate function must be defined as a dedicated class with at least three functions: @@ -77,6 +80,9 @@ struct AcceleratedSumFunction { return result; } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr auto accelerated_sum = func; +#endif /** * This is also a scalar function just like `SignFunction` but this function @@ -105,6 +111,9 @@ struct ArithmeticMeanFunction { return result; } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr auto arithmetic_mean = func; +#endif int main() { @@ -124,6 +133,49 @@ int main() { make_table("t", make_column("a", &Table::a), make_column("b", &Table::b), make_column("c", &Table::c))); storage.sync_schema(); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * This function can be called at any time doesn't matter whether connection is open or not. + * To delete created scalar function use `storage.delete_scalar_function()` function call. + */ + storage.create_scalar_function(); + + // SELECT SIGN(3) + auto signRows = storage.select(sign(3)); + cout << "SELECT SIGN(3) = " << signRows.at(0) << endl; + + storage.insert(Table{1, -1, 2}); + storage.insert(Table{2, -2, 4}); + storage.insert(Table{3, -3, 8}); + storage.insert(Table{4, -4, 16}); + + storage.create_aggregate_function(); + + // SELECT ASUM(a), ASUM(b), ASUM(c) + // FROM t + auto aSumRows = + storage.select(columns(accelerated_sum(&Table::a), accelerated_sum(&Table::b), accelerated_sum(&Table::c))); + cout << "SELECT ASUM(a), ASUM(b), ASUM(c) FROM t:" << endl; + for(auto& row: aSumRows) { + cout << '\t' << get<0>(row) << endl; + cout << '\t' << get<1>(row) << endl; + cout << '\t' << get<2>(row) << endl; + } + + storage.create_scalar_function(); + + // SELECT ARITHMETIC_MEAN(5, 6, 7) + auto arithmeticMeanRows1 = storage.select(arithmetic_mean(5, 6, 7)); + cout << "SELECT ARITHMETIC_MEAN(5, 6, 7) = " << arithmeticMeanRows1.front() << endl; + + // SELECT ARITHMETIC_MEAN(-2, 1) + auto arithmeticMeanRows2 = storage.select(arithmetic_mean(-2, 1)); + cout << "SELECT ARITHMETIC_MEAN(-2, 1) = " << arithmeticMeanRows2.front() << endl; + + // SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) + auto arithmeticMeanRows3 = storage.select(arithmetic_mean(-5.5, 4, 13.2, 256.4)); + cout << "SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) = " << arithmeticMeanRows3.front() << endl; +#else /** * This function can be called at any time doesn't matter whether connection is open or not. * To delete created scalar function use `storage.delete_scalar_function()` function call. @@ -166,6 +218,7 @@ int main() { // SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) auto arithmeticMeanRows3 = storage.select(func(-5.5, 4, 13.2, 256.4)); cout << "SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) = " << arithmeticMeanRows3.front() << endl; +#endif return 0; } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index bb20e33b0..a1ecec67b 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -203,9 +203,6 @@ using std::nullptr_t; #pragma once #include // std::enable_if, std::is_same -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES -#include -#endif // #include "functional/cxx_type_traits_polyfill.h" @@ -390,6 +387,11 @@ namespace sqlite_orm { template using type_t = typename T::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_type_t = typename decltype(a)::type; +#endif + template using field_type_t = typename T::field_type; @@ -3009,7 +3011,7 @@ namespace sqlite_orm { template concept orm_alias = std::derived_from; - /** @short Alias of a column in a record set. + /** @short Specifies that a type is an alias of a column in a record set. * * A column alias has the following traits: * - is derived from `alias_tag` @@ -3018,7 +3020,7 @@ namespace sqlite_orm { template concept orm_column_alias = (orm_alias && !orm_names_type); - /** @short Alias of any type of record set. + /** @short Specifies that a type is an alias of any type of record set. * * A record set alias has the following traits: * - is derived from `alias_tag`. @@ -3027,7 +3029,7 @@ namespace sqlite_orm { template concept orm_recordset_alias = (orm_alias && orm_names_type); - /** @short Alias of a concrete table. + /** @short Specifies that a type is an alias of a concrete table. * * A concrete table alias has the following traits: * - is derived from `alias_tag`. @@ -3036,7 +3038,7 @@ namespace sqlite_orm { template concept orm_table_alias = (orm_recordset_alias && !std::same_as>); - /** @short Reference of a concrete table, especially of a derived class. + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. * * A concrete table reference has the following traits: * - specialization of `table_reference`, whose `type` typename references a mapped object. @@ -3044,12 +3046,18 @@ namespace sqlite_orm { template concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + /** @short Specifies that a type refers to a mapped table (possibly aliased). + */ template concept orm_refers_to_table = (orm_table_reference || orm_table_alias); + /** @short Specifies that a type refers to a recordset. + */ template concept orm_refers_to_recordset = (orm_table_reference || orm_recordset_alias); + /** @short Specifies that a type is a mapped recordset (table reference). + */ template concept orm_mapped_recordset = (orm_table_reference); #endif @@ -3152,7 +3160,7 @@ namespace sqlite_orm { // #include "column_pointer.h" -#include // std::enable_if, std::remove_const +#include // std::enable_if #include // std::move // #include "functional/cxx_type_traits_polyfill.h" @@ -3202,8 +3210,7 @@ namespace sqlite_orm { */ template constexpr auto column(F O::*field) { - using R = std::remove_const_t; - return column(field); + return column>(field); } /** @@ -4512,7 +4519,7 @@ namespace sqlite_orm { } #pragma once -#include // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const +#include // std::enable_if #include // std::index_sequence, std::make_index_sequence #include // std::string #include // std::stringstream @@ -4736,7 +4743,7 @@ namespace sqlite_orm { template constexpr auto alias_column(C field) { using namespace ::sqlite_orm::internal; - using A = std::remove_const_t; + using A = decltype(als); using aliased_type = type_t; static_assert(is_field_of_v, "Column must be from aliased table"); @@ -4780,7 +4787,7 @@ namespace sqlite_orm { */ template auto as(E expression) { - return internal::as_t, E>{std::move(expression)}; + return internal::as_t{std::move(expression)}; } /** @@ -4800,7 +4807,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto get() { - return internal::alias_holder>{}; + return internal::alias_holder{}; } #endif @@ -10863,9 +10870,9 @@ namespace sqlite_orm { // #include "function.h" #include -#include +#include // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type #include // std::string -#include // std::tuple +#include // std::tuple, std::tuple_size, std::tuple_element #include // std::function #include // std::min #include // std::move, std::forward @@ -10885,7 +10892,7 @@ namespace sqlite_orm { namespace internal { - struct user_defined_function_base { + struct udf_proxy_base { using func_call = std::function< void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>; using final_call = std::function; @@ -10896,38 +10903,38 @@ namespace sqlite_orm { void (*destroy)(int*) = nullptr; #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - user_defined_function_base(decltype(name) name_, - decltype(argumentsCount) argumentsCount_, - decltype(create) create_, - decltype(destroy) destroy_) : + udf_proxy_base(decltype(name) name_, + decltype(argumentsCount) argumentsCount_, + decltype(create) create_, + decltype(destroy) destroy_) : name(std::move(name_)), argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} #endif }; - struct user_defined_scalar_function_t : user_defined_function_base { + struct scalar_udf_proxy : udf_proxy_base { func_call run; - user_defined_scalar_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(run) run_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, + scalar_udf_proxy(decltype(name) name_, + int argumentsCount_, + decltype(create) create_, + decltype(run) run_, + decltype(destroy) destroy_) : + udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, run(std::move(run_)) {} }; - struct user_defined_aggregate_function_t : user_defined_function_base { + struct aggregate_udf_proxy : udf_proxy_base { func_call step; final_call finalCall; - user_defined_aggregate_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(step) step_, - decltype(finalCall) finalCall_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, + aggregate_udf_proxy(decltype(name) name_, + int argumentsCount_, + decltype(create) create_, + decltype(step) step_, + decltype(finalCall) finalCall_, + decltype(destroy) destroy_) : + udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, step(std::move(step_)), finalCall(std::move(finalCall_)) {} }; @@ -10941,15 +10948,14 @@ namespace sqlite_orm { using aggregate_fin_function_t = decltype(&F::fin); template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v = false; + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v>> = - true; + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v>> = true; template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v = false; + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v< + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v< F, polyfill::void_t, aggregate_fin_function_t, @@ -10978,13 +10984,13 @@ namespace sqlite_orm { struct callable_arguments_impl; template - struct callable_arguments_impl>> { + struct callable_arguments_impl>> { using args_tuple = typename member_function_arguments>::tuple_type; using return_type = typename member_function_arguments>::return_type; }; template - struct callable_arguments_impl>> { + struct callable_arguments_impl>> { using args_tuple = typename member_function_arguments>::tuple_type; using return_type = typename member_function_arguments>::return_type; }; @@ -10992,9 +10998,9 @@ namespace sqlite_orm { template struct callable_arguments : callable_arguments_impl {}; - template + template struct function_call { - using function_type = F; + using udf_type = UDF; using args_tuple = std::tuple; args_tuple args; @@ -11086,26 +11092,81 @@ namespace sqlite_orm { (polyfill::is_specialization_of_v) > {}); #endif } + + /* + * 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 { + template + function_call operator()(Args... args) 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(args)...}}; + } + }; } - /** - * Used to call user defined function: `func(...);` +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Specifies that a type is a user-defined scalar function. */ - template - internal::function_call func(Args... args) { - using args_tuple = std::tuple; - using function_args_tuple = typename internal::callable_arguments::args_tuple; - constexpr auto argsCount = std::tuple_size::value; - constexpr auto functionArgsCount = std::tuple_size::value; - static_assert((argsCount == functionArgsCount && - !std::is_same>::value && - internal::validate_pointer_value_types( - polyfill::index_constant(functionArgsCount, argsCount) - 1>{})) || - std::is_same>::value, - "Number of arguments does not match"); - return {std::make_tuple(std::forward(args)...)}; - } + 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. + */ + 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); +#endif + + /** + * Call a user-defined function. + * + * Example: + * struct IdFunc { int oeprator(int arg)() const { return arg; } }; + * // inline: + * select(func(42)); + * // As this is a variable template, you can frame the user-defined function and define a variable for syntactic sugar and legibility: + * inline constexpr auto idfunc = func; + * select(idfunc(42)); + * + */ + template +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + requires(orm_scalar_udf || orm_aggregate_udf) +#endif + SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; } // #include "ast/special_keywords.h" @@ -14998,7 +15059,7 @@ namespace sqlite_orm { */ template void create_scalar_function() { - static_assert(is_scalar_function_v, "F can't be an aggregate function"); + static_assert(is_scalar_udf_v, "F cannot be an aggregate function"); std::stringstream ss; ss << F::name() << std::flush; @@ -15008,30 +15069,35 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.emplace_back(new user_defined_scalar_function_t{ + this->scalarFunctions.push_back(std::make_unique( std::move(name), argsCount, []() -> int* { return (int*)(new F()); }, /* call = */ - [](sqlite3_context* context, void* functionVoidPointer, int argsCount, sqlite3_value** values) { - auto& function = *static_cast(functionVoidPointer); + [](sqlite3_context* context, void* udfVoidPointer, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfVoidPointer); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); auto result = call(function, std::move(argsTuple)); statement_binder().result(context, result); }, - delete_function_callback, - }); + delete_function_callback)); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, - static_cast(*this->scalarFunctions.back())); + try_to_create_function(db, static_cast(*this->scalarFunctions.back())); } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void create_scalar_function() { + return this->create_scalar_function>(); + } +#endif + /** * Call this to create user defined aggregate function. Can be called at any time no matter connection is opened or no. * T - function class. T must have step member function, fin member function and static name function like this: @@ -15059,7 +15125,7 @@ namespace sqlite_orm { */ template void create_aggregate_function() { - static_assert(is_aggregate_function_v, "F can't be a scalar function"); + static_assert(is_aggregate_udf_v, "F cannot be a scalar function"); std::stringstream ss; ss << F::name() << std::flush; @@ -15069,7 +15135,7 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.emplace_back(new user_defined_aggregate_function_t{ + this->aggregateFunctions.push_back(std::make_unique( std::move(name), argsCount, /* create = */ @@ -15077,51 +15143,69 @@ namespace sqlite_orm { return (int*)(new F()); }, /* step = */ - [](sqlite3_context*, void* functionVoidPointer, int argsCount, sqlite3_value** values) { - auto& function = *static_cast(functionVoidPointer); + [](sqlite3_context*, void* udfVoidPointer, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfVoidPointer); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); call(function, &F::step, std::move(argsTuple)); }, /* finalCall = */ - [](sqlite3_context* context, void* functionVoidPointer) { - auto& function = *static_cast(functionVoidPointer); + [](sqlite3_context* context, void* udfVoidPointer) { + F& function = *static_cast(udfVoidPointer); auto result = function.fin(); statement_binder().result(context, result); }, - delete_function_callback, - }); + delete_function_callback)); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function( - db, - static_cast(*this->aggregateFunctions.back())); + try_to_create_function(db, static_cast(*this->aggregateFunctions.back())); } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void create_aggregate_function() { + return this->create_aggregate_function>(); + } +#endif + /** * Use it to delete scalar function you created before. Can be called at any time no matter connection is open or no. */ template void delete_scalar_function() { - static_assert(is_scalar_function_v, "F cannot be an aggregate function"); + static_assert(is_scalar_udf_v, "F cannot be an aggregate function"); std::stringstream ss; ss << F::name() << std::flush; this->delete_function_impl(ss.str(), this->scalarFunctions); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void delete_scalar_function() { + this->delete_scalar_function>(); + } +#endif + /** * Use it to delete aggregate function you created before. Can be called at any time no matter connection is open or no. */ template void delete_aggregate_function() { - static_assert(is_aggregate_function_v, "F cannot be a scalar function"); + static_assert(is_aggregate_udf_v, "F cannot be a scalar function"); std::stringstream ss; ss << F::name() << std::flush; this->delete_function_impl(ss.str(), this->aggregateFunctions); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void delete_aggregate_function() { + this->delete_aggregate_function>(); + } +#endif + template void create_collation() { collating_function func = [](int leftLength, const void* lhs, int rightLength, const void* rhs) { @@ -15367,12 +15451,12 @@ namespace sqlite_orm { sqlite3_busy_handler(this->connection->get(), busy_handler_callback, this); } - for(auto& functionPointer: this->scalarFunctions) { - try_to_create_function(db, static_cast(*functionPointer)); + for(auto& udfProxy: this->scalarFunctions) { + try_to_create_function(db, static_cast(*udfProxy)); } - for(auto& functionPointer: this->aggregateFunctions) { - try_to_create_function(db, static_cast(*functionPointer)); + for(auto& udfProxy: this->aggregateFunctions) { + try_to_create_function(db, static_cast(*udfProxy)); } if(this->on_open) { @@ -15381,9 +15465,9 @@ namespace sqlite_orm { } void delete_function_impl(const std::string& name, - std::vector>& functionsVector) const { - auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& functionPointer) { - return functionPointer->name == name; + std::vector>& functionsVector) const { + auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& udfProxy) { + return udfProxy->name == name; }); if(it != functionsVector.end()) { functionsVector.erase(it); @@ -15409,7 +15493,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, user_defined_scalar_function_t& function) { + void try_to_create_function(sqlite3* db, scalar_udf_proxy& function) { auto resultCode = sqlite3_create_function_v2(db, function.name.c_str(), function.argumentsCount, @@ -15424,7 +15508,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, user_defined_aggregate_function_t& function) { + void try_to_create_function(sqlite3* db, aggregate_udf_proxy& function) { auto resultCode = sqlite3_create_function(db, function.name.c_str(), function.argumentsCount, @@ -15440,34 +15524,30 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); + auto udfProxy = static_cast(sqlite3_user_data(context)); auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); if(*aggregateContextIntPointer == nullptr) { - *aggregateContextIntPointer = functionPointer->create(); + *aggregateContextIntPointer = udfProxy->create(); } - functionPointer->step(context, *aggregateContextIntPointer, argsCount, values); + udfProxy->step(context, *aggregateContextIntPointer, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); + auto udfProxy = static_cast(sqlite3_user_data(context)); auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - functionPointer->finalCall(context, *aggregateContextIntPointer); - functionPointer->destroy(*aggregateContextIntPointer); + udfProxy->finalCall(context, *aggregateContextIntPointer); + udfProxy->destroy(*aggregateContextIntPointer); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); - std::unique_ptr callablePointer(functionPointer->create(), - functionPointer->destroy); - if(functionPointer->argumentsCount != -1 && functionPointer->argumentsCount != argsCount) { + auto udfProxy = static_cast(sqlite3_user_data(context)); + const std::unique_ptr callableGuard(udfProxy->create(), udfProxy->destroy); + if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - functionPointer->run(context, functionPointer, argsCount, values); + udfProxy->run(context, udfProxy, argsCount, values); } template @@ -15561,8 +15641,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::vector> scalarFunctions; + std::vector> aggregateFunctions; }; } } diff --git a/tests/ast_iterator_tests.cpp b/tests/ast_iterator_tests.cpp index f99bcf360..32a5a8a79 100644 --- a/tests/ast_iterator_tests.cpp +++ b/tests/ast_iterator_tests.cpp @@ -272,6 +272,7 @@ TEST_CASE("ast_iterator") { } SECTION("function_call") { struct Func { + static const char* name(); bool operator()(int value) const { return value % 2 == 0; } diff --git a/tests/static_tests/alias.cpp b/tests/static_tests/alias.cpp index b0e8e4dbd..51295a1cf 100644 --- a/tests/static_tests/alias.cpp +++ b/tests/static_tests/alias.cpp @@ -47,10 +47,11 @@ TEST_CASE("aliases") { runTest, column_pointer>>( alias_column>(&User::id)); #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - runTest>>(get<"a"_col>()); - runTest>("a"_col); - runTest, int User::*>>(as<"a"_col>(&User::id)); - runTest, int User::*>>(&User::id >>= "a"_col); + constexpr auto a_col = "a"_col; + runTest>>(get()); + runTest>(a_col); + runTest, int User::*>>(as(&User::id)); + runTest, int User::*>>(&User::id >>= a_col); runTest>(alias<'a', 'l', 's'>.for_()); constexpr auto z_alias = "z"_alias.for_(); runTest>(z_alias); diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index f2207e7f6..2524951e3 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -79,6 +79,7 @@ TEST_CASE("column_result_of_t") { runTest(count()); { struct RandomFunc { + static const char* name(); int operator()() const { return 4; } diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index 9b0206990..f29092cb4 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -3,8 +3,24 @@ #include // std::is_same using namespace sqlite_orm; -using internal::is_aggregate_function_v; -using internal::is_scalar_function_v; +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 +template +concept storage_scalar_callable = requires(S& storage) { + { storage.create_scalar_function() }; + { storage.delete_scalar_function() }; +}; + +template +concept storage_aggregate_callable = requires(S& storage) { + { storage.create_aggregate_function() }; + { storage.delete_aggregate_function() }; +}; +#endif TEST_CASE("function static") { SECTION("scalar") { @@ -35,8 +51,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE_FALSE(is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = double (Function::*)(double) const; @@ -57,8 +73,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE_FALSE(is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = double (Function::*)(double); @@ -79,8 +95,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE_FALSE(is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = int (Function::*)(std::string) const; @@ -101,8 +117,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE_FALSE(is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = int (Function::*)(std::string); @@ -123,8 +139,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE_FALSE(is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = std::string (Function::*)(const std::string&, const std::string&) const; @@ -145,8 +161,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE_FALSE(is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = std::string (Function::*)(const std::string&, const std::string&); @@ -178,8 +194,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_aggregate_function_v); - STATIC_REQUIRE_FALSE(is_scalar_function_v); + STATIC_REQUIRE(is_aggregate_udf_v); + STATIC_REQUIRE_FALSE(is_scalar_udf_v); using StepMemberFunctionPointer = internal::aggregate_step_function_t; using ExpectedStepType = void (Function::*)(int); @@ -205,8 +221,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_aggregate_function_v); - STATIC_REQUIRE_FALSE(is_scalar_function_v); + STATIC_REQUIRE(is_aggregate_udf_v); + STATIC_REQUIRE_FALSE(is_scalar_udf_v); using StepMemberFunctionPointer = internal::aggregate_step_function_t; using ExpectedStepType = void (Function::*)(std::string) const; @@ -221,4 +237,36 @@ TEST_CASE("function static") { std::is_same::args_tuple, std::tuple>::value); } } + SECTION("function call expressions") { + struct SFunction { + static const char* name(); + int operator()(int) const; + }; + struct AFunction { + static const char* name(); + void step(int); + int fin() const; + }; + + constexpr auto scalar = func; + constexpr auto aggregate = func; + + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(orm_scalar_function); + STATIC_REQUIRE_FALSE(orm_aggregate_function); + STATIC_REQUIRE(orm_aggregate_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + STATIC_REQUIRE_FALSE(storage_aggregate_callable); + STATIC_REQUIRE(storage_aggregate_callable); + STATIC_REQUIRE_FALSE(storage_scalar_callable); +#endif + } } diff --git a/tests/static_tests/node_tuple.cpp b/tests/static_tests/node_tuple.cpp index 45234416c..ea114f9e7 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -916,6 +916,7 @@ TEST_CASE("Node tuple") { } SECTION("function_call") { struct Func { + static const char* name(); bool operator()(int value) const { return value % 2; } From 36b5976bc06eb5ba8a08feb7a0c6e384a1c16385 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 13:29:56 +0200 Subject: [PATCH 02/50] Table reference can be specified in call to `make_table()` --- dev/column_pointer.h | 4 +- dev/schema/table.h | 13 ++ include/sqlite_orm/sqlite_orm.h | 18 +- tests/static_tests/column_pointer.cpp | 239 +++++++++++++------------- 4 files changed, 151 insertions(+), 123 deletions(-) diff --git a/dev/column_pointer.h b/dev/column_pointer.h index 044856b7f..4d9e4f200 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -64,7 +64,7 @@ namespace sqlite_orm { */ template requires(!orm_recordset_alias) - constexpr internal::table_reference column() { + consteval internal::table_reference column() { return {}; } @@ -73,7 +73,7 @@ namespace sqlite_orm { */ template requires(!orm_recordset_alias) - constexpr internal::table_reference c() { + consteval internal::table_reference c() { return {}; } #endif diff --git a/dev/schema/table.h b/dev/schema/table.h index 86837602f..a8ed5ef42 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -19,6 +19,7 @@ #include "../member_traits/member_traits.h" #include "../typed_comparator.h" #include "../type_traits.h" +#include "../alias_traits.h" #include "../constraints.h" #include "../table_info.h" #include "column.h" @@ -408,6 +409,18 @@ namespace sqlite_orm { return {std::move(name), std::make_tuple(std::forward(args)...)}); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Factory function for a table definition. + * + * The mapped object type is explicitly specified. + */ + template + auto make_table(std::string name, Cs... args) { + return make_table>(std::move(name), std::forward(args)...); + } +#endif + template internal::virtual_table_t make_virtual_table(std::string name, M module_details) { SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)}); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index a1ecec67b..659231273 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -3226,7 +3226,7 @@ namespace sqlite_orm { */ template requires(!orm_recordset_alias) - constexpr internal::table_reference column() { + consteval internal::table_reference column() { return {}; } @@ -3235,7 +3235,7 @@ namespace sqlite_orm { */ template requires(!orm_recordset_alias) - constexpr internal::table_reference c() { + consteval internal::table_reference c() { return {}; } #endif @@ -10043,6 +10043,8 @@ namespace sqlite_orm { // #include "../type_traits.h" +// #include "../alias_traits.h" + // #include "../constraints.h" // #include "../table_info.h" @@ -10434,6 +10436,18 @@ namespace sqlite_orm { return {std::move(name), std::make_tuple(std::forward(args)...)}); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Factory function for a table definition. + * + * The mapped object type is explicitly specified. + */ + template + auto make_table(std::string name, Cs... args) { + return make_table>(std::move(name), std::forward(args)...); + } +#endif + template internal::virtual_table_t make_virtual_table(std::string name, M module_details) { SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)}); diff --git a/tests/static_tests/column_pointer.cpp b/tests/static_tests/column_pointer.cpp index d0d4950a4..ceac4a6c6 100644 --- a/tests/static_tests/column_pointer.cpp +++ b/tests/static_tests/column_pointer.cpp @@ -1,119 +1,120 @@ -#include -#include // std::is_same -#include - -using namespace sqlite_orm; -using internal::column_pointer; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES -using internal::is_recordset_alias_v; -using internal::is_table_alias_v; -using internal::table_reference; -using internal::using_t; -#endif - -template -void do_assert() { - STATIC_REQUIRE(std::is_same::value); -} - -template -void runTest(const T& /*test*/) { - do_assert(); -} - -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES -template -concept field_callable = requires(C field) { - { count(field) }; - { avg(field) }; - { max(field) }; - { min(field) }; - { sum(field) }; - { total(field) }; - { group_concat(field) }; -}; - -template -concept storage_field_callable = requires(S& storage, C field) { - { storage.count(field) }; - { storage.avg(field) }; - { storage.max(field) }; - { storage.min(field) }; - { storage.sum(field) }; - { storage.total(field) }; - { storage.group_concat(field) }; - { storage.group_concat(field, "") }; - { storage.group_concat(field, std::string{}) }; - { storage.group_concat(field, 42) }; -}; - -template -concept storage_table_reference_callable = requires(S& storage) { - { storage.get(42) }; - { storage.get_all() }; - { storage.count() }; -}; -#endif - -TEST_CASE("column pointers") { - struct User { - int id; - }; - struct DerivedUser : User {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - constexpr auto derived_user = c(); -#endif - - SECTION("table reference") { -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - STATIC_REQUIRE(orm_table_reference); - STATIC_REQUIRE_FALSE(is_table_alias_v); - STATIC_REQUIRE_FALSE(is_recordset_alias_v); - STATIC_REQUIRE_FALSE(orm_table_alias); - STATIC_REQUIRE_FALSE(orm_recordset_alias); - runTest>(derived_user); - runTest(internal::decay_table_reference_t{}); -#endif - } - SECTION("column pointer expressions") { - runTest>(column(&User::id)); - runTest>(column(&DerivedUser::id)); -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - runTest>(derived_user->*&DerivedUser::id); - STATIC_REQUIRE(field_callable); - STATIC_REQUIRE(field_callable*&DerivedUser::id)>); - - using storage_type = decltype(make_storage( - "", - make_table("user", make_column("id", &User::id, primary_key())), - make_table("derived_user", make_column("id", &DerivedUser::id, primary_key())))); - - STATIC_REQUIRE(storage_field_callable); - STATIC_REQUIRE(storage_field_callable*&DerivedUser::id)>); -#endif - } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - SECTION("table reference expressions") { - runTest>(from()); - runTest>(asterisk()); - runTest>(object()); - runTest>(count()); - runTest>(get(42)); - runTest>>(get_all()); - runTest>>( - left_join(using_(derived_user->*&DerivedUser::id))); - runTest>>( - join(using_(derived_user->*&DerivedUser::id))); - runTest>>( - left_outer_join(using_(derived_user->*&DerivedUser::id))); - runTest>>( - inner_join(using_(derived_user->*&DerivedUser::id))); - - using storage_type = decltype(make_storage( - "", - make_table("derived_user", make_column("id", &DerivedUser::id, primary_key())))); - - STATIC_REQUIRE(storage_table_reference_callable); - } -#endif -} +#include +#include // std::is_same +#include + +using namespace sqlite_orm; +using internal::column_pointer; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +using internal::is_recordset_alias_v; +using internal::is_table_alias_v; +using internal::table_reference; +using internal::using_t; +#endif + +template +void do_assert() { + STATIC_REQUIRE(std::is_same::value); +} + +template +void runTest(const T& /*test*/) { + do_assert(); +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +template +concept field_callable = requires(C field) { + { count(field) }; + { avg(field) }; + { max(field) }; + { min(field) }; + { sum(field) }; + { total(field) }; + { group_concat(field) }; +}; + +template +concept storage_field_callable = requires(S& storage, C field) { + { storage.count(field) }; + { storage.avg(field) }; + { storage.max(field) }; + { storage.min(field) }; + { storage.sum(field) }; + { storage.total(field) }; + { storage.group_concat(field) }; + { storage.group_concat(field, "") }; + { storage.group_concat(field, std::string{}) }; + { storage.group_concat(field, 42) }; +}; + +template +concept storage_table_reference_callable = requires(S& storage) { + { storage.get(42) }; + { storage.get_all() }; + { storage.count() }; +}; +#endif + +TEST_CASE("column pointers") { + struct User { + int id; + }; + struct DerivedUser : User {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto derived_user = c(); +#endif + + SECTION("table reference") { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(orm_table_reference); + STATIC_REQUIRE_FALSE(is_table_alias_v); + STATIC_REQUIRE_FALSE(is_recordset_alias_v); + STATIC_REQUIRE_FALSE(orm_table_alias); + STATIC_REQUIRE_FALSE(orm_recordset_alias); + runTest>(derived_user); + runTest(internal::decay_table_reference_t{}); +#endif + } + SECTION("column pointer expressions") { + runTest>(column(&User::id)); + runTest>(column(&DerivedUser::id)); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + runTest>(derived_user->*&DerivedUser::id); + STATIC_REQUIRE(field_callable); + STATIC_REQUIRE(field_callable*&DerivedUser::id)>); + + using storage_type = decltype(make_storage( + "", + make_table("user", make_column("id", &User::id, primary_key())), + make_table("derived_user", make_column("id", &DerivedUser::id, primary_key())))); + + STATIC_REQUIRE(storage_field_callable); + STATIC_REQUIRE(storage_field_callable*&DerivedUser::id)>); +#endif + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("table reference expressions") { + runTest>(make_table("derived_user")); + runTest>(from()); + runTest>(asterisk()); + runTest>(object()); + runTest>(count()); + runTest>(get(42)); + runTest>>(get_all()); + runTest>>( + left_join(using_(derived_user->*&DerivedUser::id))); + runTest>>( + join(using_(derived_user->*&DerivedUser::id))); + runTest>>( + left_outer_join(using_(derived_user->*&DerivedUser::id))); + runTest>>( + inner_join(using_(derived_user->*&DerivedUser::id))); + + using storage_type = decltype(make_storage( + "", + make_table("derived_user", make_column("id", &DerivedUser::id, primary_key())))); + + STATIC_REQUIRE(storage_table_reference_callable); + } +#endif +} From 4730cc34d8753e2d5c390bb3a2e4f3e0f15c74cd Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 13:37:47 +0200 Subject: [PATCH 03/50] Corrected requirement of `storage_t<>.count()` --- dev/storage.h | 6 +++--- include/sqlite_orm/sqlite_orm.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev/storage.h b/dev/storage.h index a0ebb932e..e2eb8393e 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -383,9 +383,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto get(Ids... ids) { - return this->get>(std::forward(ids)...); + return this->get>(std::forward(ids)...); } #endif @@ -448,7 +448,7 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template int count(Args&&... args) { return this->count>(std::forward(args)...); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 659231273..c4c75a220 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -18562,9 +18562,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto get(Ids... ids) { - return this->get>(std::forward(ids)...); + return this->get>(std::forward(ids)...); } #endif @@ -18627,7 +18627,7 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template int count(Args&&... args) { return this->count>(std::forward(args)...); } From c6d51042adf06880f952ee3207c8dd3d41fcacd0 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 13:49:55 +0200 Subject: [PATCH 04/50] Renamed a few variables related to the context of user-defined functions --- dev/storage_base.h | 46 ++++++++++++++++----------------- include/sqlite_orm/sqlite_orm.h | 46 ++++++++++++++++----------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 0701a6057..0132399ca 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -268,8 +268,8 @@ namespace sqlite_orm { return (int*)(new F()); }, /* call = */ - [](sqlite3_context* context, void* udfVoidPointer, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfVoidPointer); + [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); auto result = call(function, std::move(argsTuple)); @@ -335,15 +335,15 @@ namespace sqlite_orm { return (int*)(new F()); }, /* step = */ - [](sqlite3_context*, void* udfVoidPointer, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfVoidPointer); + [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); call(function, &F::step, std::move(argsTuple)); }, /* finalCall = */ - [](sqlite3_context* context, void* udfVoidPointer) { - F& function = *static_cast(udfVoidPointer); + [](sqlite3_context* context, void* udfHandle) { + F& function = *static_cast(udfHandle); auto result = function.fin(); statement_binder().result(context, result); }, @@ -685,12 +685,12 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, scalar_udf_proxy& function) { + void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { auto resultCode = sqlite3_create_function_v2(db, - function.name.c_str(), - function.argumentsCount, + udfProxy.name.c_str(), + udfProxy.argumentsCount, SQLITE_UTF8, - &function, + &udfProxy, scalar_function_callback, nullptr, nullptr, @@ -700,12 +700,12 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, aggregate_udf_proxy& function) { + void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { auto resultCode = sqlite3_create_function(db, - function.name.c_str(), - function.argumentsCount, + udfProxy.name.c_str(), + udfProxy.argumentsCount, SQLITE_UTF8, - &function, + &udfProxy, nullptr, aggregate_function_step_callback, aggregate_function_final_callback); @@ -717,20 +717,20 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - if(*aggregateContextIntPointer == nullptr) { - *aggregateContextIntPointer = udfProxy->create(); + auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); + auto aggregateContextIntHandle = static_cast(aggregateContextHandle); + if(*aggregateContextIntHandle == nullptr) { + *aggregateContextIntHandle = udfProxy->create(); } - udfProxy->step(context, *aggregateContextIntPointer, argsCount, values); + udfProxy->step(context, *aggregateContextIntHandle, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - udfProxy->finalCall(context, *aggregateContextIntPointer); - udfProxy->destroy(*aggregateContextIntPointer); + auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); + auto aggregateContextIntHandle = static_cast(aggregateContextHandle); + udfProxy->finalCall(context, *aggregateContextIntHandle); + udfProxy->destroy(*aggregateContextIntHandle); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index c4c75a220..9cb078ee9 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15090,8 +15090,8 @@ namespace sqlite_orm { return (int*)(new F()); }, /* call = */ - [](sqlite3_context* context, void* udfVoidPointer, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfVoidPointer); + [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); auto result = call(function, std::move(argsTuple)); @@ -15157,15 +15157,15 @@ namespace sqlite_orm { return (int*)(new F()); }, /* step = */ - [](sqlite3_context*, void* udfVoidPointer, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfVoidPointer); + [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { + F& function = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); call(function, &F::step, std::move(argsTuple)); }, /* finalCall = */ - [](sqlite3_context* context, void* udfVoidPointer) { - F& function = *static_cast(udfVoidPointer); + [](sqlite3_context* context, void* udfHandle) { + F& function = *static_cast(udfHandle); auto result = function.fin(); statement_binder().result(context, result); }, @@ -15507,12 +15507,12 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, scalar_udf_proxy& function) { + void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { auto resultCode = sqlite3_create_function_v2(db, - function.name.c_str(), - function.argumentsCount, + udfProxy.name.c_str(), + udfProxy.argumentsCount, SQLITE_UTF8, - &function, + &udfProxy, scalar_function_callback, nullptr, nullptr, @@ -15522,12 +15522,12 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, aggregate_udf_proxy& function) { + void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { auto resultCode = sqlite3_create_function(db, - function.name.c_str(), - function.argumentsCount, + udfProxy.name.c_str(), + udfProxy.argumentsCount, SQLITE_UTF8, - &function, + &udfProxy, nullptr, aggregate_function_step_callback, aggregate_function_final_callback); @@ -15539,20 +15539,20 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - if(*aggregateContextIntPointer == nullptr) { - *aggregateContextIntPointer = udfProxy->create(); + auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); + auto aggregateContextIntHandle = static_cast(aggregateContextHandle); + if(*aggregateContextIntHandle == nullptr) { + *aggregateContextIntHandle = udfProxy->create(); } - udfProxy->step(context, *aggregateContextIntPointer, argsCount, values); + udfProxy->step(context, *aggregateContextIntHandle, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - udfProxy->finalCall(context, *aggregateContextIntPointer); - udfProxy->destroy(*aggregateContextIntPointer); + auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); + auto aggregateContextIntHandle = static_cast(aggregateContextHandle); + udfProxy->finalCall(context, *aggregateContextIntHandle); + udfProxy->destroy(*aggregateContextIntHandle); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { From d86520e0f8e90ce86983e357a5bac970cbec5315 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 13:54:10 +0200 Subject: [PATCH 05/50] Explicitly typed result code variables --- dev/storage_base.h | 77 ++++++++++++++++----------------- include/sqlite_orm/sqlite_orm.h | 77 ++++++++++++++++----------------- 2 files changed, 76 insertions(+), 78 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 0132399ca..cc2a2fe83 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -421,12 +421,12 @@ namespace sqlite_orm { // create collations if db is open if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - auto resultCode = sqlite3_create_collation(db, - name.c_str(), - SQLITE_UTF8, - function, - functionExists ? collate_callback : nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_collation(db, + name.c_str(), + SQLITE_UTF8, + function, + functionExists ? collate_callback : nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -628,9 +628,8 @@ namespace sqlite_orm { } for(auto& p: this->collatingFunctions) { - auto resultCode = - sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -667,16 +666,16 @@ namespace sqlite_orm { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - auto resultCode = sqlite3_create_function_v2(db, - name.c_str(), - 0, - SQLITE_UTF8, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_function_v2(db, + name.c_str(), + 0, + SQLITE_UTF8, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -686,31 +685,31 @@ namespace sqlite_orm { } void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { - auto resultCode = sqlite3_create_function_v2(db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - scalar_function_callback, - nullptr, - nullptr, - nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_function_v2(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + scalar_function_callback, + nullptr, + nullptr, + nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { - auto resultCode = sqlite3_create_function(db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - nullptr, - aggregate_function_step_callback, - aggregate_function_final_callback); - if(resultCode != SQLITE_OK) { - throw_translated_sqlite_error(resultCode); + int rc = sqlite3_create_function(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + nullptr, + aggregate_function_step_callback, + aggregate_function_final_callback); + if(rc != SQLITE_OK) { + throw_translated_sqlite_error(rc); } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 9cb078ee9..7bc16e57e 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15243,12 +15243,12 @@ namespace sqlite_orm { // create collations if db is open if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - auto resultCode = sqlite3_create_collation(db, - name.c_str(), - SQLITE_UTF8, - function, - functionExists ? collate_callback : nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_collation(db, + name.c_str(), + SQLITE_UTF8, + function, + functionExists ? collate_callback : nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -15450,9 +15450,8 @@ namespace sqlite_orm { } for(auto& p: this->collatingFunctions) { - auto resultCode = - sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -15489,16 +15488,16 @@ namespace sqlite_orm { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - auto resultCode = sqlite3_create_function_v2(db, - name.c_str(), - 0, - SQLITE_UTF8, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_function_v2(db, + name.c_str(), + 0, + SQLITE_UTF8, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -15508,31 +15507,31 @@ namespace sqlite_orm { } void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { - auto resultCode = sqlite3_create_function_v2(db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - scalar_function_callback, - nullptr, - nullptr, - nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_function_v2(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + scalar_function_callback, + nullptr, + nullptr, + nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { - auto resultCode = sqlite3_create_function(db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - nullptr, - aggregate_function_step_callback, - aggregate_function_final_callback); - if(resultCode != SQLITE_OK) { - throw_translated_sqlite_error(resultCode); + int rc = sqlite3_create_function(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + nullptr, + aggregate_function_step_callback, + aggregate_function_final_callback); + if(rc != SQLITE_OK) { + throw_translated_sqlite_error(rc); } } From f997adce6c506d302d96e1ef312626ea5bc49045 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 14:24:33 +0200 Subject: [PATCH 06/50] Delete functions after successfully hitting sqlite3 --- dev/storage_base.h | 34 ++++++++++++++++++++------------- include/sqlite_orm/sqlite_orm.h | 34 ++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index cc2a2fe83..46851cd4c 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -10,7 +10,7 @@ #include // std::make_unique, std::unique_ptr #include // std::map #include // std::is_same -#include // std::find_if +#include // std::find_if, std::ranges::find #include "functional/cxx_universal.h" // ::size_t #include "tuple_helper/tuple_iteration.h" @@ -410,12 +410,10 @@ namespace sqlite_orm { } void create_collation(const std::string& name, collating_function f) { - collating_function* function = nullptr; const auto functionExists = bool(f); + collating_function* function = nullptr; if(functionExists) { function = &(collatingFunctions[name] = std::move(f)); - } else { - collatingFunctions.erase(name); } // create collations if db is open @@ -430,6 +428,10 @@ namespace sqlite_orm { throw_translated_sqlite_error(db); } } + + if(!functionExists) { + collatingFunctions.erase(name); + } } template @@ -656,14 +658,15 @@ namespace sqlite_orm { } void delete_function_impl(const std::string& name, - std::vector>& functionsVector) const { - auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& udfProxy) { + std::vector>& functions) const { +#if __cpp_lib_ranges >= 201911L + auto it = std::ranges::find(functions, name, &udf_proxy_base::name); +#else + auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { return udfProxy->name == name; }); - if(it != functionsVector.end()) { - functionsVector.erase(it); - it = functionsVector.end(); - +#endif + if(it != functions.end()) { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); int rc = sqlite3_create_function_v2(db, @@ -679,6 +682,7 @@ namespace sqlite_orm { throw_translated_sqlite_error(db); } } + it = functions.erase(it); } else { throw std::system_error{orm_error_code::function_not_found}; } @@ -796,13 +800,17 @@ namespace sqlite_orm { ++storageColumnInfoIndex) { // get storage's column info - auto& storageColumnInfo = storageTableInfo[storageColumnInfoIndex]; - auto& columnName = storageColumnInfo.name; + table_xinfo& storageColumnInfo = storageTableInfo[storageColumnInfoIndex]; + const std::string& columnName = storageColumnInfo.name; - // search for a column in db eith the same name + // search for a column in db with the same name +#if __cpp_lib_ranges >= 201911L + auto dbColumnInfoIt = std::ranges::find(dbTableInfo, columnName, &table_xinfo::name); +#else auto dbColumnInfoIt = std::find_if(dbTableInfo.begin(), dbTableInfo.end(), [&columnName](auto& ti) { return ti.name == columnName; }); +#endif if(dbColumnInfoIt != dbTableInfo.end()) { auto& dbColumnInfo = *dbColumnInfoIt; auto columnsAreEqual = diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 7bc16e57e..cf96219f8 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -13682,7 +13682,7 @@ namespace sqlite_orm { #include // std::make_unique, std::unique_ptr #include // std::map #include // std::is_same -#include // std::find_if +#include // std::find_if, std::ranges::find // #include "functional/cxx_universal.h" // ::size_t @@ -15232,12 +15232,10 @@ namespace sqlite_orm { } void create_collation(const std::string& name, collating_function f) { - collating_function* function = nullptr; const auto functionExists = bool(f); + collating_function* function = nullptr; if(functionExists) { function = &(collatingFunctions[name] = std::move(f)); - } else { - collatingFunctions.erase(name); } // create collations if db is open @@ -15252,6 +15250,10 @@ namespace sqlite_orm { throw_translated_sqlite_error(db); } } + + if(!functionExists) { + collatingFunctions.erase(name); + } } template @@ -15478,14 +15480,15 @@ namespace sqlite_orm { } void delete_function_impl(const std::string& name, - std::vector>& functionsVector) const { - auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& udfProxy) { + std::vector>& functions) const { +#if __cpp_lib_ranges >= 201911L + auto it = std::ranges::find(functions, name, &udf_proxy_base::name); +#else + auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { return udfProxy->name == name; }); - if(it != functionsVector.end()) { - functionsVector.erase(it); - it = functionsVector.end(); - +#endif + if(it != functions.end()) { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); int rc = sqlite3_create_function_v2(db, @@ -15501,6 +15504,7 @@ namespace sqlite_orm { throw_translated_sqlite_error(db); } } + it = functions.erase(it); } else { throw std::system_error{orm_error_code::function_not_found}; } @@ -15618,13 +15622,17 @@ namespace sqlite_orm { ++storageColumnInfoIndex) { // get storage's column info - auto& storageColumnInfo = storageTableInfo[storageColumnInfoIndex]; - auto& columnName = storageColumnInfo.name; + table_xinfo& storageColumnInfo = storageTableInfo[storageColumnInfoIndex]; + const std::string& columnName = storageColumnInfo.name; - // search for a column in db eith the same name + // search for a column in db with the same name +#if __cpp_lib_ranges >= 201911L + auto dbColumnInfoIt = std::ranges::find(dbTableInfo, columnName, &table_xinfo::name); +#else auto dbColumnInfoIt = std::find_if(dbTableInfo.begin(), dbTableInfo.end(), [&columnName](auto& ti) { return ti.name == columnName; }); +#endif if(dbColumnInfoIt != dbTableInfo.end()) { auto& dbColumnInfo = *dbColumnInfoIt; auto columnsAreEqual = From 6f2f7d15931dd935a99ed8db5fea8de8fa5deeea Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 19:37:04 +0200 Subject: [PATCH 07/50] Fixed a memory leak that occurs when deleting user-defined functions Either the destructor of `udf_proxy_base` (formerly `user_defined_function_base` must be virtual, or unique pointers need to carry type-erased deleter --- dev/function.h | 14 ++++----- dev/storage_base.h | 40 ++++++++++++------------ include/sqlite_orm/sqlite_orm.h | 54 ++++++++++++++++----------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/dev/function.h b/dev/function.h index e46581594..868b31dfc 100644 --- a/dev/function.h +++ b/dev/function.h @@ -23,23 +23,23 @@ namespace sqlite_orm { namespace internal { struct udf_proxy_base { - using func_call = std::function< - void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>; - using final_call = std::function; + using func_call = + std::function; + using final_call = std::function; std::string name; int argumentsCount = 0; - std::function create; - void (*destroy)(int*) = nullptr; + std::function create; + xdestroy_fn_t destroy = nullptr; -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED udf_proxy_base(decltype(name) name_, decltype(argumentsCount) argumentsCount_, decltype(create) create_, decltype(destroy) destroy_) : name(std::move(name_)), argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} -#endif + + virtual ~udf_proxy_base() = default; }; struct scalar_udf_proxy : udf_proxy_base { diff --git a/dev/storage_base.h b/dev/storage_base.h index 46851cd4c..79c72eeee 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -264,8 +264,9 @@ namespace sqlite_orm { this->scalarFunctions.push_back(std::make_unique( std::move(name), argsCount, - []() -> int* { - return (int*)(new F()); + /* create = */ + []() -> void* { + return new F(); }, /* call = */ [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { @@ -331,8 +332,8 @@ namespace sqlite_orm { std::move(name), argsCount, /* create = */ - []() -> int* { - return (int*)(new F()); + []() -> void* { + return new F(); }, /* step = */ [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { @@ -671,7 +672,7 @@ namespace sqlite_orm { sqlite3* db = this->connection->get(); int rc = sqlite3_create_function_v2(db, name.c_str(), - 0, + (*it)->argumentsCount, SQLITE_UTF8, nullptr, nullptr, @@ -719,26 +720,26 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntHandle = static_cast(aggregateContextHandle); - if(*aggregateContextIntHandle == nullptr) { - *aggregateContextIntHandle = udfProxy->create(); + auto* udfProxy = static_cast(sqlite3_user_data(context)); + void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); + void* udfHandle = *static_cast(aggregateStateMem); + if(udfHandle == nullptr) { + udfHandle = udfProxy->create(); } - udfProxy->step(context, *aggregateContextIntHandle, argsCount, values); + udfProxy->step(context, udfHandle, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { - auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntHandle = static_cast(aggregateContextHandle); - udfProxy->finalCall(context, *aggregateContextIntHandle); - udfProxy->destroy(*aggregateContextIntHandle); + auto* udfProxy = static_cast(sqlite3_user_data(context)); + void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); + void* udfHandle = *static_cast(aggregateStateMem); + udfProxy->finalCall(context, udfHandle); + udfProxy->destroy(udfHandle); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto udfProxy = static_cast(sqlite3_user_data(context)); - const std::unique_ptr callableGuard(udfProxy->create(), udfProxy->destroy); + const std::unique_ptr udfHandleGuard(udfProxy->create(), udfProxy->destroy); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } @@ -746,9 +747,8 @@ namespace sqlite_orm { } template - static void delete_function_callback(int* pointer) { - auto voidPointer = static_cast(pointer); - auto fPointer = static_cast(voidPointer); + static void delete_function_callback(void* pointer) { + auto fPointer = static_cast(pointer); delete fPointer; } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index cf96219f8..729c9225f 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10907,23 +10907,23 @@ namespace sqlite_orm { namespace internal { struct udf_proxy_base { - using func_call = std::function< - void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>; - using final_call = std::function; + using func_call = + std::function; + using final_call = std::function; std::string name; int argumentsCount = 0; - std::function create; - void (*destroy)(int*) = nullptr; + std::function create; + xdestroy_fn_t destroy = nullptr; -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED udf_proxy_base(decltype(name) name_, decltype(argumentsCount) argumentsCount_, decltype(create) create_, decltype(destroy) destroy_) : name(std::move(name_)), argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} -#endif + + virtual ~udf_proxy_base() = default; }; struct scalar_udf_proxy : udf_proxy_base { @@ -15086,8 +15086,9 @@ namespace sqlite_orm { this->scalarFunctions.push_back(std::make_unique( std::move(name), argsCount, - []() -> int* { - return (int*)(new F()); + /* create = */ + []() -> void* { + return new F(); }, /* call = */ [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { @@ -15153,8 +15154,8 @@ namespace sqlite_orm { std::move(name), argsCount, /* create = */ - []() -> int* { - return (int*)(new F()); + []() -> void* { + return new F(); }, /* step = */ [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { @@ -15493,7 +15494,7 @@ namespace sqlite_orm { sqlite3* db = this->connection->get(); int rc = sqlite3_create_function_v2(db, name.c_str(), - 0, + (*it)->argumentsCount, SQLITE_UTF8, nullptr, nullptr, @@ -15541,26 +15542,26 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntHandle = static_cast(aggregateContextHandle); - if(*aggregateContextIntHandle == nullptr) { - *aggregateContextIntHandle = udfProxy->create(); + auto* udfProxy = static_cast(sqlite3_user_data(context)); + void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); + void* udfHandle = *static_cast(aggregateStateMem); + if(udfHandle == nullptr) { + udfHandle = udfProxy->create(); } - udfProxy->step(context, *aggregateContextIntHandle, argsCount, values); + udfProxy->step(context, udfHandle, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { - auto udfProxy = static_cast(sqlite3_user_data(context)); - auto aggregateContextHandle = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntHandle = static_cast(aggregateContextHandle); - udfProxy->finalCall(context, *aggregateContextIntHandle); - udfProxy->destroy(*aggregateContextIntHandle); + auto* udfProxy = static_cast(sqlite3_user_data(context)); + void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); + void* udfHandle = *static_cast(aggregateStateMem); + udfProxy->finalCall(context, udfHandle); + udfProxy->destroy(udfHandle); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto udfProxy = static_cast(sqlite3_user_data(context)); - const std::unique_ptr callableGuard(udfProxy->create(), udfProxy->destroy); + const std::unique_ptr udfHandleGuard(udfProxy->create(), udfProxy->destroy); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } @@ -15568,9 +15569,8 @@ namespace sqlite_orm { } template - static void delete_function_callback(int* pointer) { - auto voidPointer = static_cast(pointer); - auto fPointer = static_cast(voidPointer); + static void delete_function_callback(void* pointer) { + auto fPointer = static_cast(pointer); delete fPointer; } From 3eecc45c88bfd05f0c4d20c56417b4069e2e505d Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 19:50:13 +0200 Subject: [PATCH 08/50] Improved compilation times under C++14 The statement value binder for tuples and tuple creator from result values now benefit from a parameter pack expansion trick --- dev/statement_binder.h | 11 ++++------- dev/values_to_tuple.h | 10 ++++------ include/sqlite_orm/sqlite_orm.h | 21 ++++++++------------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/dev/statement_binder.h b/dev/statement_binder.h index dd6e556dc..4f741ae2f 100644 --- a/dev/statement_binder.h +++ b/dev/statement_binder.h @@ -332,14 +332,11 @@ namespace sqlite_orm { (this->bind(polyfill::invoke(project, std::get(tpl)), Idx), ...); } #else - template - void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { - this->bind(polyfill::invoke(project, std::get(tpl)), I); - (*this)(tpl, std::index_sequence{}, std::forward(project)); + template + void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(this->bind(polyfill::invoke(project, std::get(tpl)), Idx), 0)...}; } - - template - void operator()(const Tpl&, std::index_sequence<>, Projection) const {} #endif template diff --git a/dev/values_to_tuple.h b/dev/values_to_tuple.h index f5a477f6f..d708e0284 100644 --- a/dev/values_to_tuple.h +++ b/dev/values_to_tuple.h @@ -29,13 +29,11 @@ namespace sqlite_orm { (this->extract(values[Idx], std::get(tuple)), ...); } #else - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - this->extract(values[I], std::get(tuple)); - (*this)(values, tuple, std::index_sequence{}); - } template - void operator()(sqlite3_value** /*values*/, Tpl&, std::index_sequence) const {} + void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(this->extract(values[Idx], std::get(tuple)), 0)...}; + } #endif template void extract(sqlite3_value* value, T& t) const { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 729c9225f..2b42c527d 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -8860,14 +8860,11 @@ namespace sqlite_orm { (this->bind(polyfill::invoke(project, std::get(tpl)), Idx), ...); } #else - template - void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { - this->bind(polyfill::invoke(project, std::get(tpl)), I); - (*this)(tpl, std::index_sequence{}, std::forward(project)); + template + void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(this->bind(polyfill::invoke(project, std::get(tpl)), Idx), 0)...}; } - - template - void operator()(const Tpl&, std::index_sequence<>, Projection) const {} #endif template @@ -14825,13 +14822,11 @@ namespace sqlite_orm { (this->extract(values[Idx], std::get(tuple)), ...); } #else - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - this->extract(values[I], std::get(tuple)); - (*this)(values, tuple, std::index_sequence{}); - } template - void operator()(sqlite3_value** /*values*/, Tpl&, std::index_sequence) const {} + void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(this->extract(values[Idx], std::get(tuple)), 0)...}; + } #endif template void extract(sqlite3_value* value, T& t) const { From cb4bcacb697cf3fc39df02cf22c980189cc7bfc0 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 20:00:19 +0200 Subject: [PATCH 09/50] Fix possible crash in final aggregation callback --- dev/storage_base.h | 6 ++++++ include/sqlite_orm/sqlite_orm.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/dev/storage_base.h b/dev/storage_base.h index 79c72eeee..64fed38d3 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -721,6 +721,7 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto* udfProxy = static_cast(sqlite3_user_data(context)); + // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); void* udfHandle = *static_cast(aggregateStateMem); if(udfHandle == nullptr) { @@ -731,8 +732,13 @@ namespace sqlite_orm { static void aggregate_function_final_callback(sqlite3_context* context) { auto* udfProxy = static_cast(sqlite3_user_data(context)); + // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); void* udfHandle = *static_cast(aggregateStateMem); + // note: it is possible that the 'step' function was never called + if(udfHandle == nullptr) { + udfHandle = udfProxy->create(); + } udfProxy->finalCall(context, udfHandle); udfProxy->destroy(udfHandle); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 2b42c527d..e3a27ed81 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15538,6 +15538,7 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto* udfProxy = static_cast(sqlite3_user_data(context)); + // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); void* udfHandle = *static_cast(aggregateStateMem); if(udfHandle == nullptr) { @@ -15548,8 +15549,13 @@ namespace sqlite_orm { static void aggregate_function_final_callback(sqlite3_context* context) { auto* udfProxy = static_cast(sqlite3_user_data(context)); + // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); void* udfHandle = *static_cast(aggregateStateMem); + // note: it is possible that the 'step' function was never called + if(udfHandle == nullptr) { + udfHandle = udfProxy->create(); + } udfProxy->finalCall(context, udfHandle); udfProxy->destroy(udfHandle); } From 60fff04126e1cf162d29c4edbbafae611e6cfe3b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 14 Nov 2023 21:10:58 +0200 Subject: [PATCH 10/50] Updated a few things concerning user-defined function creation --- dev/storage_base.h | 37 ++++++++++++++++++--------------- include/sqlite_orm/sqlite_orm.h | 37 ++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 64fed38d3..9c45bc53e 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -251,7 +251,7 @@ namespace sqlite_orm { */ template void create_scalar_function() { - static_assert(is_scalar_udf_v, "F cannot be an aggregate function"); + static_assert(is_scalar_udf_v, "F must be a scalar function"); std::stringstream ss; ss << F::name() << std::flush; @@ -270,10 +270,10 @@ namespace sqlite_orm { }, /* call = */ [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfHandle); + F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); - auto result = call(function, std::move(argsTuple)); + auto result = call(udf, std::move(argsTuple)); statement_binder().result(context, result); }, delete_function_callback)); @@ -318,7 +318,7 @@ namespace sqlite_orm { */ template void create_aggregate_function() { - static_assert(is_aggregate_udf_v, "F cannot be a scalar function"); + static_assert(is_aggregate_udf_v, "F must be an aggregate function"); std::stringstream ss; ss << F::name() << std::flush; @@ -337,15 +337,15 @@ namespace sqlite_orm { }, /* step = */ [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfHandle); + F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); - call(function, &F::step, std::move(argsTuple)); + call(udf, &F::step, std::move(argsTuple)); }, /* finalCall = */ [](sqlite3_context* context, void* udfHandle) { - F& function = *static_cast(udfHandle); - auto result = function.fin(); + F& udf = *static_cast(udfHandle); + auto result = udf.fin(); statement_binder().result(context, result); }, delete_function_callback)); @@ -689,7 +689,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { + static void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { int rc = sqlite3_create_function_v2(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -704,7 +704,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { + static void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { int rc = sqlite3_create_function(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -723,8 +723,11 @@ namespace sqlite_orm { auto* udfProxy = static_cast(sqlite3_user_data(context)); // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void* udfHandle = *static_cast(aggregateStateMem); + void*& udfHandle = *static_cast(aggregateStateMem); if(udfHandle == nullptr) { + if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } udfHandle = udfProxy->create(); } udfProxy->step(context, udfHandle, argsCount, values); @@ -734,7 +737,7 @@ namespace sqlite_orm { auto* udfProxy = static_cast(sqlite3_user_data(context)); // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void* udfHandle = *static_cast(aggregateStateMem); + void*& udfHandle = *static_cast(aggregateStateMem); // note: it is possible that the 'step' function was never called if(udfHandle == nullptr) { udfHandle = udfProxy->create(); @@ -745,17 +748,17 @@ namespace sqlite_orm { static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto udfProxy = static_cast(sqlite3_user_data(context)); - const std::unique_ptr udfHandleGuard(udfProxy->create(), udfProxy->destroy); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - udfProxy->run(context, udfProxy, argsCount, values); + const std::unique_ptr udfHandle(udfProxy->create(), udfProxy->destroy); + udfProxy->run(context, udfHandle.get(), argsCount, values); } template - static void delete_function_callback(void* pointer) { - auto fPointer = static_cast(pointer); - delete fPointer; + static void delete_function_callback(void* udfHandle) { + F* udf = static_cast(udfHandle); + delete udf; } std::string current_time(sqlite3* db) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index e3a27ed81..f73528b6d 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15068,7 +15068,7 @@ namespace sqlite_orm { */ template void create_scalar_function() { - static_assert(is_scalar_udf_v, "F cannot be an aggregate function"); + static_assert(is_scalar_udf_v, "F must be a scalar function"); std::stringstream ss; ss << F::name() << std::flush; @@ -15087,10 +15087,10 @@ namespace sqlite_orm { }, /* call = */ [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfHandle); + F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); - auto result = call(function, std::move(argsTuple)); + auto result = call(udf, std::move(argsTuple)); statement_binder().result(context, result); }, delete_function_callback)); @@ -15135,7 +15135,7 @@ namespace sqlite_orm { */ template void create_aggregate_function() { - static_assert(is_aggregate_udf_v, "F cannot be a scalar function"); + static_assert(is_aggregate_udf_v, "F must be an aggregate function"); std::stringstream ss; ss << F::name() << std::flush; @@ -15154,15 +15154,15 @@ namespace sqlite_orm { }, /* step = */ [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { - F& function = *static_cast(udfHandle); + F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); - call(function, &F::step, std::move(argsTuple)); + call(udf, &F::step, std::move(argsTuple)); }, /* finalCall = */ [](sqlite3_context* context, void* udfHandle) { - F& function = *static_cast(udfHandle); - auto result = function.fin(); + F& udf = *static_cast(udfHandle); + auto result = udf.fin(); statement_binder().result(context, result); }, delete_function_callback)); @@ -15506,7 +15506,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { + static void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { int rc = sqlite3_create_function_v2(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -15521,7 +15521,7 @@ namespace sqlite_orm { } } - void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { + static void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { int rc = sqlite3_create_function(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -15540,8 +15540,11 @@ namespace sqlite_orm { auto* udfProxy = static_cast(sqlite3_user_data(context)); // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void* udfHandle = *static_cast(aggregateStateMem); + void*& udfHandle = *static_cast(aggregateStateMem); if(udfHandle == nullptr) { + if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } udfHandle = udfProxy->create(); } udfProxy->step(context, udfHandle, argsCount, values); @@ -15551,7 +15554,7 @@ namespace sqlite_orm { auto* udfProxy = static_cast(sqlite3_user_data(context)); // allocate or fetch pointer handle to user-defined function void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void* udfHandle = *static_cast(aggregateStateMem); + void*& udfHandle = *static_cast(aggregateStateMem); // note: it is possible that the 'step' function was never called if(udfHandle == nullptr) { udfHandle = udfProxy->create(); @@ -15562,17 +15565,17 @@ namespace sqlite_orm { static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { auto udfProxy = static_cast(sqlite3_user_data(context)); - const std::unique_ptr udfHandleGuard(udfProxy->create(), udfProxy->destroy); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - udfProxy->run(context, udfProxy, argsCount, values); + const std::unique_ptr udfHandle(udfProxy->create(), udfProxy->destroy); + udfProxy->run(context, udfHandle.get(), argsCount, values); } template - static void delete_function_callback(void* pointer) { - auto fPointer = static_cast(pointer); - delete fPointer; + static void delete_function_callback(void* udfHandle) { + F* udf = static_cast(udfHandle); + delete udf; } std::string current_time(sqlite3* db) { From cc85bc401d238f4969c7414124ead85dc99ddc96 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 15 Nov 2023 00:25:58 +0200 Subject: [PATCH 11/50] Merged the data elements of "udf proxies" into the base class --- dev/function.h | 59 +++++----- dev/storage_base.h | 33 +++--- dev/xdestroy_handling.h | 24 ++-- include/sqlite_orm/sqlite_orm.h | 117 +++++++++---------- tests/static_tests/function_static_tests.cpp | 4 + 5 files changed, 113 insertions(+), 124 deletions(-) diff --git a/dev/function.h b/dev/function.h index 868b31dfc..67a593214 100644 --- a/dev/function.h +++ b/dev/function.h @@ -23,49 +23,44 @@ namespace sqlite_orm { namespace internal { struct udf_proxy_base { - using func_call = - std::function; - using final_call = std::function; + using func_call_fn_t = void (*)(void* udfHandle, + sqlite3_context* context, + int argsCount, + sqlite3_value** values); + using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); std::string name; int argumentsCount = 0; std::function create; xdestroy_fn_t destroy = nullptr; - - udf_proxy_base(decltype(name) name_, - decltype(argumentsCount) argumentsCount_, - decltype(create) create_, - decltype(destroy) destroy_) : - name(std::move(name_)), - argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} - - virtual ~udf_proxy_base() = default; + func_call_fn_t func = nullptr; + final_call_fn_t finalAggregateCall = nullptr; + + udf_proxy_base(std::string name, + int argumentsCount, + std::function create, + xdestroy_fn_t destroy, + func_call_fn_t run) : + name(std::move(name)), + argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(run) {} + + udf_proxy_base(std::string name, + int argumentsCount, + std::function create, + xdestroy_fn_t destroy, + func_call_fn_t step, + final_call_fn_t finalCall) : + name(std::move(name)), + argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(step), + finalAggregateCall(finalCall) {} }; struct scalar_udf_proxy : udf_proxy_base { - func_call run; - - scalar_udf_proxy(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(run) run_, - decltype(destroy) destroy_) : - udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - run(std::move(run_)) {} + using udf_proxy_base::udf_proxy_base; }; struct aggregate_udf_proxy : udf_proxy_base { - func_call step; - final_call finalCall; - - aggregate_udf_proxy(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(step) step_, - decltype(finalCall) finalCall_, - decltype(destroy) destroy_) : - udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - step(std::move(step_)), finalCall(std::move(finalCall_)) {} + using udf_proxy_base::udf_proxy_base; }; template diff --git a/dev/storage_base.h b/dev/storage_base.h index 9c45bc53e..9f935cefc 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -24,6 +24,7 @@ #include "values_to_tuple.h" #include "arg_values.h" #include "util.h" +#include "xdestroy_handling.h" #include "serializing_util.h" namespace sqlite_orm { @@ -268,15 +269,16 @@ namespace sqlite_orm { []() -> void* { return new F(); }, + /* destroy = */ + obtain_xdestroy_for(std::default_delete{}), /* call = */ - [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { + [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); auto result = call(udf, std::move(argsTuple)); statement_binder().result(context, result); - }, - delete_function_callback)); + })); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); @@ -335,20 +337,21 @@ namespace sqlite_orm { []() -> void* { return new F(); }, + /* destroy = */ + obtain_xdestroy_for(std::default_delete{}), /* step = */ - [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { + [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); call(udf, &F::step, std::move(argsTuple)); }, /* finalCall = */ - [](sqlite3_context* context, void* udfHandle) { + [](void* udfHandle, sqlite3_context* context) { F& udf = *static_cast(udfHandle); auto result = udf.fin(); statement_binder().result(context, result); - }, - delete_function_callback)); + })); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); @@ -730,7 +733,7 @@ namespace sqlite_orm { } udfHandle = udfProxy->create(); } - udfProxy->step(context, udfHandle, argsCount, values); + udfProxy->func(udfHandle, context, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { @@ -742,23 +745,17 @@ namespace sqlite_orm { if(udfHandle == nullptr) { udfHandle = udfProxy->create(); } - udfProxy->finalCall(context, udfHandle); + udfProxy->finalAggregateCall(udfHandle, context); udfProxy->destroy(udfHandle); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto udfProxy = static_cast(sqlite3_user_data(context)); + auto* udfProxy = static_cast(sqlite3_user_data(context)); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - const std::unique_ptr udfHandle(udfProxy->create(), udfProxy->destroy); - udfProxy->run(context, udfHandle.get(), argsCount, values); - } - - template - static void delete_function_callback(void* udfHandle) { - F* udf = static_cast(udfHandle); - delete udf; + const std::unique_ptr udfHandle{udfProxy->create(), udfProxy->destroy}; + udfProxy->func(udfHandle.get(), context, argsCount, values); } std::string current_time(sqlite3* db) { diff --git a/dev/xdestroy_handling.h b/dev/xdestroy_handling.h index b545e0c81..0b4b40afd 100644 --- a/dev/xdestroy_handling.h +++ b/dev/xdestroy_handling.h @@ -181,8 +181,8 @@ namespace sqlite_orm { * * Explicitly declared for better error messages. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept requires(internal::is_unusable_for_xdestroy) { static_assert(polyfill::always_false_v, @@ -202,8 +202,8 @@ namespace sqlite_orm { * Type-safety is garanteed by checking whether the deleter or yielded function pointer * is invocable with the non-q-qualified pointer value. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept requires(internal::needs_xdestroy_proxy) { return internal::xdestroy_proxy; @@ -223,27 +223,27 @@ namespace sqlite_orm { * Type-safety is garanteed by checking whether the deleter or yielded function pointer * is invocable with the non-q-qualified pointer value. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P* = nullptr) noexcept requires(internal::yields_xdestroy) { return d; } #else - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) { static_assert(polyfill::always_false_v, "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); return nullptr; } - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept { return internal::xdestroy_proxy; } - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P*) noexcept { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P* = nullptr) noexcept { return d; } #endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index f73528b6d..a1ae6f586 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -8285,8 +8285,8 @@ namespace sqlite_orm { * * Explicitly declared for better error messages. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept requires(internal::is_unusable_for_xdestroy) { static_assert(polyfill::always_false_v, @@ -8306,8 +8306,8 @@ namespace sqlite_orm { * Type-safety is garanteed by checking whether the deleter or yielded function pointer * is invocable with the non-q-qualified pointer value. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept requires(internal::needs_xdestroy_proxy) { return internal::xdestroy_proxy; @@ -8327,27 +8327,27 @@ namespace sqlite_orm { * Type-safety is garanteed by checking whether the deleter or yielded function pointer * is invocable with the non-q-qualified pointer value. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P* = nullptr) noexcept requires(internal::yields_xdestroy) { return d; } #else - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) { static_assert(polyfill::always_false_v, "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); return nullptr; } - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept { return internal::xdestroy_proxy; } - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P*) noexcept { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P* = nullptr) noexcept { return d; } #endif @@ -10904,49 +10904,44 @@ namespace sqlite_orm { namespace internal { struct udf_proxy_base { - using func_call = - std::function; - using final_call = std::function; + using func_call_fn_t = void (*)(void* udfHandle, + sqlite3_context* context, + int argsCount, + sqlite3_value** values); + using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); std::string name; int argumentsCount = 0; std::function create; xdestroy_fn_t destroy = nullptr; - - udf_proxy_base(decltype(name) name_, - decltype(argumentsCount) argumentsCount_, - decltype(create) create_, - decltype(destroy) destroy_) : - name(std::move(name_)), - argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} - - virtual ~udf_proxy_base() = default; + func_call_fn_t func = nullptr; + final_call_fn_t finalAggregateCall = nullptr; + + udf_proxy_base(std::string name, + int argumentsCount, + std::function create, + xdestroy_fn_t destroy, + func_call_fn_t run) : + name(std::move(name)), + argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(run) {} + + udf_proxy_base(std::string name, + int argumentsCount, + std::function create, + xdestroy_fn_t destroy, + func_call_fn_t step, + final_call_fn_t finalCall) : + name(std::move(name)), + argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(step), + finalAggregateCall(finalCall) {} }; struct scalar_udf_proxy : udf_proxy_base { - func_call run; - - scalar_udf_proxy(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(run) run_, - decltype(destroy) destroy_) : - udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - run(std::move(run_)) {} + using udf_proxy_base::udf_proxy_base; }; struct aggregate_udf_proxy : udf_proxy_base { - func_call step; - final_call finalCall; - - aggregate_udf_proxy(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(step) step_, - decltype(finalCall) finalCall_, - decltype(destroy) destroy_) : - udf_proxy_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - step(std::move(step_)), finalCall(std::move(finalCall_)) {} + using udf_proxy_base::udf_proxy_base; }; template @@ -14841,6 +14836,8 @@ namespace sqlite_orm { // #include "util.h" +// #include "xdestroy_handling.h" + // #include "serializing_util.h" namespace sqlite_orm { @@ -15085,15 +15082,16 @@ namespace sqlite_orm { []() -> void* { return new F(); }, + /* destroy = */ + obtain_xdestroy_for(std::default_delete{}), /* call = */ - [](sqlite3_context* context, void* udfHandle, int argsCount, sqlite3_value** values) { + [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); auto result = call(udf, std::move(argsTuple)); statement_binder().result(context, result); - }, - delete_function_callback)); + })); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); @@ -15152,20 +15150,21 @@ namespace sqlite_orm { []() -> void* { return new F(); }, + /* destroy = */ + obtain_xdestroy_for(std::default_delete{}), /* step = */ - [](sqlite3_context*, void* udfHandle, int argsCount, sqlite3_value** values) { + [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); args_tuple argsTuple; values_to_tuple{}(values, argsTuple, argsCount); call(udf, &F::step, std::move(argsTuple)); }, /* finalCall = */ - [](sqlite3_context* context, void* udfHandle) { + [](void* udfHandle, sqlite3_context* context) { F& udf = *static_cast(udfHandle); auto result = udf.fin(); statement_binder().result(context, result); - }, - delete_function_callback)); + })); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); @@ -15547,7 +15546,7 @@ namespace sqlite_orm { } udfHandle = udfProxy->create(); } - udfProxy->step(context, udfHandle, argsCount, values); + udfProxy->func(udfHandle, context, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { @@ -15559,23 +15558,17 @@ namespace sqlite_orm { if(udfHandle == nullptr) { udfHandle = udfProxy->create(); } - udfProxy->finalCall(context, udfHandle); + udfProxy->finalAggregateCall(udfHandle, context); udfProxy->destroy(udfHandle); } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto udfProxy = static_cast(sqlite3_user_data(context)); + auto* udfProxy = static_cast(sqlite3_user_data(context)); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - const std::unique_ptr udfHandle(udfProxy->create(), udfProxy->destroy); - udfProxy->run(context, udfHandle.get(), argsCount, values); - } - - template - static void delete_function_callback(void* udfHandle) { - F* udf = static_cast(udfHandle); - delete udf; + const std::unique_ptr udfHandle{udfProxy->create(), udfProxy->destroy}; + udfProxy->func(udfHandle.get(), context, argsCount, values); } std::string current_time(sqlite3* db) { diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index f29092cb4..7cd8e0122 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -268,5 +268,9 @@ TEST_CASE("function static") { STATIC_REQUIRE(storage_aggregate_callable); STATIC_REQUIRE_FALSE(storage_scalar_callable); #endif + + // must be veneers (no additional data members) + STATIC_REQUIRE(sizeof(internal::scalar_udf_proxy) == sizeof(internal::udf_proxy_base)); + STATIC_REQUIRE(sizeof(internal::aggregate_udf_proxy) == sizeof(internal::udf_proxy_base)); } } From 6bd73677942ef4f62a7444192239b27b2cb23a75 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 15 Nov 2023 10:55:36 +0200 Subject: [PATCH 12/50] Allocate memory for user-defined function once --- dev/function.h | 86 ++++++++--- dev/storage_base.h | 68 ++++---- include/sqlite_orm/sqlite_orm.h | 154 +++++++++++-------- tests/static_tests/function_static_tests.cpp | 4 - 4 files changed, 188 insertions(+), 124 deletions(-) diff --git a/dev/function.h b/dev/function.h index 67a593214..e3dd044a6 100644 --- a/dev/function.h +++ b/dev/function.h @@ -22,6 +22,15 @@ namespace sqlite_orm { namespace internal { + /* + * Stores type-erased information about a user-defined scalar or aggregate function: + * - name and argument count + * - function pointers for construction/destruction + * - function dispatch + * - allocated memory and location + * + * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. + */ struct udf_proxy_base { using func_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context, @@ -29,38 +38,65 @@ namespace sqlite_orm { sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + struct destruct_only_deleter { + template + void operator()(F* f) const noexcept { + f->~F(); + } + }; + std::string name; - int argumentsCount = 0; - std::function create; - xdestroy_fn_t destroy = nullptr; - func_call_fn_t func = nullptr; - final_call_fn_t finalAggregateCall = nullptr; - - udf_proxy_base(std::string name, - int argumentsCount, - std::function create, - xdestroy_fn_t destroy, - func_call_fn_t run) : - name(std::move(name)), - argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(run) {} - - udf_proxy_base(std::string name, - int argumentsCount, - std::function create, - xdestroy_fn_t destroy, - func_call_fn_t step, - final_call_fn_t finalCall) : - name(std::move(name)), - argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(step), - finalAggregateCall(finalCall) {} + int argumentsCount; + std::function constructAt; + xdestroy_fn_t destroy; + func_call_fn_t func; + final_call_fn_t finalAggregateCall; + // flag whether the UDF has been constructed at `udfHandle`; + // necessary for aggregation operations + bool constructed; + // pointer to memory for UDF in derived proxy class + void* const udfHandle; }; + template struct scalar_udf_proxy : udf_proxy_base { - using udf_proxy_base::udf_proxy_base; + // allocated memory for user-defined function + alignas(UDF) char udfMem[sizeof(UDF)]; + + scalar_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t run) : + udf_proxy_base{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + run, + nullptr, + false, + udfMem} {} }; + template struct aggregate_udf_proxy : udf_proxy_base { - using udf_proxy_base::udf_proxy_base; + // allocated memory for user-defined function + alignas(UDF) char udfMem[sizeof(UDF)]; + + aggregate_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t step, + final_call_fn_t finalCall) : + udf_proxy_base{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + step, + finalCall, + false, + udfMem} {} }; template diff --git a/dev/storage_base.h b/dev/storage_base.h index 9f935cefc..74c0390be 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -262,15 +262,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(std::make_unique( + this->scalarFunctions.push_back(std::make_unique>( std::move(name), argsCount, - /* create = */ - []() -> void* { - return new F(); + /* constructAt = */ + [](void* place) -> void* { + return new(place) F(); }, /* destroy = */ - obtain_xdestroy_for(std::default_delete{}), + obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), /* call = */ [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -282,7 +282,7 @@ namespace sqlite_orm { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, static_cast(*this->scalarFunctions.back())); + try_to_create_scalar_function(db, *this->scalarFunctions.back()); } } @@ -330,15 +330,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(std::make_unique( + this->aggregateFunctions.push_back(std::make_unique>( std::move(name), argsCount, - /* create = */ - []() -> void* { - return new F(); + /* constructAt = */ + [](void* place) -> void* { + return new(place) F(); }, /* destroy = */ - obtain_xdestroy_for(std::default_delete{}), + obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), /* step = */ [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -355,7 +355,7 @@ namespace sqlite_orm { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, static_cast(*this->aggregateFunctions.back())); + try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); } } @@ -371,7 +371,7 @@ namespace sqlite_orm { */ template void delete_scalar_function() { - static_assert(is_scalar_udf_v, "F cannot be an aggregate 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); @@ -389,7 +389,7 @@ namespace sqlite_orm { */ template void delete_aggregate_function() { - static_assert(is_aggregate_udf_v, "F cannot be a scalar 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); @@ -649,11 +649,11 @@ namespace sqlite_orm { } for(auto& udfProxy: this->scalarFunctions) { - try_to_create_function(db, static_cast(*udfProxy)); + try_to_create_scalar_function(db, *udfProxy); } for(auto& udfProxy: this->aggregateFunctions) { - try_to_create_function(db, static_cast(*udfProxy)); + try_to_create_aggregate_function(db, *udfProxy); } if(this->on_open) { @@ -692,7 +692,7 @@ namespace sqlite_orm { } } - static void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { + static void try_to_create_scalar_function(sqlite3* db, udf_proxy_base& udfProxy) { int rc = sqlite3_create_function_v2(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -707,7 +707,7 @@ namespace sqlite_orm { } } - static void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { + static void try_to_create_aggregate_function(sqlite3* db, udf_proxy_base& udfProxy) { int rc = sqlite3_create_function(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -723,38 +723,36 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - // allocate or fetch pointer handle to user-defined function - void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void*& udfHandle = *static_cast(aggregateStateMem); - if(udfHandle == nullptr) { + auto* udfProxy = static_cast(sqlite3_user_data(context)); + if(!udfProxy->constructed) { if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - udfHandle = udfProxy->create(); + udfProxy->constructAt(udfProxy->udfHandle); + udfProxy->constructed = true; } - udfProxy->func(udfHandle, context, argsCount, values); + udfProxy->func(udfProxy->udfHandle, context, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - // allocate or fetch pointer handle to user-defined function - void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void*& udfHandle = *static_cast(aggregateStateMem); + auto* udfProxy = static_cast(sqlite3_user_data(context)); // note: it is possible that the 'step' function was never called - if(udfHandle == nullptr) { - udfHandle = udfProxy->create(); + if(!udfProxy->constructed) { + udfProxy->constructAt(udfProxy->udfHandle); + udfProxy->constructed = true; } - udfProxy->finalAggregateCall(udfHandle, context); - udfProxy->destroy(udfHandle); + udfProxy->finalAggregateCall(udfProxy->udfHandle, context); + udfProxy->destroy(udfProxy->udfHandle); + udfProxy->constructed = false; } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); + auto* udfProxy = static_cast(sqlite3_user_data(context)); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - const std::unique_ptr udfHandle{udfProxy->create(), udfProxy->destroy}; + const std::unique_ptr udfHandle{udfProxy->constructAt(udfProxy->udfHandle), + udfProxy->destroy}; udfProxy->func(udfHandle.get(), context, argsCount, values); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index a1ae6f586..f1965e9c9 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10903,6 +10903,15 @@ namespace sqlite_orm { namespace internal { + /* + * Stores type-erased information about a user-defined scalar or aggregate function: + * - name and argument count + * - function pointers for construction/destruction + * - function dispatch + * - allocated memory and location + * + * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. + */ struct udf_proxy_base { using func_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context, @@ -10910,38 +10919,65 @@ namespace sqlite_orm { sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + struct destruct_only_deleter { + template + void operator()(F* f) const noexcept { + f->~F(); + } + }; + std::string name; - int argumentsCount = 0; - std::function create; - xdestroy_fn_t destroy = nullptr; - func_call_fn_t func = nullptr; - final_call_fn_t finalAggregateCall = nullptr; - - udf_proxy_base(std::string name, - int argumentsCount, - std::function create, - xdestroy_fn_t destroy, - func_call_fn_t run) : - name(std::move(name)), - argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(run) {} - - udf_proxy_base(std::string name, - int argumentsCount, - std::function create, - xdestroy_fn_t destroy, - func_call_fn_t step, - final_call_fn_t finalCall) : - name(std::move(name)), - argumentsCount(argumentsCount), create(std::move(create)), destroy(destroy), func(step), - finalAggregateCall(finalCall) {} + int argumentsCount; + std::function constructAt; + xdestroy_fn_t destroy; + func_call_fn_t func; + final_call_fn_t finalAggregateCall; + // flag whether the UDF has been constructed at `udfHandle`; + // necessary for aggregation operations + bool constructed; + // pointer to memory for UDF in derived proxy class + void* const udfHandle; }; + template struct scalar_udf_proxy : udf_proxy_base { - using udf_proxy_base::udf_proxy_base; + // allocated memory for user-defined function + alignas(UDF) char udfMem[sizeof(UDF)]; + + scalar_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t run) : + udf_proxy_base{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + run, + nullptr, + false, + udfMem} {} }; + template struct aggregate_udf_proxy : udf_proxy_base { - using udf_proxy_base::udf_proxy_base; + // allocated memory for user-defined function + alignas(UDF) char udfMem[sizeof(UDF)]; + + aggregate_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t step, + final_call_fn_t finalCall) : + udf_proxy_base{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + step, + finalCall, + false, + udfMem} {} }; template @@ -15075,15 +15111,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(std::make_unique( + this->scalarFunctions.push_back(std::make_unique>( std::move(name), argsCount, - /* create = */ - []() -> void* { - return new F(); + /* constructAt = */ + [](void* place) -> void* { + return new(place) F(); }, /* destroy = */ - obtain_xdestroy_for(std::default_delete{}), + obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), /* call = */ [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -15095,7 +15131,7 @@ namespace sqlite_orm { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, static_cast(*this->scalarFunctions.back())); + try_to_create_scalar_function(db, *this->scalarFunctions.back()); } } @@ -15143,15 +15179,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(std::make_unique( + this->aggregateFunctions.push_back(std::make_unique>( std::move(name), argsCount, - /* create = */ - []() -> void* { - return new F(); + /* constructAt = */ + [](void* place) -> void* { + return new(place) F(); }, /* destroy = */ - obtain_xdestroy_for(std::default_delete{}), + obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), /* step = */ [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -15168,7 +15204,7 @@ namespace sqlite_orm { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, static_cast(*this->aggregateFunctions.back())); + try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); } } @@ -15184,7 +15220,7 @@ namespace sqlite_orm { */ template void delete_scalar_function() { - static_assert(is_scalar_udf_v, "F cannot be an aggregate 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); @@ -15202,7 +15238,7 @@ namespace sqlite_orm { */ template void delete_aggregate_function() { - static_assert(is_aggregate_udf_v, "F cannot be a scalar 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); @@ -15462,11 +15498,11 @@ namespace sqlite_orm { } for(auto& udfProxy: this->scalarFunctions) { - try_to_create_function(db, static_cast(*udfProxy)); + try_to_create_scalar_function(db, *udfProxy); } for(auto& udfProxy: this->aggregateFunctions) { - try_to_create_function(db, static_cast(*udfProxy)); + try_to_create_aggregate_function(db, *udfProxy); } if(this->on_open) { @@ -15505,7 +15541,7 @@ namespace sqlite_orm { } } - static void try_to_create_function(sqlite3* db, scalar_udf_proxy& udfProxy) { + static void try_to_create_scalar_function(sqlite3* db, udf_proxy_base& udfProxy) { int rc = sqlite3_create_function_v2(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -15520,7 +15556,7 @@ namespace sqlite_orm { } } - static void try_to_create_function(sqlite3* db, aggregate_udf_proxy& udfProxy) { + static void try_to_create_aggregate_function(sqlite3* db, udf_proxy_base& udfProxy) { int rc = sqlite3_create_function(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -15536,38 +15572,36 @@ namespace sqlite_orm { static void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - // allocate or fetch pointer handle to user-defined function - void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void*& udfHandle = *static_cast(aggregateStateMem); - if(udfHandle == nullptr) { + auto* udfProxy = static_cast(sqlite3_user_data(context)); + if(!udfProxy->constructed) { if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - udfHandle = udfProxy->create(); + udfProxy->constructAt(udfProxy->udfHandle); + udfProxy->constructed = true; } - udfProxy->func(udfHandle, context, argsCount, values); + udfProxy->func(udfProxy->udfHandle, context, argsCount, values); } static void aggregate_function_final_callback(sqlite3_context* context) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - // allocate or fetch pointer handle to user-defined function - void* aggregateStateMem = sqlite3_aggregate_context(context, sizeof(void**)); - void*& udfHandle = *static_cast(aggregateStateMem); + auto* udfProxy = static_cast(sqlite3_user_data(context)); // note: it is possible that the 'step' function was never called - if(udfHandle == nullptr) { - udfHandle = udfProxy->create(); + if(!udfProxy->constructed) { + udfProxy->constructAt(udfProxy->udfHandle); + udfProxy->constructed = true; } - udfProxy->finalAggregateCall(udfHandle, context); - udfProxy->destroy(udfHandle); + udfProxy->finalAggregateCall(udfProxy->udfHandle, context); + udfProxy->destroy(udfProxy->udfHandle); + udfProxy->constructed = false; } static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); + auto* udfProxy = static_cast(sqlite3_user_data(context)); if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { throw std::system_error{orm_error_code::arguments_count_does_not_match}; } - const std::unique_ptr udfHandle{udfProxy->create(), udfProxy->destroy}; + const std::unique_ptr udfHandle{udfProxy->constructAt(udfProxy->udfHandle), + udfProxy->destroy}; udfProxy->func(udfHandle.get(), context, argsCount, values); } diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index 7cd8e0122..f29092cb4 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -268,9 +268,5 @@ TEST_CASE("function static") { STATIC_REQUIRE(storage_aggregate_callable); STATIC_REQUIRE_FALSE(storage_scalar_callable); #endif - - // must be veneers (no additional data members) - STATIC_REQUIRE(sizeof(internal::scalar_udf_proxy) == sizeof(internal::udf_proxy_base)); - STATIC_REQUIRE(sizeof(internal::aggregate_udf_proxy) == sizeof(internal::udf_proxy_base)); } } From f60f2a18f523dd2b5899cc4660a8b171b287c092 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 15 Nov 2023 15:19:35 +0200 Subject: [PATCH 13/50] Replaced custom `call()` function by `std::apply` --- dev/functional/cxx_functional_polyfill.h | 2 - dev/functional/cxx_tuple_polyfill.h | 34 ++++++ dev/storage_base.h | 21 ++-- dev/tuple_helper/tuple_iteration.h | 29 +---- dev/tuple_helper/tuple_transformer.h | 3 + dev/values_to_tuple.h | 40 ++++--- include/sqlite_orm/sqlite_orm.h | 131 +++++++++++++---------- 7 files changed, 144 insertions(+), 116 deletions(-) create mode 100644 dev/functional/cxx_tuple_polyfill.h diff --git a/dev/functional/cxx_functional_polyfill.h b/dev/functional/cxx_functional_polyfill.h index e2af5077b..c8533256a 100644 --- a/dev/functional/cxx_functional_polyfill.h +++ b/dev/functional/cxx_functional_polyfill.h @@ -5,9 +5,7 @@ #endif #include // std::forward -#if __cpp_lib_invoke < 201411L #include "cxx_type_traits_polyfill.h" -#endif #include "../member_traits/member_traits.h" namespace sqlite_orm { diff --git a/dev/functional/cxx_tuple_polyfill.h b/dev/functional/cxx_tuple_polyfill.h new file mode 100644 index 000000000..e77fe2ae5 --- /dev/null +++ b/dev/functional/cxx_tuple_polyfill.h @@ -0,0 +1,34 @@ +#pragma once + +#include // std::apply; std::tuple_size +#if __cpp_lib_apply < 201603L +#include // std::forward, std::index_sequence, std::make_index_sequence +#endif + +#include "../functional/cxx_universal.h" // ::size_t +#include "../functional/cxx_functional_polyfill.h" // std::invoke + +namespace sqlite_orm { + namespace internal { + namespace polyfill { +#if __cpp_lib_apply >= 201603L + using std::apply; +#else + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl, std::index_sequence) { + return polyfill::invoke(std::forward(callable), std::get(std::forward(tpl))...); + } + + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl) { + constexpr size_t size = std::tuple_size>::value; + return apply(std::forward(callable), + std::forward(tpl), + std::make_index_sequence{}); + } +#endif + } + } + + namespace polyfill = internal::polyfill; +} diff --git a/dev/storage_base.h b/dev/storage_base.h index 74c0390be..64b4e91b6 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -1,7 +1,7 @@ #pragma once #include -#include // std::function, std::bind +#include // std::function, std::bind, std::bind_front #include // std::string #include // std::stringstream #include // std::move @@ -13,6 +13,7 @@ #include // std::find_if, std::ranges::find #include "functional/cxx_universal.h" // ::size_t +#include "functional/cxx_tuple_polyfill.h" // std::apply #include "tuple_helper/tuple_iteration.h" #include "pragma.h" #include "limit_accessor.h" @@ -274,9 +275,8 @@ namespace sqlite_orm { /* call = */ [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); - args_tuple argsTuple; - values_to_tuple{}(values, argsTuple, argsCount); - auto result = call(udf, std::move(argsTuple)); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(udf, std::move(argsTuple)); statement_binder().result(context, result); })); @@ -342,9 +342,16 @@ namespace sqlite_orm { /* step = */ [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); - args_tuple argsTuple; - values_to_tuple{}(values, argsTuple, argsCount); - call(udf, &F::step, std::move(argsTuple)); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); +#if __cpp_lib_bind_front >= 201907L + std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); +#else + polyfill::apply( + [&udf](auto&&... args) { + udf.step(std::forward(args)...); + }, + std::move(argsTuple)); +#endif }, /* finalCall = */ [](void* udfHandle, sqlite3_context* context) { diff --git a/dev/tuple_helper/tuple_iteration.h b/dev/tuple_helper/tuple_iteration.h index f519f08ff..7ce9c1a7b 100644 --- a/dev/tuple_helper/tuple_iteration.h +++ b/dev/tuple_helper/tuple_iteration.h @@ -1,38 +1,13 @@ #pragma once -#include // std::tuple, std::get, std::tuple_element, std::tuple_size -#include // std::remove_reference, std::index_sequence, std::make_index_sequence, std::forward, std::move +#include // std::get, std::tuple_element, std::tuple_size +#include // std::index_sequence, std::make_index_sequence #include // std::forward, std::move #include "../functional/cxx_universal.h" // ::size_t namespace sqlite_orm { namespace internal { - - // got it form here https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer - template - auto call(Function& f, FunctionPointer functionPointer, Tpl&& tpl, std::index_sequence) { - return (f.*functionPointer)(std::get(std::forward(tpl))...); - } - - template - auto call(Function& f, Tpl&& tpl, std::index_sequence) { - return f(std::get(std::forward(tpl))...); - } - - template - auto call(Function& f, FunctionPointer functionPointer, Tpl&& tpl) { - constexpr size_t size = std::tuple_size>::value; - return call(f, functionPointer, std::forward(tpl), std::make_index_sequence{}); - } - - // custom std::apply - template - auto call(Function& f, Tpl&& tpl) { - constexpr size_t size = std::tuple_size>::value; - return call(f, std::forward(tpl), std::make_index_sequence{}); - } - #if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) template void iterate_tuple(Tpl& tpl, std::index_sequence, L&& lambda) { diff --git a/dev/tuple_helper/tuple_transformer.h b/dev/tuple_helper/tuple_transformer.h index e091d1b8f..a4333a0d3 100644 --- a/dev/tuple_helper/tuple_transformer.h +++ b/dev/tuple_helper/tuple_transformer.h @@ -101,6 +101,9 @@ namespace sqlite_orm { return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; } + /* + * Like `std::make_from_tuple`, but using a projection on the tuple elements. + */ template constexpr R create_from_tuple(Tpl&& tpl, Projection project = {}) { return create_from_tuple( diff --git a/dev/values_to_tuple.h b/dev/values_to_tuple.h index d708e0284..c63a5611d 100644 --- a/dev/values_to_tuple.h +++ b/dev/values_to_tuple.h @@ -1,10 +1,10 @@ #pragma once #include -#include // std::index_sequence, std::make_index_sequence -#include // std::tuple, std::tuple_size, std::get +#include // std::enable_if, std::is_same, std::index_sequence, std::make_index_sequence +#include // std::tuple, std::tuple_size, std::tuple_element -#include "functional/cxx_universal.h" +#include "functional/cxx_universal.h" // ::size_t #include "row_extractor.h" #include "arg_values.h" @@ -12,33 +12,29 @@ namespace sqlite_orm { namespace internal { - struct values_to_tuple { - template - void operator()(sqlite3_value** values, Tpl& tuple, int /*argsCount*/) const { - (*this)(values, tuple, std::make_index_sequence::value>{}); + template + struct tuple_from_values { + template>>, bool> = true> + R operator()(sqlite3_value** values, int /*argsCount*/) const { + return this->create_from(values, std::make_index_sequence::value>{}); } - void operator()(sqlite3_value** values, std::tuple& tuple, int argsCount) const { - std::get<0>(tuple) = arg_values(argsCount, values); + template>::value, bool> = true> + R operator()(sqlite3_value** values, int argsCount) const { + return {arg_values(argsCount, values)}; } private: -#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - (this->extract(values[Idx], std::get(tuple)), ...); + template + Tpl create_from(sqlite3_value** values, std::index_sequence) const { + return {this->extract>(values[Idx])...}; } -#else - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - using Sink = int[sizeof...(Idx)]; - (void)Sink{(this->extract(values[Idx], std::get(tuple)), 0)...}; - } -#endif + template - void extract(sqlite3_value* value, T& t) const { + T extract(sqlite3_value* value) const { const auto rowExtractor = boxed_value_extractor(); - t = rowExtractor.extract(value); + return rowExtractor.extract(value); } }; } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index f1965e9c9..cbeacde57 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1988,10 +1988,8 @@ namespace sqlite_orm { #endif #include // std::forward -#if __cpp_lib_invoke < 201411L // #include "cxx_type_traits_polyfill.h" -#endif // #include "../member_traits/member_traits.h" #include // std::enable_if, std::is_function, std::true_type, std::false_type @@ -7068,8 +7066,8 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_iteration.h" -#include // std::tuple, std::get, std::tuple_element, std::tuple_size -#include // std::remove_reference, std::index_sequence, std::make_index_sequence, std::forward, std::move +#include // std::get, std::tuple_element, std::tuple_size +#include // std::index_sequence, std::make_index_sequence #include // std::forward, std::move // #include "../functional/cxx_universal.h" @@ -7077,31 +7075,6 @@ namespace sqlite_orm { namespace sqlite_orm { namespace internal { - - // got it form here https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer - template - auto call(Function& f, FunctionPointer functionPointer, Tpl&& tpl, std::index_sequence) { - return (f.*functionPointer)(std::get(std::forward(tpl))...); - } - - template - auto call(Function& f, Tpl&& tpl, std::index_sequence) { - return f(std::get(std::forward(tpl))...); - } - - template - auto call(Function& f, FunctionPointer functionPointer, Tpl&& tpl) { - constexpr size_t size = std::tuple_size>::value; - return call(f, functionPointer, std::forward(tpl), std::make_index_sequence{}); - } - - // custom std::apply - template - auto call(Function& f, Tpl&& tpl) { - constexpr size_t size = std::tuple_size>::value; - return call(f, std::forward(tpl), std::make_index_sequence{}); - } - #if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) template void iterate_tuple(Tpl& tpl, std::index_sequence, L&& lambda) { @@ -10024,6 +9997,9 @@ namespace sqlite_orm { return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; } + /* + * Like `std::make_from_tuple`, but using a projection on the tuple elements. + */ template constexpr R create_from_tuple(Tpl&& tpl, Projection project = {}) { return create_from_tuple( @@ -13701,7 +13677,7 @@ namespace sqlite_orm { // #include "storage_base.h" #include -#include // std::function, std::bind +#include // std::function, std::bind, std::bind_front #include // std::string #include // std::stringstream #include // std::move @@ -13714,6 +13690,43 @@ namespace sqlite_orm { // #include "functional/cxx_universal.h" // ::size_t +// #include "functional/cxx_tuple_polyfill.h" + +#include // std::apply; std::tuple_size +#if __cpp_lib_apply < 201603L +#include // std::forward, std::index_sequence, std::make_index_sequence +#endif + +// #include "../functional/cxx_universal.h" +// ::size_t +// #include "../functional/cxx_functional_polyfill.h" +// std::invoke + +namespace sqlite_orm { + namespace internal { + namespace polyfill { +#if __cpp_lib_apply >= 201603L + using std::apply; +#else + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl, std::index_sequence) { + return polyfill::invoke(std::forward(callable), std::get(std::forward(tpl))...); + } + + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl) { + constexpr size_t size = std::tuple_size>::value; + return apply(std::forward(callable), + std::forward(tpl), + std::make_index_sequence{}); + } +#endif + } + } + + namespace polyfill = internal::polyfill; +} +// std::apply // #include "tuple_helper/tuple_iteration.h" // #include "pragma.h" @@ -14676,11 +14689,11 @@ namespace sqlite_orm { // #include "values_to_tuple.h" #include -#include // std::index_sequence, std::make_index_sequence -#include // std::tuple, std::tuple_size, std::get +#include // std::enable_if, std::is_same, std::index_sequence, std::make_index_sequence +#include // std::tuple, std::tuple_size, std::tuple_element // #include "functional/cxx_universal.h" - +// ::size_t // #include "row_extractor.h" // #include "arg_values.h" @@ -14836,33 +14849,29 @@ namespace sqlite_orm { namespace internal { - struct values_to_tuple { - template - void operator()(sqlite3_value** values, Tpl& tuple, int /*argsCount*/) const { - (*this)(values, tuple, std::make_index_sequence::value>{}); + template + struct tuple_from_values { + template>>, bool> = true> + R operator()(sqlite3_value** values, int /*argsCount*/) const { + return this->create_from(values, std::make_index_sequence::value>{}); } - void operator()(sqlite3_value** values, std::tuple& tuple, int argsCount) const { - std::get<0>(tuple) = arg_values(argsCount, values); + template>::value, bool> = true> + R operator()(sqlite3_value** values, int argsCount) const { + return {arg_values(argsCount, values)}; } private: -#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - (this->extract(values[Idx], std::get(tuple)), ...); - } -#else - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - using Sink = int[sizeof...(Idx)]; - (void)Sink{(this->extract(values[Idx], std::get(tuple)), 0)...}; + template + Tpl create_from(sqlite3_value** values, std::index_sequence) const { + return {this->extract>(values[Idx])...}; } -#endif + template - void extract(sqlite3_value* value, T& t) const { + T extract(sqlite3_value* value) const { const auto rowExtractor = boxed_value_extractor(); - t = rowExtractor.extract(value); + return rowExtractor.extract(value); } }; } @@ -15123,9 +15132,8 @@ namespace sqlite_orm { /* call = */ [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); - args_tuple argsTuple; - values_to_tuple{}(values, argsTuple, argsCount); - auto result = call(udf, std::move(argsTuple)); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(udf, std::move(argsTuple)); statement_binder().result(context, result); })); @@ -15191,9 +15199,16 @@ namespace sqlite_orm { /* step = */ [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); - args_tuple argsTuple; - values_to_tuple{}(values, argsTuple, argsCount); - call(udf, &F::step, std::move(argsTuple)); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); +#if __cpp_lib_bind_front >= 201907L + std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); +#else + polyfill::apply( + [&udf](auto&&... args) { + udf.step(std::forward(args)...); + }, + std::move(argsTuple)); +#endif }, /* finalCall = */ [](void* udfHandle, sqlite3_context* context) { From 42999ac038c60679a4432d2df6063eb78a84f923 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 15 Nov 2023 20:20:10 +0200 Subject: [PATCH 14/50] Reorganized user-defined function proxy, and used a provenance fence --- dev/function.h | 81 ---------- dev/storage_base.h | 64 ++------ dev/udf_proxy.h | 124 +++++++++++++++ include/sqlite_orm/sqlite_orm.h | 269 +++++++++++++++++--------------- 4 files changed, 278 insertions(+), 260 deletions(-) create mode 100644 dev/udf_proxy.h diff --git a/dev/function.h b/dev/function.h index e3dd044a6..f8cd7efe3 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,10 +1,7 @@ #pragma once -#include #include // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type -#include // std::string #include // std::tuple, std::tuple_size, std::tuple_element -#include // std::function #include // std::min #include // std::move, std::forward @@ -21,84 +18,6 @@ namespace sqlite_orm { class pointer_binding; namespace internal { - - /* - * Stores type-erased information about a user-defined scalar or aggregate function: - * - name and argument count - * - function pointers for construction/destruction - * - function dispatch - * - allocated memory and location - * - * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. - */ - struct udf_proxy_base { - using func_call_fn_t = void (*)(void* udfHandle, - sqlite3_context* context, - int argsCount, - sqlite3_value** values); - using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); - - struct destruct_only_deleter { - template - void operator()(F* f) const noexcept { - f->~F(); - } - }; - - std::string name; - int argumentsCount; - std::function constructAt; - xdestroy_fn_t destroy; - func_call_fn_t func; - final_call_fn_t finalAggregateCall; - // flag whether the UDF has been constructed at `udfHandle`; - // necessary for aggregation operations - bool constructed; - // pointer to memory for UDF in derived proxy class - void* const udfHandle; - }; - - template - struct scalar_udf_proxy : udf_proxy_base { - // allocated memory for user-defined function - alignas(UDF) char udfMem[sizeof(UDF)]; - - scalar_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t run) : - udf_proxy_base{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - run, - nullptr, - false, - udfMem} {} - }; - - template - struct aggregate_udf_proxy : udf_proxy_base { - // allocated memory for user-defined function - alignas(UDF) char udfMem[sizeof(UDF)]; - - aggregate_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t step, - final_call_fn_t finalCall) : - udf_proxy_base{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - step, - finalCall, - false, - udfMem} {} - }; - template using scalar_call_function_t = decltype(&F::operator()); diff --git a/dev/storage_base.h b/dev/storage_base.h index 64b4e91b6..72c6f27a7 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -26,6 +26,7 @@ #include "arg_values.h" #include "util.h" #include "xdestroy_handling.h" +#include "udf_proxy.h" #include "serializing_util.h" namespace sqlite_orm { @@ -263,15 +264,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(std::make_unique>( + this->scalarFunctions.push_back(std::make_unique>( std::move(name), argsCount, /* constructAt = */ - [](void* place) -> void* { - return new(place) F(); + [](void* location) -> void* { + return new(location) F(); }, /* destroy = */ - obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -330,15 +331,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(std::make_unique>( + this->aggregateFunctions.push_back(std::make_unique>( std::move(name), argsCount, /* constructAt = */ - [](void* place) -> void* { - return new(place) F(); + [](void* location) -> void* { + return new(location) F(); }, /* destroy = */ - obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* step = */ [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -669,9 +670,9 @@ namespace sqlite_orm { } void delete_function_impl(const std::string& name, - std::vector>& functions) const { + std::vector>& functions) const { #if __cpp_lib_ranges >= 201911L - auto it = std::ranges::find(functions, name, &udf_proxy_base::name); + auto it = std::ranges::find(functions, name, &udf_proxy::name); #else auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { return udfProxy->name == name; @@ -699,7 +700,7 @@ namespace sqlite_orm { } } - static void try_to_create_scalar_function(sqlite3* db, udf_proxy_base& udfProxy) { + static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { int rc = sqlite3_create_function_v2(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -714,7 +715,7 @@ namespace sqlite_orm { } } - static void try_to_create_aggregate_function(sqlite3* db, udf_proxy_base& udfProxy) { + static void try_to_create_aggregate_function(sqlite3* db, udf_proxy& udfProxy) { int rc = sqlite3_create_function(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -728,41 +729,6 @@ namespace sqlite_orm { } } - static void - aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - if(!udfProxy->constructed) { - if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - udfProxy->constructAt(udfProxy->udfHandle); - udfProxy->constructed = true; - } - udfProxy->func(udfProxy->udfHandle, context, argsCount, values); - } - - static void aggregate_function_final_callback(sqlite3_context* context) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - // note: it is possible that the 'step' function was never called - if(!udfProxy->constructed) { - udfProxy->constructAt(udfProxy->udfHandle); - udfProxy->constructed = true; - } - udfProxy->finalAggregateCall(udfProxy->udfHandle, context); - udfProxy->destroy(udfProxy->udfHandle); - udfProxy->constructed = false; - } - - static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - const std::unique_ptr udfHandle{udfProxy->constructAt(udfProxy->udfHandle), - udfProxy->destroy}; - udfProxy->func(udfHandle.get(), context, argsCount, values); - } - std::string current_time(sqlite3* db) { std::string result; perform_exec(db, "SELECT CURRENT_TIME", extract_single_value, &result); @@ -851,8 +817,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::vector> scalarFunctions; + std::vector> aggregateFunctions; }; } } diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h new file mode 100644 index 000000000..3e108bf3f --- /dev/null +++ b/dev/udf_proxy.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include // std::launder +#include // std::string +#include // std::function +#include // std::move + +#include "error_code.h" + +namespace sqlite_orm { + namespace internal { + /* + * Stores type-erased information in relation to a user-defined scalar or aggregate function object: + * - name and argument count + * - function pointers for construction/destruction + * - function dispatch + * - allocated memory and location + * + * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. + */ + struct udf_proxy { + using func_call_fn_t = void (*)(void* udfHandle, + sqlite3_context* context, + int argsCount, + sqlite3_value** values); + using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + + struct destruct_only_deleter { + template + void operator()(F* f) const noexcept { + f->~F(); + } + }; + + std::string name; + int argumentsCount; + std::function constructAt; + xdestroy_fn_t destroy; + func_call_fn_t func; + final_call_fn_t finalAggregateCall; + // flag whether the UDF has been constructed at `udfHandle`; + // necessary for aggregation operations + bool constructed; + // pointer to memory for UDF in derived proxy veneer + void* const udfHandle; + }; + + /* + * A veneer to `udf_proxy` that provides memory space for a user-defined function object. + * + * Note: it must be a veneer, i.e. w/o any non-trivially destructible member variables. + */ + template + struct udf_proxy_veneer : udf_proxy { + // allocated memory for user-defined function + alignas(UDF) char storage[sizeof(UDF)]; + + udf_proxy_veneer(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t run) : + udf_proxy { + std::move(name), argumentsCount, std::move(constructAt), destroy, run, nullptr, false, +#if __cpp_lib_launder >= 201606L + std::launder((UDF*)this->storage) +#else + storage +#endif + } + {} + + udf_proxy_veneer(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t step, + final_call_fn_t finalCall) : + udf_proxy { + std::move(name), argumentsCount, std::move(constructAt), destroy, step, finalCall, false, +#if __cpp_lib_launder >= 201606L + std::launder((UDF*)this->storage) +#else + storage +#endif + } + {} + }; + + inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } + const std::unique_ptr udfHandle{proxy->constructAt(proxy->udfHandle), proxy->destroy}; + proxy->func(udfHandle.get(), 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)); + if(!proxy->constructed) { + if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } + proxy->constructAt(proxy->udfHandle); + proxy->constructed = true; + } + proxy->func(proxy->udfHandle, context, argsCount, values); + } + + inline void aggregate_function_final_callback(sqlite3_context* context) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + // note: it is possible that the 'step' function was never called + if(!proxy->constructed) { + proxy->constructAt(proxy->udfHandle); + proxy->constructed = true; + } + proxy->finalAggregateCall(proxy->udfHandle, context); + proxy->destroy(proxy->udfHandle); + proxy->constructed = false; + } + } +} diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index cbeacde57..6006bb768 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10856,11 +10856,8 @@ namespace sqlite_orm { // #include "function.h" -#include #include // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type -#include // std::string #include // std::tuple, std::tuple_size, std::tuple_element -#include // std::function #include // std::min #include // std::move, std::forward @@ -10878,84 +10875,6 @@ namespace sqlite_orm { class pointer_binding; namespace internal { - - /* - * Stores type-erased information about a user-defined scalar or aggregate function: - * - name and argument count - * - function pointers for construction/destruction - * - function dispatch - * - allocated memory and location - * - * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. - */ - struct udf_proxy_base { - using func_call_fn_t = void (*)(void* udfHandle, - sqlite3_context* context, - int argsCount, - sqlite3_value** values); - using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); - - struct destruct_only_deleter { - template - void operator()(F* f) const noexcept { - f->~F(); - } - }; - - std::string name; - int argumentsCount; - std::function constructAt; - xdestroy_fn_t destroy; - func_call_fn_t func; - final_call_fn_t finalAggregateCall; - // flag whether the UDF has been constructed at `udfHandle`; - // necessary for aggregation operations - bool constructed; - // pointer to memory for UDF in derived proxy class - void* const udfHandle; - }; - - template - struct scalar_udf_proxy : udf_proxy_base { - // allocated memory for user-defined function - alignas(UDF) char udfMem[sizeof(UDF)]; - - scalar_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t run) : - udf_proxy_base{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - run, - nullptr, - false, - udfMem} {} - }; - - template - struct aggregate_udf_proxy : udf_proxy_base { - // allocated memory for user-defined function - alignas(UDF) char udfMem[sizeof(UDF)]; - - aggregate_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t step, - final_call_fn_t finalCall) : - udf_proxy_base{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - step, - finalCall, - false, - udfMem} {} - }; - template using scalar_call_function_t = decltype(&F::operator()); @@ -14883,6 +14802,131 @@ namespace sqlite_orm { // #include "xdestroy_handling.h" +// #include "udf_proxy.h" + +#include +#include // std::launder +#include // std::string +#include // std::function +#include // std::move + +// #include "error_code.h" + +namespace sqlite_orm { + namespace internal { + /* + * Stores type-erased information in relation to a user-defined scalar or aggregate function object: + * - name and argument count + * - function pointers for construction/destruction + * - function dispatch + * - allocated memory and location + * + * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. + */ + struct udf_proxy { + using func_call_fn_t = void (*)(void* udfHandle, + sqlite3_context* context, + int argsCount, + sqlite3_value** values); + using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + + struct destruct_only_deleter { + template + void operator()(F* f) const noexcept { + f->~F(); + } + }; + + std::string name; + int argumentsCount; + std::function constructAt; + xdestroy_fn_t destroy; + func_call_fn_t func; + final_call_fn_t finalAggregateCall; + // flag whether the UDF has been constructed at `udfHandle`; + // necessary for aggregation operations + bool constructed; + // pointer to memory for UDF in derived proxy veneer + void* const udfHandle; + }; + + /* + * A veneer to `udf_proxy` that provides memory space for a user-defined function object. + * + * Note: it must be a veneer, i.e. w/o any non-trivially destructible member variables. + */ + template + struct udf_proxy_veneer : udf_proxy { + // allocated memory for user-defined function + alignas(UDF) char storage[sizeof(UDF)]; + + udf_proxy_veneer(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t run) : + udf_proxy { + std::move(name), argumentsCount, std::move(constructAt), destroy, run, nullptr, false, +#if __cpp_lib_launder >= 201606L + std::launder((UDF*)this->storage) +#else + storage +#endif + } + {} + + udf_proxy_veneer(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t step, + final_call_fn_t finalCall) : + udf_proxy { + std::move(name), argumentsCount, std::move(constructAt), destroy, step, finalCall, false, +#if __cpp_lib_launder >= 201606L + std::launder((UDF*)this->storage) +#else + storage +#endif + } + {} + }; + + inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } + const std::unique_ptr udfHandle{proxy->constructAt(proxy->udfHandle), proxy->destroy}; + proxy->func(udfHandle.get(), 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)); + if(!proxy->constructed) { + if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } + proxy->constructAt(proxy->udfHandle); + proxy->constructed = true; + } + proxy->func(proxy->udfHandle, context, argsCount, values); + } + + inline void aggregate_function_final_callback(sqlite3_context* context) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + // note: it is possible that the 'step' function was never called + if(!proxy->constructed) { + proxy->constructAt(proxy->udfHandle); + proxy->constructed = true; + } + proxy->finalAggregateCall(proxy->udfHandle, context); + proxy->destroy(proxy->udfHandle); + proxy->constructed = false; + } + } +} + // #include "serializing_util.h" namespace sqlite_orm { @@ -15120,15 +15164,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(std::make_unique>( + this->scalarFunctions.push_back(std::make_unique>( std::move(name), argsCount, /* constructAt = */ - [](void* place) -> void* { - return new(place) F(); + [](void* location) -> void* { + return new(location) F(); }, /* destroy = */ - obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -15187,15 +15231,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(std::make_unique>( + this->aggregateFunctions.push_back(std::make_unique>( std::move(name), argsCount, /* constructAt = */ - [](void* place) -> void* { - return new(place) F(); + [](void* location) -> void* { + return new(location) F(); }, /* destroy = */ - obtain_xdestroy_for(udf_proxy_base::destruct_only_deleter{}), + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* step = */ [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); @@ -15526,9 +15570,9 @@ namespace sqlite_orm { } void delete_function_impl(const std::string& name, - std::vector>& functions) const { + std::vector>& functions) const { #if __cpp_lib_ranges >= 201911L - auto it = std::ranges::find(functions, name, &udf_proxy_base::name); + auto it = std::ranges::find(functions, name, &udf_proxy::name); #else auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { return udfProxy->name == name; @@ -15556,7 +15600,7 @@ namespace sqlite_orm { } } - static void try_to_create_scalar_function(sqlite3* db, udf_proxy_base& udfProxy) { + static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { int rc = sqlite3_create_function_v2(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -15571,7 +15615,7 @@ namespace sqlite_orm { } } - static void try_to_create_aggregate_function(sqlite3* db, udf_proxy_base& udfProxy) { + static void try_to_create_aggregate_function(sqlite3* db, udf_proxy& udfProxy) { int rc = sqlite3_create_function(db, udfProxy.name.c_str(), udfProxy.argumentsCount, @@ -15585,41 +15629,6 @@ namespace sqlite_orm { } } - static void - aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - if(!udfProxy->constructed) { - if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - udfProxy->constructAt(udfProxy->udfHandle); - udfProxy->constructed = true; - } - udfProxy->func(udfProxy->udfHandle, context, argsCount, values); - } - - static void aggregate_function_final_callback(sqlite3_context* context) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - // note: it is possible that the 'step' function was never called - if(!udfProxy->constructed) { - udfProxy->constructAt(udfProxy->udfHandle); - udfProxy->constructed = true; - } - udfProxy->finalAggregateCall(udfProxy->udfHandle, context); - udfProxy->destroy(udfProxy->udfHandle); - udfProxy->constructed = false; - } - - static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto* udfProxy = static_cast(sqlite3_user_data(context)); - if(udfProxy->argumentsCount != -1 && udfProxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - const std::unique_ptr udfHandle{udfProxy->constructAt(udfProxy->udfHandle), - udfProxy->destroy}; - udfProxy->func(udfHandle.get(), context, argsCount, values); - } - std::string current_time(sqlite3* db) { std::string result; perform_exec(db, "SELECT CURRENT_TIME", extract_single_value, &result); @@ -15708,8 +15717,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::vector> scalarFunctions; + std::vector> aggregateFunctions; }; } } From 8933209df44b7f5fecd9a0af478992e06034ac6f Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 15 Nov 2023 21:35:23 +0200 Subject: [PATCH 15/50] Operators for user-defined functions --- dev/function.h | 5 +++++ include/sqlite_orm/sqlite_orm.h | 6 ++++++ tests/static_tests/operators_adl.cpp | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/dev/function.h b/dev/function.h index f8cd7efe3..469423f93 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 "tags.h" namespace sqlite_orm { @@ -86,6 +87,10 @@ namespace sqlite_orm { args_tuple args; }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v>> = true; + template struct unpacked_arg { using type = T; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 6006bb768..ff98d125e 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10865,6 +10865,8 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" +// #include "tags.h" + namespace sqlite_orm { struct arg_values; @@ -10943,6 +10945,10 @@ namespace sqlite_orm { args_tuple args; }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v>> = true; + template struct unpacked_arg { using type = T; diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index 2c8a3c200..23335cf60 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -10,6 +10,7 @@ using sqlite_orm::and_; using sqlite_orm::c; using sqlite_orm::colalias_a; using sqlite_orm::column; +using sqlite_orm::func; using sqlite_orm::get; using sqlite_orm::or_; using sqlite_orm::internal::and_condition_t; @@ -107,11 +108,15 @@ TEST_CASE("ADL and expression operators") { struct User { int id; }; + struct ScalarFunction { + int operator()() const; + }; runTests(c(&User::id)); runTests(column(&User::id)); runTests(get()); runTests(alias_column>(&User::id)); + runTests(func()); #ifdef SQLITE_ORM_WITH_CPP20_ALIASES runTests("a"_col); #endif From 731b69f0afcd61ea1b404cadf7ae972871856d6c Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 15 Nov 2023 23:38:07 +0200 Subject: [PATCH 16/50] Corrected unit test for operator overloading for user-defined function --- tests/static_tests/operators_adl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index 23335cf60..1d5f833ec 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -109,6 +109,7 @@ TEST_CASE("ADL and expression operators") { int id; }; struct ScalarFunction { + static const char* name(); int operator()() const; }; From 2c234491ccfe05111656110bb06c1e0306a438f9 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 17 Nov 2023 17:53:15 +0200 Subject: [PATCH 17/50] Used std allocator for providing memory space to user-defined functions This approach is not cumbersome when it comes to raw storage and the possible memory requirements of the user-defined function classes, and therefore sticks to regular "new"/"delete" expressions. --- dev/function.h | 9 ++ dev/functional/config.h | 8 ++ dev/storage_base.h | 47 +++++-- dev/udf_proxy.h | 162 +++++++++++++---------- include/sqlite_orm/sqlite_orm.h | 226 +++++++++++++++++++++----------- tests/tests.cpp | 8 ++ tests/tests2.cpp | 42 ++++++ 7 files changed, 346 insertions(+), 156 deletions(-) diff --git a/dev/function.h b/dev/function.h index 469423f93..bb525121a 100644 --- a/dev/function.h +++ b/dev/function.h @@ -204,6 +204,10 @@ namespace sqlite_orm { #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 { @@ -212,6 +216,11 @@ namespace sqlite_orm { }; /** @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 { diff --git a/dev/functional/config.h b/dev/functional/config.h index edf98cfb1..ec984c51d 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -24,6 +24,14 @@ #define SQLITE_ORM_NOUNIQUEADDRESS #endif +#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(likely) >= 201803L +#define SQLITE_ORM_CPP_LIKELY [[likely]] +#define SQLITE_ORM_CPP_UNLIKELY [[unlikely]] +#else +#define SQLITE_ORM_CPP_LIKELY +#define SQLITE_ORM_CPP_UNLIKELY +#endif + #ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED #define SQLITE_ORM_CONSTEVAL consteval #else diff --git a/dev/storage_base.h b/dev/storage_base.h index 72c6f27a7..6785a0fa0 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -1,6 +1,7 @@ #pragma once #include +#include // std::allocator #include // std::function, std::bind, std::bind_front #include // std::string #include // std::stringstream @@ -235,7 +236,9 @@ namespace sqlite_orm { } /** - * Call this to create user defined scalar function. Can be called at any time no matter connection is opened or no. + * Create a user-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or no. + * * T - function class. T must have operator() overload and static name function like this: * ``` * struct SqrtFunction { @@ -250,7 +253,7 @@ namespace sqlite_orm { * }; * ``` * - * Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function() { @@ -264,12 +267,14 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(std::make_unique>( + this->scalarFunctions.push_back(make_udf_proxy( std::move(name), argsCount, /* constructAt = */ - [](void* location) -> void* { - return new(location) F(); + [](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location); }, /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), @@ -288,6 +293,12 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a user-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or no. + * + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + */ template void create_scalar_function() { return this->create_scalar_function>(); @@ -295,7 +306,9 @@ namespace sqlite_orm { #endif /** - * Call this to create user defined aggregate function. Can be called at any time no matter connection is opened or no. + * Create a user-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or no. + * * T - function class. T must have step member function, fin member function and static name function like this: * ``` * struct MeanFunction { @@ -317,7 +330,7 @@ namespace sqlite_orm { * }; * ``` * - * Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_aggregate_function() { @@ -331,12 +344,14 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(std::make_unique>( + this->aggregateFunctions.push_back(make_udf_proxy( std::move(name), argsCount, /* constructAt = */ - [](void* location) -> void* { - return new(location) F(); + [](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location); }, /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), @@ -368,6 +383,12 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a user-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or no. + * + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + */ template void create_aggregate_function() { return this->create_aggregate_function>(); @@ -375,7 +396,8 @@ namespace sqlite_orm { #endif /** - * Use it to delete scalar function you created before. Can be called at any time no matter connection is open or no. + * 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() { @@ -393,7 +415,8 @@ namespace sqlite_orm { #endif /** - * Use it to delete aggregate function you created before. Can be called at any time no matter connection is open or no. + * Delete aggregate function you created before. + * Can be called at any time no matter whether the database connection is open or not. */ template void delete_aggregate_function() { diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index 3e108bf3f..b84431087 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -1,10 +1,10 @@ #pragma once #include -#include // std::launder +#include // std::allocator #include // std::string #include // std::function -#include // std::move +#include // std::move, std::pair #include "error_code.h" @@ -27,98 +27,128 @@ namespace sqlite_orm { using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); struct destruct_only_deleter { - template - void operator()(F* f) const noexcept { - f->~F(); + template + void operator()(UDF* f) const noexcept { + f->~UDF(); } }; std::string name; int argumentsCount; - std::function constructAt; + std::function constructAt; xdestroy_fn_t destroy; func_call_fn_t func; final_call_fn_t finalAggregateCall; - // flag whether the UDF has been constructed at `udfHandle`; + + const xdestroy_fn_t udfDeallocate; + // flag whether the UDF has been udfConstructed at `udfHandle`; // necessary for aggregation operations - bool constructed; - // pointer to memory for UDF in derived proxy veneer + bool udfConstructed; + // pointer to memory for UDF void* const udfHandle; + + ~udf_proxy() { + udfDeallocate(udfHandle); + } }; - /* - * A veneer to `udf_proxy` that provides memory space for a user-defined function object. - * - * Note: it must be a veneer, i.e. w/o any non-trivially destructible member variables. - */ template - struct udf_proxy_veneer : udf_proxy { - // allocated memory for user-defined function - alignas(UDF) char storage[sizeof(UDF)]; - - udf_proxy_veneer(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t run) : - udf_proxy { - std::move(name), argumentsCount, std::move(constructAt), destroy, run, nullptr, false, -#if __cpp_lib_launder >= 201606L - std::launder((UDF*)this->storage) -#else - storage -#endif - } - {} - - udf_proxy_veneer(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t step, - final_call_fn_t finalCall) : - udf_proxy { - std::move(name), argumentsCount, std::move(constructAt), destroy, step, finalCall, false, -#if __cpp_lib_launder >= 201606L - std::launder((UDF*)this->storage) -#else - storage -#endif - } - {} + std::pair create_udf_storage() { + std::allocator allocator; + using traits = std::allocator_traits; + + constexpr auto deallocate = [](void* location) noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::deallocate(allocator, (UDF*)location, 1); + }; + return {traits::allocate(allocator, 1), deallocate}; + } + + template + std::unique_ptr make_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + udf_proxy::func_call_fn_t run) { + auto p = create_udf_storage(); + std::unique_ptr proxy{new udf_proxy{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + run, + nullptr, + p.second, + false, + p.first}}; + return proxy; + } + + template + std::unique_ptr make_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + udf_proxy::func_call_fn_t step, + udf_proxy::final_call_fn_t finalCall) { + auto p = create_udf_storage(); + std::unique_ptr proxy{new udf_proxy{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + step, + finalCall, + p.second, + false, + p.first}}; + return proxy; }; + inline void check_args_count(const udf_proxy* proxy, int argsCount) { + if(proxy->argumentsCount != -1) { + if(proxy->argumentsCount != argsCount && + /*check fin call*/ argsCount != -1) + SQLITE_ORM_CPP_UNLIKELY { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } + } + } + + inline void ensure_udf(udf_proxy* proxy, int argsCount) { + if(proxy->udfConstructed) + SQLITE_ORM_CPP_LIKELY { + return; + } + check_args_count(proxy, argsCount); + proxy->constructAt(proxy->udfHandle); + proxy->udfConstructed = true; + } + + inline void destruct_udf(udf_proxy* proxy) { + proxy->udfConstructed = false; + proxy->destroy(proxy->udfHandle); + } + inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); - if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - const std::unique_ptr udfHandle{proxy->constructAt(proxy->udfHandle), proxy->destroy}; - proxy->func(udfHandle.get(), context, argsCount, values); + check_args_count(proxy, argsCount); + proxy->constructAt(proxy->udfHandle); + const std::unique_ptr udfGuard{proxy->udfHandle, proxy->destroy}; + proxy->func(proxy->udfHandle, 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)); - if(!proxy->constructed) { - if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - proxy->constructAt(proxy->udfHandle); - proxy->constructed = true; - } + ensure_udf(proxy, argsCount); proxy->func(proxy->udfHandle, context, argsCount, values); } inline void aggregate_function_final_callback(sqlite3_context* context) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); // note: it is possible that the 'step' function was never called - if(!proxy->constructed) { - proxy->constructAt(proxy->udfHandle); - proxy->constructed = true; - } + ensure_udf(proxy, -1); proxy->finalAggregateCall(proxy->udfHandle, context); - proxy->destroy(proxy->udfHandle); - proxy->constructed = false; + destruct_udf(proxy); } } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ff98d125e..213aaa13a 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -185,6 +185,14 @@ using std::nullptr_t; #define SQLITE_ORM_NOUNIQUEADDRESS #endif +#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(likely) >= 201803L +#define SQLITE_ORM_CPP_LIKELY [[likely]] +#define SQLITE_ORM_CPP_UNLIKELY [[unlikely]] +#else +#define SQLITE_ORM_CPP_LIKELY +#define SQLITE_ORM_CPP_UNLIKELY +#endif + #ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED #define SQLITE_ORM_CONSTEVAL consteval #else @@ -11062,6 +11070,10 @@ namespace sqlite_orm { #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 { @@ -11070,6 +11082,11 @@ namespace sqlite_orm { }; /** @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 { @@ -13602,6 +13619,7 @@ namespace sqlite_orm { // #include "storage_base.h" #include +#include // std::allocator #include // std::function, std::bind, std::bind_front #include // std::string #include // std::stringstream @@ -14811,10 +14829,10 @@ namespace sqlite_orm { // #include "udf_proxy.h" #include -#include // std::launder +#include // std::allocator #include // std::string #include // std::function -#include // std::move +#include // std::move, std::pair // #include "error_code.h" @@ -14837,98 +14855,128 @@ namespace sqlite_orm { using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); struct destruct_only_deleter { - template - void operator()(F* f) const noexcept { - f->~F(); + template + void operator()(UDF* f) const noexcept { + f->~UDF(); } }; std::string name; int argumentsCount; - std::function constructAt; + std::function constructAt; xdestroy_fn_t destroy; func_call_fn_t func; final_call_fn_t finalAggregateCall; - // flag whether the UDF has been constructed at `udfHandle`; + + const xdestroy_fn_t udfDeallocate; + // flag whether the UDF has been udfConstructed at `udfHandle`; // necessary for aggregation operations - bool constructed; - // pointer to memory for UDF in derived proxy veneer + bool udfConstructed; + // pointer to memory for UDF void* const udfHandle; + + ~udf_proxy() { + udfDeallocate(udfHandle); + } }; - /* - * A veneer to `udf_proxy` that provides memory space for a user-defined function object. - * - * Note: it must be a veneer, i.e. w/o any non-trivially destructible member variables. - */ template - struct udf_proxy_veneer : udf_proxy { - // allocated memory for user-defined function - alignas(UDF) char storage[sizeof(UDF)]; - - udf_proxy_veneer(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t run) : - udf_proxy { - std::move(name), argumentsCount, std::move(constructAt), destroy, run, nullptr, false, -#if __cpp_lib_launder >= 201606L - std::launder((UDF*)this->storage) -#else - storage -#endif - } - {} - - udf_proxy_veneer(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - func_call_fn_t step, - final_call_fn_t finalCall) : - udf_proxy { - std::move(name), argumentsCount, std::move(constructAt), destroy, step, finalCall, false, -#if __cpp_lib_launder >= 201606L - std::launder((UDF*)this->storage) -#else - storage -#endif + std::pair create_udf_storage() { + std::allocator allocator; + using traits = std::allocator_traits; + + constexpr auto deallocate = [](void* location) noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::deallocate(allocator, (UDF*)location, 1); + }; + return {traits::allocate(allocator, 1), deallocate}; + } + + template + std::unique_ptr make_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + udf_proxy::func_call_fn_t run) { + auto p = create_udf_storage(); + std::unique_ptr proxy{new udf_proxy{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + run, + nullptr, + p.second, + false, + p.first}}; + return proxy; + } + + template + std::unique_ptr make_udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + udf_proxy::func_call_fn_t step, + udf_proxy::final_call_fn_t finalCall) { + auto p = create_udf_storage(); + std::unique_ptr proxy{new udf_proxy{std::move(name), + argumentsCount, + std::move(constructAt), + destroy, + step, + finalCall, + p.second, + false, + p.first}}; + return proxy; + }; + + inline void check_args_count(const udf_proxy* proxy, int argsCount) { + if(proxy->argumentsCount != -1) { + if(proxy->argumentsCount != argsCount && + /*check fin call*/ argsCount != -1) + SQLITE_ORM_CPP_UNLIKELY { + throw std::system_error{orm_error_code::arguments_count_does_not_match}; + } } - {} - }; + } + + inline void ensure_udf(udf_proxy* proxy, int argsCount) { + if(proxy->udfConstructed) + SQLITE_ORM_CPP_LIKELY { + return; + } + check_args_count(proxy, argsCount); + proxy->constructAt(proxy->udfHandle); + proxy->udfConstructed = true; + } + + inline void destruct_udf(udf_proxy* proxy) { + proxy->udfConstructed = false; + proxy->destroy(proxy->udfHandle); + } inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); - if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - const std::unique_ptr udfHandle{proxy->constructAt(proxy->udfHandle), proxy->destroy}; - proxy->func(udfHandle.get(), context, argsCount, values); + check_args_count(proxy, argsCount); + proxy->constructAt(proxy->udfHandle); + const std::unique_ptr udfGuard{proxy->udfHandle, proxy->destroy}; + proxy->func(proxy->udfHandle, 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)); - if(!proxy->constructed) { - if(proxy->argumentsCount != -1 && proxy->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - proxy->constructAt(proxy->udfHandle); - proxy->constructed = true; - } + ensure_udf(proxy, argsCount); proxy->func(proxy->udfHandle, context, argsCount, values); } inline void aggregate_function_final_callback(sqlite3_context* context) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); // note: it is possible that the 'step' function was never called - if(!proxy->constructed) { - proxy->constructAt(proxy->udfHandle); - proxy->constructed = true; - } + ensure_udf(proxy, -1); proxy->finalAggregateCall(proxy->udfHandle, context); - proxy->destroy(proxy->udfHandle); - proxy->constructed = false; + destruct_udf(proxy); } } } @@ -15141,7 +15189,9 @@ namespace sqlite_orm { } /** - * Call this to create user defined scalar function. Can be called at any time no matter connection is opened or no. + * Create a user-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or no. + * * T - function class. T must have operator() overload and static name function like this: * ``` * struct SqrtFunction { @@ -15156,7 +15206,7 @@ namespace sqlite_orm { * }; * ``` * - * Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function() { @@ -15170,12 +15220,14 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(std::make_unique>( + this->scalarFunctions.push_back(make_udf_proxy( std::move(name), argsCount, /* constructAt = */ - [](void* location) -> void* { - return new(location) F(); + [](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location); }, /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), @@ -15194,6 +15246,12 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a user-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or no. + * + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + */ template void create_scalar_function() { return this->create_scalar_function>(); @@ -15201,7 +15259,9 @@ namespace sqlite_orm { #endif /** - * Call this to create user defined aggregate function. Can be called at any time no matter connection is opened or no. + * Create a user-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or no. + * * T - function class. T must have step member function, fin member function and static name function like this: * ``` * struct MeanFunction { @@ -15223,7 +15283,7 @@ namespace sqlite_orm { * }; * ``` * - * Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_aggregate_function() { @@ -15237,12 +15297,14 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(std::make_unique>( + this->aggregateFunctions.push_back(make_udf_proxy( std::move(name), argsCount, /* constructAt = */ - [](void* location) -> void* { - return new(location) F(); + [](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location); }, /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), @@ -15274,6 +15336,12 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a user-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or no. + * + * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + */ template void create_aggregate_function() { return this->create_aggregate_function>(); @@ -15281,7 +15349,8 @@ namespace sqlite_orm { #endif /** - * Use it to delete scalar function you created before. Can be called at any time no matter connection is open or no. + * 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() { @@ -15299,7 +15368,8 @@ namespace sqlite_orm { #endif /** - * Use it to delete aggregate function you created before. Can be called at any time no matter connection is open or no. + * Delete aggregate function you created before. + * Can be called at any time no matter whether the database connection is open or not. */ template void delete_aggregate_function() { diff --git a/tests/tests.cpp b/tests/tests.cpp index dd5bdc63d..bd2dda14c 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -1,6 +1,14 @@ #include #include +#ifdef _DEBUG +#pragma comment(lib, "Catch2d.Lib") +#pragma comment(lib, "manual-link/Catch2Maind.Lib") +#else +#pragma comment(lib, "Catch2.Lib") +#pragma comment(lib, "manual-link/Catch2Main.Lib") +#endif + #include // std::vector #include // std::string #include // remove diff --git a/tests/tests2.cpp b/tests/tests2.cpp index 871b9c3b5..d0104157e 100644 --- a/tests/tests2.cpp +++ b/tests/tests2.cpp @@ -356,6 +356,33 @@ int MultiSum::objectsCount = 0; int FirstFunction::objectsCount = 0; int FirstFunction::callsCount = 0; +#if __cpp_aligned_new >= 201606L +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedScalarFunction { + int operator()(int arg) const { + return arg; + } + + static const char* name() { + return "OVERALIGNED"; + } +}; + +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedAggregateFunction { + double sum = 0; + + void step(double arg) { + sum += arg; + } + int fin() const { + return sum; + } + + static const char* name() { + return "OVERALIGNED"; + } +}; +#endif + TEST_CASE("custom functions") { using Catch::Matchers::ContainsSubstring; @@ -381,6 +408,12 @@ TEST_CASE("custom functions") { }; auto storage = make_storage(path, make_table("users", make_column("id", &User::id))); storage.sync_schema(); + + storage.create_aggregate_function(); + // test the case when `MeanFunction::step()` was never called + { REQUIRE_NOTHROW(storage.select(func(&User::id))); } + storage.delete_aggregate_function(); + // call before creation REQUIRE_THROWS_WITH(storage.select(func(4)), ContainsSubstring("no such function")); @@ -492,6 +525,15 @@ TEST_CASE("custom functions") { REQUIRE(MultiSum::objectsCount == 0); } storage.delete_aggregate_function(); + +#if __cpp_aligned_new >= 201606L + { + storage.create_scalar_function(); + REQUIRE_NOTHROW(storage.delete_scalar_function()); + storage.create_aggregate_function(); + REQUIRE_NOTHROW(storage.delete_aggregate_function()); + } +#endif } // Wrap std::default_delete in a function From 0eada6af56187346cf892ff35e6226104f1cfd67 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 17 Nov 2023 18:17:41 +0200 Subject: [PATCH 18/50] Extracted unit tests from tests2.cpp --- tests/CMakeLists.txt | 2 + tests/tests.cpp | 8 - tests/tests2.cpp | 492 ------------------------------- tests/user_defined_functions.cpp | 351 ++++++++++++++++++++++ tests/xdestroy_handling.cpp | 147 +++++++++ 5 files changed, 500 insertions(+), 500 deletions(-) create mode 100644 tests/user_defined_functions.cpp create mode 100644 tests/xdestroy_handling.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a0e811893..b5b26a216 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,6 +35,7 @@ add_executable(unit_tests static_tests/operators_adl.cpp static_tests/table_static_tests.cpp tuple_iteration.cpp + xdestroy_handling.cpp sync_schema_tests.cpp tests.cpp tests2.cpp @@ -51,6 +52,7 @@ add_executable(unit_tests built_in_functions_tests/core_functions_tests.cpp built_in_functions_tests/datetime_function_tests.cpp built_in_functions_tests/math_functions.cpp + user_defined_functions.cpp constraints/composite_key.cpp operators/arithmetic_operators.cpp operators/like.cpp diff --git a/tests/tests.cpp b/tests/tests.cpp index bd2dda14c..dd5bdc63d 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -1,14 +1,6 @@ #include #include -#ifdef _DEBUG -#pragma comment(lib, "Catch2d.Lib") -#pragma comment(lib, "manual-link/Catch2Maind.Lib") -#else -#pragma comment(lib, "Catch2.Lib") -#pragma comment(lib, "manual-link/Catch2Main.Lib") -#endif - #include // std::vector #include // std::string #include // remove diff --git a/tests/tests2.cpp b/tests/tests2.cpp index d0104157e..9f73c46ae 100644 --- a/tests/tests2.cpp +++ b/tests/tests2.cpp @@ -1,13 +1,7 @@ #include #include -#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED -#include // std::optional -#endif // SQLITE_ORM_OPTIONAL_SUPPORTED -#include // std::default_delete -#include // free() using namespace sqlite_orm; -using std::default_delete; using std::unique_ptr; TEST_CASE("Empty storage") { @@ -188,489 +182,3 @@ TEST_CASE("insert") { storage.insert(ObjectWithoutRowid{20, "Death"}); REQUIRE(storage.get(20).name == "Death"); } - -struct SqrtFunction { - static int callsCount; - - double operator()(double arg) const { - ++callsCount; - return std::sqrt(arg); - } - - static const char* name() { - return "SQRT_CUSTOM"; - } -}; - -int SqrtFunction::callsCount = 0; - -struct HasPrefixFunction { - static int callsCount; - static int objectsCount; - - HasPrefixFunction() { - ++objectsCount; - } - - HasPrefixFunction(const HasPrefixFunction&) { - ++objectsCount; - } - - HasPrefixFunction(HasPrefixFunction&&) { - ++objectsCount; - } - - ~HasPrefixFunction() { - --objectsCount; - } - - bool operator()(const std::string& str, const std::string& prefix) { - ++callsCount; - return str.compare(0, prefix.size(), prefix) == 0; - } - - static std::string name() { - return "HAS_PREFIX"; - } -}; - -int HasPrefixFunction::callsCount = 0; -int HasPrefixFunction::objectsCount = 0; - -struct MeanFunction { - double total = 0; - int count = 0; - - static int objectsCount; - - MeanFunction() { - ++objectsCount; - } - - MeanFunction(const MeanFunction&) { - ++objectsCount; - } - - MeanFunction(MeanFunction&&) { - ++objectsCount; - } - - ~MeanFunction() { - --objectsCount; - } - - void step(double value) { - total += value; - ++count; - } - - double fin() const { - return total / count; - } - - static std::string name() { - return "MEAN"; - } -}; - -int MeanFunction::objectsCount = 0; - -struct FirstFunction { - static int objectsCount; - static int callsCount; - - FirstFunction() { - ++objectsCount; - } - - FirstFunction(const MeanFunction&) { - ++objectsCount; - } - - FirstFunction(MeanFunction&&) { - ++objectsCount; - } - - ~FirstFunction() { - --objectsCount; - } - - std::string operator()(const arg_values& args) const { - ++callsCount; - std::string res; - res.reserve(args.size()); - for(auto value: args) { - auto stringValue = value.get(); - if(!stringValue.empty()) { - res += stringValue.front(); - } - } - return res; - } - - static const char* name() { - return "FIRST"; - } -}; - -struct MultiSum { - double sum = 0; - - static int objectsCount; - - MultiSum() { - ++objectsCount; - } - - MultiSum(const MeanFunction&) { - ++objectsCount; - } - - MultiSum(MeanFunction&&) { - ++objectsCount; - } - - ~MultiSum() { - --objectsCount; - } - - void step(const arg_values& args) { - for(auto it = args.begin(); it != args.end(); ++it) { - if(!it->empty() && (it->is_integer() || it->is_float())) { - this->sum += it->get(); - } - } - } - - double fin() const { - return this->sum; - } - - static const char* name() { - return "MULTI_SUM"; - } -}; - -int MultiSum::objectsCount = 0; - -int FirstFunction::objectsCount = 0; -int FirstFunction::callsCount = 0; - -#if __cpp_aligned_new >= 201606L -struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedScalarFunction { - int operator()(int arg) const { - return arg; - } - - static const char* name() { - return "OVERALIGNED"; - } -}; - -struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedAggregateFunction { - double sum = 0; - - void step(double arg) { - sum += arg; - } - int fin() const { - return sum; - } - - static const char* name() { - return "OVERALIGNED"; - } -}; -#endif - -TEST_CASE("custom functions") { - using Catch::Matchers::ContainsSubstring; - - SqrtFunction::callsCount = 0; - HasPrefixFunction::callsCount = 0; - FirstFunction::callsCount = 0; - - std::string path; - SECTION("in memory") { - path = {}; - } - SECTION("file") { - path = "custom_function.sqlite"; - ::remove(path.c_str()); - } - struct User { - int id = 0; - -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - User() = default; - User(int id) : id{id} {} -#endif - }; - auto storage = make_storage(path, make_table("users", make_column("id", &User::id))); - storage.sync_schema(); - - storage.create_aggregate_function(); - // test the case when `MeanFunction::step()` was never called - { REQUIRE_NOTHROW(storage.select(func(&User::id))); } - storage.delete_aggregate_function(); - - // call before creation - REQUIRE_THROWS_WITH(storage.select(func(4)), ContainsSubstring("no such function")); - - // create function - REQUIRE(SqrtFunction::callsCount == 0); - - storage.create_scalar_function(); - - REQUIRE(SqrtFunction::callsCount == 0); - - // call after creation - { - auto rows = storage.select(func(4)); - REQUIRE(SqrtFunction::callsCount == 1); - decltype(rows) expected; - expected.push_back(2); - REQUIRE(rows == expected); - } - - // create function - REQUIRE(HasPrefixFunction::callsCount == 0); - REQUIRE(HasPrefixFunction::objectsCount == 0); - storage.create_scalar_function(); - REQUIRE(HasPrefixFunction::callsCount == 0); - REQUIRE(HasPrefixFunction::objectsCount == 0); - - // call after creation - { - auto rows = storage.select(func("one", "o")); - decltype(rows) expected; - expected.push_back(true); - REQUIRE(rows == expected); - } - REQUIRE(HasPrefixFunction::callsCount == 1); - REQUIRE(HasPrefixFunction::objectsCount == 0); - { - auto rows = storage.select(func("two", "b")); - decltype(rows) expected; - expected.push_back(false); - REQUIRE(rows == expected); - } - REQUIRE(HasPrefixFunction::callsCount == 2); - REQUIRE(HasPrefixFunction::objectsCount == 0); - - // delete function - storage.delete_scalar_function(); - - // delete function - storage.delete_scalar_function(); - - storage.create_aggregate_function(); - - storage.replace(User{1}); - storage.replace(User{2}); - storage.replace(User{3}); - REQUIRE(storage.count() == 3); - { - REQUIRE(MeanFunction::objectsCount == 0); - auto rows = storage.select(func(&User::id)); - REQUIRE(MeanFunction::objectsCount == 0); - decltype(rows) expected; - expected.push_back(2); - REQUIRE(rows == expected); - } - storage.delete_aggregate_function(); - - storage.create_scalar_function(); - { - auto rows = storage.select(func("Vanotek", "Tinashe", "Pitbull")); - decltype(rows) expected; - expected.push_back("VTP"); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 1); - } - { - auto rows = storage.select(func("Charli XCX", "Rita Ora")); - decltype(rows) expected; - expected.push_back("CR"); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 2); - } - { - auto rows = storage.select(func("Ted")); - decltype(rows) expected; - expected.push_back("T"); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 3); - } - { - auto rows = storage.select(func()); - decltype(rows) expected; - expected.push_back(""); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 4); - } - storage.delete_scalar_function(); - - storage.create_aggregate_function(); - { - REQUIRE(MultiSum::objectsCount == 0); - auto rows = storage.select(func(&User::id, 5)); - decltype(rows) expected; - expected.push_back(21); - REQUIRE(rows == expected); - REQUIRE(MultiSum::objectsCount == 0); - } - storage.delete_aggregate_function(); - -#if __cpp_aligned_new >= 201606L - { - storage.create_scalar_function(); - REQUIRE_NOTHROW(storage.delete_scalar_function()); - storage.create_aggregate_function(); - REQUIRE_NOTHROW(storage.delete_aggregate_function()); - } -#endif -} - -// Wrap std::default_delete in a function -#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION -template -void delete_default(std::conditional_t::value, std::decay_t, T*> o) noexcept( - noexcept(std::default_delete{}(o))) { - std::default_delete{}(o); -} - -// Integral function constant for default deletion -template -using delete_default_t = std::integral_constant), delete_default>; -// Integral function constant variable for default deletion -template -SQLITE_ORM_INLINE_VAR constexpr delete_default_t delete_default_f{}; -#endif - -using free_t = std::integral_constant; -SQLITE_ORM_INLINE_VAR constexpr free_t free_f{}; - -TEST_CASE("obtain_xdestroy_for") { - - using internal::xdestroy_proxy; - - // class yielding a 'xDestroy' function pointer - struct xdestroy_holder { - xdestroy_fn_t xDestroy = free; - - constexpr operator xdestroy_fn_t() const noexcept { - return xDestroy; - } - }; - - // class yielding a function pointer not of type xdestroy_fn_t - struct int_destroy_holder { - using destroy_fn_t = void (*)(int*); - - destroy_fn_t destroy = nullptr; - - constexpr operator destroy_fn_t() const noexcept { - return destroy; - } - }; - - { - constexpr int* int_nullptr = nullptr; -#if !defined(SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION) || \ - (__cpp_constexpr >= 201907L) // Trivial default initialization in constexpr functions - constexpr const int* const_int_nullptr = nullptr; -#endif - - // null_xdestroy_f(int*) - constexpr xdestroy_fn_t xDestroy1 = obtain_xdestroy_for(null_xdestroy_f, int_nullptr); - STATIC_REQUIRE(xDestroy1 == nullptr); - REQUIRE(xDestroy1 == nullptr); - - // free(int*) - constexpr xdestroy_fn_t xDestroy2 = obtain_xdestroy_for(free, int_nullptr); - STATIC_REQUIRE(xDestroy2 == &free); - REQUIRE(xDestroy2 == &free); - - // free_f(int*) - constexpr xdestroy_fn_t xDestroy3 = obtain_xdestroy_for(free_f, int_nullptr); - STATIC_REQUIRE(xDestroy3 == &free); - REQUIRE(xDestroy3 == &free); - -#if __cpp_constexpr >= 201603L // constexpr lambda - // [](void* p){} - constexpr auto lambda4_1 = [](void*) {}; - constexpr xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); - STATIC_REQUIRE(xDestroy4_1 == lambda4_1); - REQUIRE(xDestroy4_1 == lambda4_1); -#else -#if !defined(_MSC_VER) || (_MSC_VER >= 1914) // conversion of lambda closure to function pointer using `+` - // [](void* p){} - auto lambda4_1 = [](void*) {}; - xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); - REQUIRE(xDestroy4_1 == lambda4_1); -#endif -#endif - - // [](int* p) { delete p; } -#if __cplusplus >= 202002L // default-constructible non-capturing lambdas - constexpr auto lambda4_2 = [](int* p) { - delete p; - }; - using lambda4_2_t = std::remove_const_t; - constexpr xdestroy_fn_t xDestroy4_2 = obtain_xdestroy_for(lambda4_2, int_nullptr); - STATIC_REQUIRE(xDestroy4_2 == &xdestroy_proxy); - REQUIRE((xDestroy4_2 == &xdestroy_proxy)); -#endif - - // default_delete(int*) - constexpr xdestroy_fn_t xDestroy5 = obtain_xdestroy_for(default_delete{}, int_nullptr); - STATIC_REQUIRE(xDestroy5 == &xdestroy_proxy, int>); - REQUIRE((xDestroy5 == &xdestroy_proxy, int>)); - -#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION - // delete_default_f(int*) - constexpr xdestroy_fn_t xDestroy6 = obtain_xdestroy_for(delete_default_f, int_nullptr); - STATIC_REQUIRE(xDestroy6 == &xdestroy_proxy, int>); - REQUIRE((xDestroy6 == &xdestroy_proxy, int>)); - - // delete_default_f(const int*) - constexpr xdestroy_fn_t xDestroy7 = obtain_xdestroy_for(delete_default_f, const_int_nullptr); - STATIC_REQUIRE(xDestroy7 == &xdestroy_proxy, const int>); - REQUIRE((xDestroy7 == &xdestroy_proxy, const int>)); -#endif - -#if __cpp_constexpr >= 201907L // Trivial default initialization in constexpr functions - // xdestroy_holder{ free }(int*) - constexpr xdestroy_fn_t xDestroy8 = obtain_xdestroy_for(xdestroy_holder{free}, int_nullptr); - STATIC_REQUIRE(xDestroy8 == &free); - REQUIRE(xDestroy8 == &free); - - // xdestroy_holder{ free }(const int*) - constexpr xdestroy_fn_t xDestroy9 = obtain_xdestroy_for(xdestroy_holder{free}, const_int_nullptr); - STATIC_REQUIRE(xDestroy9 == &free); - REQUIRE(xDestroy9 == &free); - - // xdestroy_holder{ nullptr }(const int*) - constexpr xdestroy_fn_t xDestroy10 = obtain_xdestroy_for(xdestroy_holder{nullptr}, const_int_nullptr); - STATIC_REQUIRE(xDestroy10 == nullptr); - REQUIRE(xDestroy10 == nullptr); -#endif - - // expressions that do not work -#if 0 - // can't use functions that differ from xdestroy_fn_t - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(delete_default, int_nullptr); - // can't use object yielding a function pointer that differs from xdestroy_fn_t - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(int_destroy_holder{}, int_nullptr); - // successfully takes default_delete, but default_delete statically asserts on a non-complete type `void*` - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, int_nullptr); - // successfully takes default_delete, but xdestroy_proxy can't call the deleter with a `const int*` - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, const_int_nullptr); -#endif - } -} diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp new file mode 100644 index 000000000..0c67031ff --- /dev/null +++ b/tests/user_defined_functions.cpp @@ -0,0 +1,351 @@ +#include +#include + +using namespace sqlite_orm; + +struct SqrtFunction { + static int callsCount; + + double operator()(double arg) const { + ++callsCount; + return std::sqrt(arg); + } + + static const char* name() { + return "SQRT_CUSTOM"; + } +}; + +int SqrtFunction::callsCount = 0; + +struct HasPrefixFunction { + static int callsCount; + static int objectsCount; + + HasPrefixFunction() { + ++objectsCount; + } + + HasPrefixFunction(const HasPrefixFunction&) { + ++objectsCount; + } + + HasPrefixFunction(HasPrefixFunction&&) { + ++objectsCount; + } + + ~HasPrefixFunction() { + --objectsCount; + } + + bool operator()(const std::string& str, const std::string& prefix) { + ++callsCount; + return str.compare(0, prefix.size(), prefix) == 0; + } + + static std::string name() { + return "HAS_PREFIX"; + } +}; + +int HasPrefixFunction::callsCount = 0; +int HasPrefixFunction::objectsCount = 0; + +struct MeanFunction { + double total = 0; + int count = 0; + + static int objectsCount; + + MeanFunction() { + ++objectsCount; + } + + MeanFunction(const MeanFunction&) { + ++objectsCount; + } + + MeanFunction(MeanFunction&&) { + ++objectsCount; + } + + ~MeanFunction() { + --objectsCount; + } + + void step(double value) { + total += value; + ++count; + } + + double fin() const { + return total / count; + } + + static std::string name() { + return "MEAN"; + } +}; + +int MeanFunction::objectsCount = 0; + +struct FirstFunction { + static int objectsCount; + static int callsCount; + + FirstFunction() { + ++objectsCount; + } + + FirstFunction(const MeanFunction&) { + ++objectsCount; + } + + FirstFunction(MeanFunction&&) { + ++objectsCount; + } + + ~FirstFunction() { + --objectsCount; + } + + std::string operator()(const arg_values& args) const { + ++callsCount; + std::string res; + res.reserve(args.size()); + for(auto value: args) { + auto stringValue = value.get(); + if(!stringValue.empty()) { + res += stringValue.front(); + } + } + return res; + } + + static const char* name() { + return "FIRST"; + } +}; + +struct MultiSum { + double sum = 0; + + static int objectsCount; + + MultiSum() { + ++objectsCount; + } + + MultiSum(const MeanFunction&) { + ++objectsCount; + } + + MultiSum(MeanFunction&&) { + ++objectsCount; + } + + ~MultiSum() { + --objectsCount; + } + + void step(const arg_values& args) { + for(auto it = args.begin(); it != args.end(); ++it) { + if(!it->empty() && (it->is_integer() || it->is_float())) { + this->sum += it->get(); + } + } + } + + double fin() const { + return this->sum; + } + + static const char* name() { + return "MULTI_SUM"; + } +}; + +int MultiSum::objectsCount = 0; + +int FirstFunction::objectsCount = 0; +int FirstFunction::callsCount = 0; + +#if __cpp_aligned_new >= 201606L +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedScalarFunction { + int operator()(int arg) const { + return arg; + } + + static const char* name() { + return "OVERALIGNED"; + } +}; + +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedAggregateFunction { + double sum = 0; + + void step(double arg) { + sum += arg; + } + int fin() const { + return sum; + } + + static const char* name() { + return "OVERALIGNED"; + } +}; +#endif + +TEST_CASE("custom functions") { + using Catch::Matchers::ContainsSubstring; + + SqrtFunction::callsCount = 0; + HasPrefixFunction::callsCount = 0; + FirstFunction::callsCount = 0; + + std::string path; + SECTION("in memory") { + path = {}; + } + SECTION("file") { + path = "custom_function.sqlite"; + ::remove(path.c_str()); + } + struct User { + int id = 0; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + User() = default; + User(int id) : id{id} {} +#endif + }; + auto storage = make_storage(path, make_table("users", make_column("id", &User::id))); + storage.sync_schema(); + + storage.create_aggregate_function(); + // test the case when `MeanFunction::step()` was never called + { REQUIRE_NOTHROW(storage.select(func(&User::id))); } + storage.delete_aggregate_function(); + + // call before creation + REQUIRE_THROWS_WITH(storage.select(func(4)), ContainsSubstring("no such function")); + + // create function + REQUIRE(SqrtFunction::callsCount == 0); + + storage.create_scalar_function(); + + REQUIRE(SqrtFunction::callsCount == 0); + + // call after creation + { + auto rows = storage.select(func(4)); + REQUIRE(SqrtFunction::callsCount == 1); + decltype(rows) expected; + expected.push_back(2); + REQUIRE(rows == expected); + } + + // create function + REQUIRE(HasPrefixFunction::callsCount == 0); + REQUIRE(HasPrefixFunction::objectsCount == 0); + storage.create_scalar_function(); + REQUIRE(HasPrefixFunction::callsCount == 0); + REQUIRE(HasPrefixFunction::objectsCount == 0); + + // call after creation + { + auto rows = storage.select(func("one", "o")); + decltype(rows) expected; + expected.push_back(true); + REQUIRE(rows == expected); + } + REQUIRE(HasPrefixFunction::callsCount == 1); + REQUIRE(HasPrefixFunction::objectsCount == 0); + { + auto rows = storage.select(func("two", "b")); + decltype(rows) expected; + expected.push_back(false); + REQUIRE(rows == expected); + } + REQUIRE(HasPrefixFunction::callsCount == 2); + REQUIRE(HasPrefixFunction::objectsCount == 0); + + // delete function + storage.delete_scalar_function(); + + // delete function + storage.delete_scalar_function(); + + storage.create_aggregate_function(); + + storage.replace(User{1}); + storage.replace(User{2}); + storage.replace(User{3}); + REQUIRE(storage.count() == 3); + { + REQUIRE(MeanFunction::objectsCount == 0); + auto rows = storage.select(func(&User::id)); + REQUIRE(MeanFunction::objectsCount == 0); + decltype(rows) expected; + expected.push_back(2); + REQUIRE(rows == expected); + } + storage.delete_aggregate_function(); + + storage.create_scalar_function(); + { + auto rows = storage.select(func("Vanotek", "Tinashe", "Pitbull")); + decltype(rows) expected; + expected.push_back("VTP"); + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 1); + } + { + auto rows = storage.select(func("Charli XCX", "Rita Ora")); + decltype(rows) expected; + expected.push_back("CR"); + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 2); + } + { + auto rows = storage.select(func("Ted")); + decltype(rows) expected; + expected.push_back("T"); + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 3); + } + { + auto rows = storage.select(func()); + decltype(rows) expected; + expected.push_back(""); + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 4); + } + storage.delete_scalar_function(); + + storage.create_aggregate_function(); + { + REQUIRE(MultiSum::objectsCount == 0); + auto rows = storage.select(func(&User::id, 5)); + decltype(rows) expected; + expected.push_back(21); + REQUIRE(rows == expected); + REQUIRE(MultiSum::objectsCount == 0); + } + storage.delete_aggregate_function(); + +#if __cpp_aligned_new >= 201606L + { + storage.create_scalar_function(); + REQUIRE_NOTHROW(storage.delete_scalar_function()); + storage.create_aggregate_function(); + REQUIRE_NOTHROW(storage.delete_aggregate_function()); + } +#endif +} diff --git a/tests/xdestroy_handling.cpp b/tests/xdestroy_handling.cpp new file mode 100644 index 000000000..58e3d9c60 --- /dev/null +++ b/tests/xdestroy_handling.cpp @@ -0,0 +1,147 @@ +#include +#include +#include // std::default_delete +#include // free() + +using namespace sqlite_orm; +using std::default_delete; +using std::unique_ptr; + +// Wrap std::default_delete in a function +#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION +template +void delete_default(std::conditional_t::value, std::decay_t, T*> o) noexcept( + noexcept(std::default_delete{}(o))) { + std::default_delete{}(o); +} + +// Integral function constant for default deletion +template +using delete_default_t = std::integral_constant), delete_default>; +// Integral function constant variable for default deletion +template +SQLITE_ORM_INLINE_VAR constexpr delete_default_t delete_default_f{}; +#endif + +using free_t = std::integral_constant; +SQLITE_ORM_INLINE_VAR constexpr free_t free_f{}; + +TEST_CASE("obtain_xdestroy_for") { + + using internal::xdestroy_proxy; + + // class yielding a 'xDestroy' function pointer + struct xdestroy_holder { + xdestroy_fn_t xDestroy = free; + + constexpr operator xdestroy_fn_t() const noexcept { + return xDestroy; + } + }; + + // class yielding a function pointer not of type xdestroy_fn_t + struct int_destroy_holder { + using destroy_fn_t = void (*)(int*); + + destroy_fn_t destroy = nullptr; + + constexpr operator destroy_fn_t() const noexcept { + return destroy; + } + }; + + { + constexpr int* int_nullptr = nullptr; +#if !defined(SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION) || \ + (__cpp_constexpr >= 201907L) // Trivial default initialization in constexpr functions + constexpr const int* const_int_nullptr = nullptr; +#endif + + // null_xdestroy_f(int*) + constexpr xdestroy_fn_t xDestroy1 = obtain_xdestroy_for(null_xdestroy_f, int_nullptr); + STATIC_REQUIRE(xDestroy1 == nullptr); + REQUIRE(xDestroy1 == nullptr); + + // free(int*) + constexpr xdestroy_fn_t xDestroy2 = obtain_xdestroy_for(free, int_nullptr); + STATIC_REQUIRE(xDestroy2 == &free); + REQUIRE(xDestroy2 == &free); + + // free_f(int*) + constexpr xdestroy_fn_t xDestroy3 = obtain_xdestroy_for(free_f, int_nullptr); + STATIC_REQUIRE(xDestroy3 == &free); + REQUIRE(xDestroy3 == &free); + +#if __cpp_constexpr >= 201603L // constexpr lambda + // [](void* p){} + constexpr auto lambda4_1 = [](void*) {}; + constexpr xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); + STATIC_REQUIRE(xDestroy4_1 == lambda4_1); + REQUIRE(xDestroy4_1 == lambda4_1); +#else +#if !defined(_MSC_VER) || (_MSC_VER >= 1914) // conversion of lambda closure to function pointer using `+` + // [](void* p){} + auto lambda4_1 = [](void*) {}; + xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); + REQUIRE(xDestroy4_1 == lambda4_1); +#endif +#endif + + // [](int* p) { delete p; } +#if __cplusplus >= 202002L // default-constructible non-capturing lambdas + constexpr auto lambda4_2 = [](int* p) { + delete p; + }; + using lambda4_2_t = std::remove_const_t; + constexpr xdestroy_fn_t xDestroy4_2 = obtain_xdestroy_for(lambda4_2, int_nullptr); + STATIC_REQUIRE(xDestroy4_2 == &xdestroy_proxy); + REQUIRE((xDestroy4_2 == &xdestroy_proxy)); +#endif + + // default_delete(int*) + constexpr xdestroy_fn_t xDestroy5 = obtain_xdestroy_for(default_delete{}, int_nullptr); + STATIC_REQUIRE(xDestroy5 == &xdestroy_proxy, int>); + REQUIRE((xDestroy5 == &xdestroy_proxy, int>)); + +#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION + // delete_default_f(int*) + constexpr xdestroy_fn_t xDestroy6 = obtain_xdestroy_for(delete_default_f, int_nullptr); + STATIC_REQUIRE(xDestroy6 == &xdestroy_proxy, int>); + REQUIRE((xDestroy6 == &xdestroy_proxy, int>)); + + // delete_default_f(const int*) + constexpr xdestroy_fn_t xDestroy7 = obtain_xdestroy_for(delete_default_f, const_int_nullptr); + STATIC_REQUIRE(xDestroy7 == &xdestroy_proxy, const int>); + REQUIRE((xDestroy7 == &xdestroy_proxy, const int>)); +#endif + +#if __cpp_constexpr >= 201907L // Trivial default initialization in constexpr functions + // xdestroy_holder{ free }(int*) + constexpr xdestroy_fn_t xDestroy8 = obtain_xdestroy_for(xdestroy_holder{free}, int_nullptr); + STATIC_REQUIRE(xDestroy8 == &free); + REQUIRE(xDestroy8 == &free); + + // xdestroy_holder{ free }(const int*) + constexpr xdestroy_fn_t xDestroy9 = obtain_xdestroy_for(xdestroy_holder{free}, const_int_nullptr); + STATIC_REQUIRE(xDestroy9 == &free); + REQUIRE(xDestroy9 == &free); + + // xdestroy_holder{ nullptr }(const int*) + constexpr xdestroy_fn_t xDestroy10 = obtain_xdestroy_for(xdestroy_holder{nullptr}, const_int_nullptr); + STATIC_REQUIRE(xDestroy10 == nullptr); + REQUIRE(xDestroy10 == nullptr); +#endif + + // expressions that do not work +#if 0 + // can't use functions that differ from xdestroy_fn_t + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(delete_default, int_nullptr); + // can't use object yielding a function pointer that differs from xdestroy_fn_t + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(int_destroy_holder{}, int_nullptr); + // successfully takes default_delete, but default_delete statically asserts on a non-complete type `void*` + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, int_nullptr); + // successfully takes default_delete, but xdestroy_proxy can't call the deleter with a `const int*` + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, const_int_nullptr); +#endif + } +} From 5aaca8667cbbfa2ae5b72d778796daa3375b46c0 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 17 Nov 2023 18:19:05 +0200 Subject: [PATCH 19/50] Fixed constexpr lambda only being available with C++17 --- dev/functional/config.h | 6 ++++++ dev/functional/cxx_core_features.h | 4 ++++ dev/udf_proxy.h | 2 +- include/sqlite_orm/sqlite_orm.h | 12 +++++++++++- tests/user_defined_functions.cpp | 4 ++-- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/dev/functional/config.h b/dev/functional/config.h index ec984c51d..2c88944ca 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -6,6 +6,12 @@ #include #endif +#ifdef SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 constexpr +#else +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 +#endif + #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED #define SQLITE_ORM_INLINE_VAR inline #else diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index d71644fd3..ee804439a 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -37,6 +37,10 @@ #define SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED #endif +#if __cpp_constexpr >= 201603L +#define SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#endif + #if __cpp_if_constexpr >= 201606L #define SQLITE_ORM_IF_CONSTEXPR_SUPPORTED #endif diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index b84431087..7b81d1c2a 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -57,7 +57,7 @@ namespace sqlite_orm { std::allocator allocator; using traits = std::allocator_traits; - constexpr auto deallocate = [](void* location) noexcept { + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { std::allocator allocator; using traits = std::allocator_traits; traits::deallocate(allocator, (UDF*)location, 1); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 213aaa13a..e13bbe52c 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -65,6 +65,10 @@ using std::nullptr_t; #define SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED #endif +#if __cpp_constexpr >= 201603L +#define SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#endif + #if __cpp_if_constexpr >= 201606L #define SQLITE_ORM_IF_CONSTEXPR_SUPPORTED #endif @@ -167,6 +171,12 @@ using std::nullptr_t; #include #endif +#ifdef SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 constexpr +#else +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 +#endif + #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED #define SQLITE_ORM_INLINE_VAR inline #else @@ -14885,7 +14895,7 @@ namespace sqlite_orm { std::allocator allocator; using traits = std::allocator_traits; - constexpr auto deallocate = [](void* location) noexcept { + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { std::allocator allocator; using traits = std::allocator_traits; traits::deallocate(allocator, (UDF*)location, 1); diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 0c67031ff..0ac76a3f4 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -171,7 +171,7 @@ int FirstFunction::objectsCount = 0; int FirstFunction::callsCount = 0; #if __cpp_aligned_new >= 201606L -struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedScalarFunction { +struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedScalarFunction { int operator()(int arg) const { return arg; } @@ -181,7 +181,7 @@ struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedScalarFunction { } }; -struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) OverAlignedAggregateFunction { +struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedAggregateFunction { double sum = 0; void step(double arg) { From 0ca2015ead9d70b8d0ff70b2a87edd489b91bf7b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 18 Nov 2023 23:35:20 +0200 Subject: [PATCH 20/50] Possibility of passing arguments when constructing user-defined function --- dev/storage_base.h | 215 ++++++++++++++++++------------- include/sqlite_orm/sqlite_orm.h | 215 ++++++++++++++++++------------- tests/user_defined_functions.cpp | 53 +++++++- 3 files changed, 294 insertions(+), 189 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 6785a0fa0..55bdd31e2 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -236,8 +236,13 @@ namespace sqlite_orm { } /** - * Create a user-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined scalar SQL function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * T - function class. T must have operator() overload and static name function like this: * ``` @@ -255,59 +260,43 @@ namespace sqlite_orm { * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_scalar_function() { + template + void create_scalar_function(Args&&... args) { static_assert(is_scalar_udf_v, "F must be a scalar function"); - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); - 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.push_back(make_udf_proxy( - std::move(name), - argsCount, - /* constructAt = */ - [](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, (F*)location); - }, - /* destroy = */ - obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), - /* call = */ - [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(udf, std::move(argsTuple)); - statement_binder().result(context, result); - })); - - if(this->connection->retain_count() > 0) { - sqlite3* db = this->connection->get(); - try_to_create_scalar_function(db, *this->scalarFunctions.back()); - } + this->create_scalar_function_impl(/* constructAt */ [args...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, args...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create a user-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_scalar_function() { - return this->create_scalar_function>(); + template + void create_scalar_function(Args&&... args) { + return this->create_scalar_function>(std::forward(args)...); } #endif /** - * Create a user-defined aggregate function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined aggregate SQL function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * T - function class. T must have step member function, fin member function and static name function like this: * ``` @@ -332,66 +321,33 @@ namespace sqlite_orm { * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_aggregate_function() { + template + void create_aggregate_function(Args&&... args) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); - 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.push_back(make_udf_proxy( - std::move(name), - argsCount, - /* constructAt = */ - [](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, (F*)location); - }, - /* destroy = */ - obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), - /* step = */ - [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); -#if __cpp_lib_bind_front >= 201907L - std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); -#else - polyfill::apply( - [&udf](auto&&... args) { - udf.step(std::forward(args)...); - }, - std::move(argsTuple)); -#endif - }, - /* finalCall = */ - [](void* udfHandle, sqlite3_context* context) { - F& udf = *static_cast(udfHandle); - auto result = udf.fin(); - statement_binder().result(context, result); - })); - - if(this->connection->retain_count() > 0) { - sqlite3* db = this->connection->get(); - try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); - } + this->create_aggregate_function_impl(/* constructAt = */ + [args...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, args...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create a user-defined aggregate function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_aggregate_function() { - return this->create_aggregate_function>(); + template + void create_aggregate_function(Args&&... args) { + return this->create_aggregate_function>(std::forward(args)...); } #endif @@ -692,6 +648,79 @@ namespace sqlite_orm { } } + template + void create_scalar_function_impl(std::function constructAt) { + std::stringstream ss; + ss << F::name() << std::flush; + auto name = ss.str(); + 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.push_back(make_udf_proxy( + std::move(name), + argsCount, + std::move(constructAt), + /* destroy = */ + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), + /* call = */ + [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { + F& udf = *static_cast(udfHandle); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(udf, std::move(argsTuple)); + statement_binder().result(context, result); + })); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_scalar_function(db, *this->scalarFunctions.back()); + } + } + + template + void create_aggregate_function_impl(std::function constructAt) { + std::stringstream ss; + ss << F::name() << std::flush; + auto name = ss.str(); + 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.push_back(make_udf_proxy( + std::move(name), + argsCount, + std::move(constructAt), + /* destroy = */ + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), + /* step = */ + [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { + F& udf = *static_cast(udfHandle); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); +#if __cpp_lib_bind_front >= 201907L + std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); +#else + polyfill::apply( + [&udf](auto&&... args) { + udf.step(std::forward(args)...); + }, + std::move(argsTuple)); +#endif + }, + /* finalCall = */ + [](void* udfHandle, sqlite3_context* context) { + F& udf = *static_cast(udfHandle); + auto result = udf.fin(); + statement_binder().result(context, result); + })); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); + } + } + void delete_function_impl(const std::string& name, std::vector>& functions) const { #if __cpp_lib_ranges >= 201911L diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index e13bbe52c..ab6233fe9 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15199,8 +15199,13 @@ namespace sqlite_orm { } /** - * Create a user-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined scalar SQL function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * T - function class. T must have operator() overload and static name function like this: * ``` @@ -15218,59 +15223,43 @@ namespace sqlite_orm { * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_scalar_function() { + template + void create_scalar_function(Args&&... args) { static_assert(is_scalar_udf_v, "F must be a scalar function"); - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); - 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.push_back(make_udf_proxy( - std::move(name), - argsCount, - /* constructAt = */ - [](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, (F*)location); - }, - /* destroy = */ - obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), - /* call = */ - [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(udf, std::move(argsTuple)); - statement_binder().result(context, result); - })); - - if(this->connection->retain_count() > 0) { - sqlite3* db = this->connection->get(); - try_to_create_scalar_function(db, *this->scalarFunctions.back()); - } + this->create_scalar_function_impl(/* constructAt */ [args...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, args...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create a user-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_scalar_function() { - return this->create_scalar_function>(); + template + void create_scalar_function(Args&&... args) { + return this->create_scalar_function>(std::forward(args)...); } #endif /** - * Create a user-defined aggregate function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined aggregate SQL function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * T - function class. T must have step member function, fin member function and static name function like this: * ``` @@ -15295,66 +15284,33 @@ namespace sqlite_orm { * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_aggregate_function() { + template + void create_aggregate_function(Args&&... args) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); - 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.push_back(make_udf_proxy( - std::move(name), - argsCount, - /* constructAt = */ - [](void* location) { - std::allocator allocator; - using traits = std::allocator_traits; - traits::construct(allocator, (F*)location); - }, - /* destroy = */ - obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), - /* step = */ - [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); -#if __cpp_lib_bind_front >= 201907L - std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); -#else - polyfill::apply( - [&udf](auto&&... args) { - udf.step(std::forward(args)...); - }, - std::move(argsTuple)); -#endif - }, - /* finalCall = */ - [](void* udfHandle, sqlite3_context* context) { - F& udf = *static_cast(udfHandle); - auto result = udf.fin(); - statement_binder().result(context, result); - })); - - if(this->connection->retain_count() > 0) { - sqlite3* db = this->connection->get(); - try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); - } + this->create_aggregate_function_impl(/* constructAt = */ + [args...](void* location) { + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, args...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create a user-defined aggregate function. - * Can be called at any time no matter whether the database connection is opened or no. + * Create an application-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ - template - void create_aggregate_function() { - return this->create_aggregate_function>(); + template + void create_aggregate_function(Args&&... args) { + return this->create_aggregate_function>(std::forward(args)...); } #endif @@ -15655,6 +15611,79 @@ namespace sqlite_orm { } } + template + void create_scalar_function_impl(std::function constructAt) { + std::stringstream ss; + ss << F::name() << std::flush; + auto name = ss.str(); + 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.push_back(make_udf_proxy( + std::move(name), + argsCount, + std::move(constructAt), + /* destroy = */ + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), + /* call = */ + [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { + F& udf = *static_cast(udfHandle); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(udf, std::move(argsTuple)); + statement_binder().result(context, result); + })); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_scalar_function(db, *this->scalarFunctions.back()); + } + } + + template + void create_aggregate_function_impl(std::function constructAt) { + std::stringstream ss; + ss << F::name() << std::flush; + auto name = ss.str(); + 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.push_back(make_udf_proxy( + std::move(name), + argsCount, + std::move(constructAt), + /* destroy = */ + obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), + /* step = */ + [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { + F& udf = *static_cast(udfHandle); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); +#if __cpp_lib_bind_front >= 201907L + std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); +#else + polyfill::apply( + [&udf](auto&&... args) { + udf.step(std::forward(args)...); + }, + std::move(argsTuple)); +#endif + }, + /* finalCall = */ + [](void* udfHandle, sqlite3_context* context) { + F& udf = *static_cast(udfHandle); + auto result = udf.fin(); + statement_binder().result(context, result); + })); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); + } + } + void delete_function_impl(const std::string& name, std::vector>& functions) const { #if __cpp_lib_ranges >= 201911L diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 0ac76a3f4..1b9e23f8a 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -177,7 +177,7 @@ struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedScalarFunction { } static const char* name() { - return "OVERALIGNED"; + return "OVERALIGNED1"; } }; @@ -187,16 +187,47 @@ struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedAggregateFunctio void step(double arg) { sum += arg; } - int fin() const { + double fin() const { return sum; } static const char* name() { - return "OVERALIGNED"; + return "OVERALIGNED2"; } }; #endif +struct NonDefaultCtorScalarFunction { + const int multiplier; + + NonDefaultCtorScalarFunction(int multiplier) : multiplier{multiplier} {} + + int operator()(int arg) const { + return multiplier * arg; + } + + static const char* name() { + return "CTORTEST1"; + } +}; + +struct NonDefaultCtorAggregateFunction { + int sum; + + NonDefaultCtorAggregateFunction(int initialValue) : sum{initialValue} {} + + void step(int arg) { + sum += arg; + } + int fin() const { + return sum; + } + + static const char* name() { + return "CTORTEST2"; + } +}; + TEST_CASE("custom functions") { using Catch::Matchers::ContainsSubstring; @@ -348,4 +379,20 @@ TEST_CASE("custom functions") { REQUIRE_NOTHROW(storage.delete_aggregate_function()); } #endif + + storage.create_scalar_function(42); + { + auto rows = storage.select(func(1)); + decltype(rows) expected{42}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + storage.create_aggregate_function(42); + { + auto rows = storage.select(func(1)); + decltype(rows) expected{43}; + REQUIRE(rows == expected); + } + storage.delete_aggregate_function(); } From 6715ffcf116315a3898b5b97783c5a2cd83859fc Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 18 Nov 2023 23:37:16 +0200 Subject: [PATCH 21/50] Used standard traits for deallocating user-defined function --- dev/udf_proxy.h | 8 +++++--- include/sqlite_orm/sqlite_orm.h | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index 7b81d1c2a..a08704906 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -29,7 +29,9 @@ namespace sqlite_orm { struct destruct_only_deleter { template void operator()(UDF* f) const noexcept { - f->~UDF(); + std::allocator allocator; + using traits = std::allocator_traits; + traits::destroy(allocator, f); } }; @@ -41,10 +43,10 @@ namespace sqlite_orm { final_call_fn_t finalAggregateCall; const xdestroy_fn_t udfDeallocate; - // flag whether the UDF has been udfConstructed at `udfHandle`; + // flag whether the UDF has been constructed at `udfHandle`; // necessary for aggregation operations bool udfConstructed; - // pointer to memory for UDF + // pointer to memory space for UDF void* const udfHandle; ~udf_proxy() { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ab6233fe9..d349e0028 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14867,7 +14867,9 @@ namespace sqlite_orm { struct destruct_only_deleter { template void operator()(UDF* f) const noexcept { - f->~UDF(); + std::allocator allocator; + using traits = std::allocator_traits; + traits::destroy(allocator, f); } }; @@ -14879,10 +14881,10 @@ namespace sqlite_orm { final_call_fn_t finalAggregateCall; const xdestroy_fn_t udfDeallocate; - // flag whether the UDF has been udfConstructed at `udfHandle`; + // flag whether the UDF has been constructed at `udfHandle`; // necessary for aggregation operations bool udfConstructed; - // pointer to memory for UDF + // pointer to memory space for UDF void* const udfHandle; ~udf_proxy() { From 4c83b49ffbe1c7e1e5f7d14b2a929618323b2fc7 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 19 Nov 2023 18:28:38 +0200 Subject: [PATCH 22/50] Note on thread safety and pointer provenance for a user-defined function --- dev/udf_proxy.h | 11 +++++++++++ include/sqlite_orm/sqlite_orm.h | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index a08704906..286e618fd 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -122,6 +122,10 @@ namespace sqlite_orm { return; } check_args_count(proxy, argsCount); + // Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. proxy->constructAt(proxy->udfHandle); proxy->udfConstructed = true; } @@ -134,6 +138,13 @@ namespace sqlite_orm { inline void 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); + // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory area. + // The `udf_proxy` is one instance per database connection, + // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. + // 2. Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. proxy->constructAt(proxy->udfHandle); const std::unique_ptr udfGuard{proxy->udfHandle, proxy->destroy}; proxy->func(proxy->udfHandle, context, argsCount, values); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index d349e0028..22e5540c4 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14960,6 +14960,10 @@ namespace sqlite_orm { return; } check_args_count(proxy, argsCount); + // Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. proxy->constructAt(proxy->udfHandle); proxy->udfConstructed = true; } @@ -14972,6 +14976,13 @@ namespace sqlite_orm { inline void 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); + // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory area. + // The `udf_proxy` is one instance per database connection, + // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. + // 2. Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. proxy->constructAt(proxy->udfHandle); const std::unique_ptr udfGuard{proxy->udfHandle, proxy->destroy}; proxy->func(proxy->udfHandle, context, argsCount, values); From a367296cddfa4efb3740b56afc9c3a4a19ff8162 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 19 Nov 2023 18:43:09 +0200 Subject: [PATCH 23/50] Renamed parameter name from `args` to `constructorArgs` --- dev/storage_base.h | 26 ++++++++++++++------------ include/sqlite_orm/sqlite_orm.h | 26 ++++++++++++++------------ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 55bdd31e2..1f69b2458 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -261,13 +261,13 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_scalar_function(Args&&... args) { + void create_scalar_function(Args&&... constructorArgs) { static_assert(is_scalar_udf_v, "F must be a scalar function"); - this->create_scalar_function_impl(/* constructAt */ [args...](void* location) { + this->create_scalar_function_impl(/* constructAt */ [constructorArgs...](void* location) { std::allocator allocator; using traits = std::allocator_traits; - traits::construct(allocator, (F*)location, args...); + traits::construct(allocator, (F*)location, constructorArgs...); }); } @@ -284,8 +284,8 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_scalar_function(Args&&... args) { - return this->create_scalar_function>(std::forward(args)...); + void create_scalar_function(Args&&... constructorArgs) { + return this->create_scalar_function>(std::forward(constructorArgs)...); } #endif @@ -293,7 +293,7 @@ namespace sqlite_orm { * Create an application-defined aggregate SQL function. * Can be called at any time no matter whether the database connection is opened or not. * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, * together with a copy of the passed initialization arguments. * An instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. @@ -322,14 +322,16 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_aggregate_function(Args&&... args) { + void create_aggregate_function(Args&&... constructorArgs) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); this->create_aggregate_function_impl(/* constructAt = */ - [args...](void* location) { + [constructorArgs...](void* location) { std::allocator allocator; using traits = std::allocator_traits; - traits::construct(allocator, (F*)location, args...); + traits::construct(allocator, + (F*)location, + constructorArgs...); }); } @@ -338,7 +340,7 @@ namespace sqlite_orm { * Create an application-defined aggregate function. * Can be called at any time no matter whether the database connection is opened or not. * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, * together with a copy of the passed initialization arguments. * An instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. @@ -346,8 +348,8 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_aggregate_function(Args&&... args) { - return this->create_aggregate_function>(std::forward(args)...); + void create_aggregate_function(Args&&... constructorArgs) { + return this->create_aggregate_function>(std::forward(constructorArgs)...); } #endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 22e5540c4..4ba0e6de8 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15237,13 +15237,13 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_scalar_function(Args&&... args) { + void create_scalar_function(Args&&... constructorArgs) { static_assert(is_scalar_udf_v, "F must be a scalar function"); - this->create_scalar_function_impl(/* constructAt */ [args...](void* location) { + this->create_scalar_function_impl(/* constructAt */ [constructorArgs...](void* location) { std::allocator allocator; using traits = std::allocator_traits; - traits::construct(allocator, (F*)location, args...); + traits::construct(allocator, (F*)location, constructorArgs...); }); } @@ -15260,8 +15260,8 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_scalar_function(Args&&... args) { - return this->create_scalar_function>(std::forward(args)...); + void create_scalar_function(Args&&... constructorArgs) { + return this->create_scalar_function>(std::forward(constructorArgs)...); } #endif @@ -15269,7 +15269,7 @@ namespace sqlite_orm { * Create an application-defined aggregate SQL function. * Can be called at any time no matter whether the database connection is opened or not. * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, * together with a copy of the passed initialization arguments. * An instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. @@ -15298,14 +15298,16 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_aggregate_function(Args&&... args) { + void create_aggregate_function(Args&&... constructorArgs) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); this->create_aggregate_function_impl(/* constructAt = */ - [args...](void* location) { + [constructorArgs...](void* location) { std::allocator allocator; using traits = std::allocator_traits; - traits::construct(allocator, (F*)location, args...); + traits::construct(allocator, + (F*)location, + constructorArgs...); }); } @@ -15314,7 +15316,7 @@ namespace sqlite_orm { * Create an application-defined aggregate function. * Can be called at any time no matter whether the database connection is opened or not. * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the scalar function object, + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, * together with a copy of the passed initialization arguments. * An instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. @@ -15322,8 +15324,8 @@ namespace sqlite_orm { * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template - void create_aggregate_function(Args&&... args) { - return this->create_aggregate_function>(std::forward(args)...); + void create_aggregate_function(Args&&... constructorArgs) { + return this->create_aggregate_function>(std::forward(constructorArgs)...); } #endif From ebb20d81aae40351952ec9b869251397dfdb71b5 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 19 Nov 2023 20:06:26 +0200 Subject: [PATCH 24/50] Added missing include directive to dev/values_to_tuple.h --- dev/values_to_tuple.h | 1 + include/sqlite_orm/sqlite_orm.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dev/values_to_tuple.h b/dev/values_to_tuple.h index c63a5611d..b4b338848 100644 --- a/dev/values_to_tuple.h +++ b/dev/values_to_tuple.h @@ -5,6 +5,7 @@ #include // std::tuple, std::tuple_size, std::tuple_element #include "functional/cxx_universal.h" // ::size_t +#include "functional/cxx_functional_polyfill.h" #include "row_extractor.h" #include "arg_values.h" diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 4ba0e6de8..1ceb6d247 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14647,6 +14647,8 @@ namespace sqlite_orm { // #include "functional/cxx_universal.h" // ::size_t +// #include "functional/cxx_functional_polyfill.h" + // #include "row_extractor.h" // #include "arg_values.h" From f83e1311657af0d6e5ae66c663ebbf10c84e9a92 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 19 Nov 2023 20:09:59 +0200 Subject: [PATCH 25/50] Declared `udf_proxy` non-copyable --- dev/udf_proxy.h | 7 +++++-- include/sqlite_orm/sqlite_orm.h | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index 286e618fd..b40e04ca1 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -1,7 +1,7 @@ #pragma once #include -#include // std::allocator +#include // std::allocator, std::allocator_traits, std::unique_ptr #include // std::string #include // std::function #include // std::move, std::pair @@ -52,6 +52,9 @@ namespace sqlite_orm { ~udf_proxy() { udfDeallocate(udfHandle); } + + udf_proxy(const udf_proxy&) = delete; + udf_proxy& operator=(const udf_proxy&) = delete; }; template @@ -138,7 +141,7 @@ namespace sqlite_orm { inline void 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); - // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory area. + // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory space. // The `udf_proxy` is one instance per database connection, // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. // 2. Note on the use of the `udfHandle` pointer after the object construction: diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 1ceb6d247..c8e48a2a2 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14841,7 +14841,7 @@ namespace sqlite_orm { // #include "udf_proxy.h" #include -#include // std::allocator +#include // std::allocator, std::allocator_traits, std::unique_ptr #include // std::string #include // std::function #include // std::move, std::pair @@ -14892,6 +14892,9 @@ namespace sqlite_orm { ~udf_proxy() { udfDeallocate(udfHandle); } + + udf_proxy(const udf_proxy&) = delete; + udf_proxy& operator=(const udf_proxy&) = delete; }; template @@ -14978,7 +14981,7 @@ namespace sqlite_orm { inline void 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); - // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory area. + // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory space. // The `udf_proxy` is one instance per database connection, // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. // 2. Note on the use of the `udfHandle` pointer after the object construction: From 31f18b0a5f206306debfa5bc20306122d9f79b17 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 19 Nov 2023 21:28:46 +0200 Subject: [PATCH 26/50] Made `udf_proxy` non-copyable again, otherwise it's not an aggregate --- dev/udf_proxy.h | 3 --- include/sqlite_orm/sqlite_orm.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index b40e04ca1..d2c400954 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -52,9 +52,6 @@ namespace sqlite_orm { ~udf_proxy() { udfDeallocate(udfHandle); } - - udf_proxy(const udf_proxy&) = delete; - udf_proxy& operator=(const udf_proxy&) = delete; }; template diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index c8e48a2a2..f16cf6bfb 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14892,9 +14892,6 @@ namespace sqlite_orm { ~udf_proxy() { udfDeallocate(udfHandle); } - - udf_proxy(const udf_proxy&) = delete; - udf_proxy& operator=(const udf_proxy&) = delete; }; template From 4313bcdef0ffd5bd25b70df8eb641c7d3bdd8d27 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 20 Nov 2023 00:11:26 +0200 Subject: [PATCH 27/50] Keep `udf_proxy` in a list instead of a vector --- dev/storage_base.h | 31 ++++---- dev/udf_proxy.h | 105 +++++++++++------------- include/sqlite_orm/sqlite_orm.h | 136 ++++++++++++++------------------ 3 files changed, 122 insertions(+), 150 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 1f69b2458..46c682f26 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -8,6 +8,7 @@ #include // std::move #include // std::system_error #include // std::vector +#include // std::list #include // std::make_unique, std::unique_ptr #include // std::map #include // std::is_same @@ -638,11 +639,11 @@ namespace sqlite_orm { } for(auto& udfProxy: this->scalarFunctions) { - try_to_create_scalar_function(db, *udfProxy); + try_to_create_scalar_function(db, udfProxy); } for(auto& udfProxy: this->aggregateFunctions) { - try_to_create_aggregate_function(db, *udfProxy); + try_to_create_aggregate_function(db, udfProxy); } if(this->on_open) { @@ -660,7 +661,7 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(make_udf_proxy( + this->scalarFunctions.emplace_back( std::move(name), argsCount, std::move(constructAt), @@ -672,11 +673,13 @@ namespace sqlite_orm { args_tuple argsTuple = tuple_from_values{}(values, argsCount); auto result = polyfill::apply(udf, std::move(argsTuple)); statement_binder().result(context, result); - })); + }, + nullptr, + allocate_udf_storage()); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_scalar_function(db, *this->scalarFunctions.back()); + try_to_create_scalar_function(db, this->scalarFunctions.back()); } } @@ -690,7 +693,7 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(make_udf_proxy( + this->aggregateFunctions.emplace_back( std::move(name), argsCount, std::move(constructAt), @@ -715,21 +718,21 @@ namespace sqlite_orm { F& udf = *static_cast(udfHandle); auto result = udf.fin(); statement_binder().result(context, result); - })); + }, + allocate_udf_storage()); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); + try_to_create_aggregate_function(db, this->aggregateFunctions.back()); } } - void delete_function_impl(const std::string& name, - std::vector>& functions) const { + void delete_function_impl(const std::string& name, std::list& functions) const { #if __cpp_lib_ranges >= 201911L auto it = std::ranges::find(functions, name, &udf_proxy::name); #else auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { - return udfProxy->name == name; + return udfProxy.name == name; }); #endif if(it != functions.end()) { @@ -737,7 +740,7 @@ namespace sqlite_orm { sqlite3* db = this->connection->get(); int rc = sqlite3_create_function_v2(db, name.c_str(), - (*it)->argumentsCount, + it->argumentsCount, SQLITE_UTF8, nullptr, nullptr, @@ -871,8 +874,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::list scalarFunctions; + std::list aggregateFunctions; }; } } diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index d2c400954..d97f7a985 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -11,7 +11,24 @@ namespace sqlite_orm { namespace internal { /* - * Stores type-erased information in relation to a user-defined scalar or aggregate function object: + * Returns allocated memory space for the specified application-defined function + * paired with a deallocation function. + */ + template + std::pair allocate_udf_storage() { + std::allocator allocator; + using traits = std::allocator_traits; + + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::deallocate(allocator, (UDF*)location, 1); + }; + return {traits::allocate(allocator, 1), deallocate}; + } + + /* + * Stores type-erased information in relation to a application-defined scalar or aggregate function object: * - name and argument count * - function pointers for construction/destruction * - function dispatch @@ -25,6 +42,7 @@ namespace sqlite_orm { int argsCount, sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + using memory_space = std::pair; struct destruct_only_deleter { template @@ -42,68 +60,33 @@ namespace sqlite_orm { func_call_fn_t func; final_call_fn_t finalAggregateCall; - const xdestroy_fn_t udfDeallocate; // flag whether the UDF has been constructed at `udfHandle`; // necessary for aggregation operations bool udfConstructed; // pointer to memory space for UDF - void* const udfHandle; + const memory_space udfMemory; + + udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t func, + final_call_fn_t finalAggregateCall, + memory_space udfMemory) : + name{std::move(name)}, + argumentsCount{argumentsCount}, constructAt{std::move(constructAt)}, destroy{destroy}, func{func}, + finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { - udfDeallocate(udfHandle); + udfMemory.second(udfMemory.first); } - }; - template - std::pair create_udf_storage() { - std::allocator allocator; - using traits = std::allocator_traits; - - SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { - std::allocator allocator; - using traits = std::allocator_traits; - traits::deallocate(allocator, (UDF*)location, 1); - }; - return {traits::allocate(allocator, 1), deallocate}; - } - - template - std::unique_ptr make_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - udf_proxy::func_call_fn_t run) { - auto p = create_udf_storage(); - std::unique_ptr proxy{new udf_proxy{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - run, - nullptr, - p.second, - false, - p.first}}; - return proxy; - } + friend void* udfHandle(udf_proxy* proxy) { + return proxy->udfMemory.first; + } - template - std::unique_ptr make_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - udf_proxy::func_call_fn_t step, - udf_proxy::final_call_fn_t finalCall) { - auto p = create_udf_storage(); - std::unique_ptr proxy{new udf_proxy{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - step, - finalCall, - p.second, - false, - p.first}}; - return proxy; + udf_proxy(const udf_proxy&) = delete; + udf_proxy& operator=(const udf_proxy&) = delete; }; inline void check_args_count(const udf_proxy* proxy, int argsCount) { @@ -126,13 +109,13 @@ namespace sqlite_orm { // since we only ever cast between void* and UDF* pointer types and // only use the memory space for one type during the entire lifetime of a proxy, // we can use `udfHandle` interconvertibly without laundering its provenance. - proxy->constructAt(proxy->udfHandle); + proxy->constructAt(udfHandle(proxy)); proxy->udfConstructed = true; } inline void destruct_udf(udf_proxy* proxy) { proxy->udfConstructed = false; - proxy->destroy(proxy->udfHandle); + proxy->destroy(udfHandle(proxy)); } inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { @@ -145,22 +128,22 @@ namespace sqlite_orm { // since we only ever cast between void* and UDF* pointer types and // only use the memory space for one type during the entire lifetime of a proxy, // we can use `udfHandle` interconvertibly without laundering its provenance. - proxy->constructAt(proxy->udfHandle); - const std::unique_ptr udfGuard{proxy->udfHandle, proxy->destroy}; - proxy->func(proxy->udfHandle, context, argsCount, values); + proxy->constructAt(udfHandle(proxy)); + const std::unique_ptr udfGuard{udfHandle(proxy), proxy->destroy}; + 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); - proxy->func(proxy->udfHandle, context, argsCount, values); + proxy->func(udfHandle(proxy), context, argsCount, values); } inline void aggregate_function_final_callback(sqlite3_context* context) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); // note: it is possible that the 'step' function was never called ensure_udf(proxy, -1); - proxy->finalAggregateCall(proxy->udfHandle, context); + proxy->finalAggregateCall(udfHandle(proxy), context); destruct_udf(proxy); } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index f16cf6bfb..da248d065 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -13636,6 +13636,7 @@ namespace sqlite_orm { #include // std::move #include // std::system_error #include // std::vector +#include // std::list #include // std::make_unique, std::unique_ptr #include // std::map #include // std::is_same @@ -14851,7 +14852,24 @@ namespace sqlite_orm { namespace sqlite_orm { namespace internal { /* - * Stores type-erased information in relation to a user-defined scalar or aggregate function object: + * Returns allocated memory space for the specified application-defined function + * paired with a deallocation function. + */ + template + std::pair allocate_udf_storage() { + std::allocator allocator; + using traits = std::allocator_traits; + + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::deallocate(allocator, (UDF*)location, 1); + }; + return {traits::allocate(allocator, 1), deallocate}; + } + + /* + * Stores type-erased information in relation to a application-defined scalar or aggregate function object: * - name and argument count * - function pointers for construction/destruction * - function dispatch @@ -14865,6 +14883,7 @@ namespace sqlite_orm { int argsCount, sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + using memory_space = std::pair; struct destruct_only_deleter { template @@ -14882,68 +14901,33 @@ namespace sqlite_orm { func_call_fn_t func; final_call_fn_t finalAggregateCall; - const xdestroy_fn_t udfDeallocate; // flag whether the UDF has been constructed at `udfHandle`; // necessary for aggregation operations bool udfConstructed; // pointer to memory space for UDF - void* const udfHandle; + const memory_space udfMemory; + + udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + func_call_fn_t func, + final_call_fn_t finalAggregateCall, + memory_space udfMemory) : + name{std::move(name)}, + argumentsCount{argumentsCount}, constructAt{std::move(constructAt)}, destroy{destroy}, func{func}, + finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { - udfDeallocate(udfHandle); + udfMemory.second(udfMemory.first); } - }; - - template - std::pair create_udf_storage() { - std::allocator allocator; - using traits = std::allocator_traits; - - SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { - std::allocator allocator; - using traits = std::allocator_traits; - traits::deallocate(allocator, (UDF*)location, 1); - }; - return {traits::allocate(allocator, 1), deallocate}; - } - template - std::unique_ptr make_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - udf_proxy::func_call_fn_t run) { - auto p = create_udf_storage(); - std::unique_ptr proxy{new udf_proxy{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - run, - nullptr, - p.second, - false, - p.first}}; - return proxy; - } + friend void* udfHandle(udf_proxy* proxy) { + return proxy->udfMemory.first; + } - template - std::unique_ptr make_udf_proxy(std::string name, - int argumentsCount, - std::function constructAt, - xdestroy_fn_t destroy, - udf_proxy::func_call_fn_t step, - udf_proxy::final_call_fn_t finalCall) { - auto p = create_udf_storage(); - std::unique_ptr proxy{new udf_proxy{std::move(name), - argumentsCount, - std::move(constructAt), - destroy, - step, - finalCall, - p.second, - false, - p.first}}; - return proxy; + udf_proxy(const udf_proxy&) = delete; + udf_proxy& operator=(const udf_proxy&) = delete; }; inline void check_args_count(const udf_proxy* proxy, int argsCount) { @@ -14966,13 +14950,13 @@ namespace sqlite_orm { // since we only ever cast between void* and UDF* pointer types and // only use the memory space for one type during the entire lifetime of a proxy, // we can use `udfHandle` interconvertibly without laundering its provenance. - proxy->constructAt(proxy->udfHandle); + proxy->constructAt(udfHandle(proxy)); proxy->udfConstructed = true; } inline void destruct_udf(udf_proxy* proxy) { proxy->udfConstructed = false; - proxy->destroy(proxy->udfHandle); + proxy->destroy(udfHandle(proxy)); } inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { @@ -14985,22 +14969,22 @@ namespace sqlite_orm { // since we only ever cast between void* and UDF* pointer types and // only use the memory space for one type during the entire lifetime of a proxy, // we can use `udfHandle` interconvertibly without laundering its provenance. - proxy->constructAt(proxy->udfHandle); - const std::unique_ptr udfGuard{proxy->udfHandle, proxy->destroy}; - proxy->func(proxy->udfHandle, context, argsCount, values); + proxy->constructAt(udfHandle(proxy)); + const std::unique_ptr udfGuard{udfHandle(proxy), proxy->destroy}; + 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); - proxy->func(proxy->udfHandle, context, argsCount, values); + proxy->func(udfHandle(proxy), context, argsCount, values); } inline void aggregate_function_final_callback(sqlite3_context* context) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); // note: it is possible that the 'step' function was never called ensure_udf(proxy, -1); - proxy->finalAggregateCall(proxy->udfHandle, context); + proxy->finalAggregateCall(udfHandle(proxy), context); destruct_udf(proxy); } } @@ -15616,11 +15600,11 @@ namespace sqlite_orm { } for(auto& udfProxy: this->scalarFunctions) { - try_to_create_scalar_function(db, *udfProxy); + try_to_create_scalar_function(db, udfProxy); } for(auto& udfProxy: this->aggregateFunctions) { - try_to_create_aggregate_function(db, *udfProxy); + try_to_create_aggregate_function(db, udfProxy); } if(this->on_open) { @@ -15638,7 +15622,7 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.push_back(make_udf_proxy( + this->scalarFunctions.emplace_back( std::move(name), argsCount, std::move(constructAt), @@ -15650,11 +15634,13 @@ namespace sqlite_orm { args_tuple argsTuple = tuple_from_values{}(values, argsCount); auto result = polyfill::apply(udf, std::move(argsTuple)); statement_binder().result(context, result); - })); + }, + nullptr, + allocate_udf_storage()); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_scalar_function(db, *this->scalarFunctions.back()); + try_to_create_scalar_function(db, this->scalarFunctions.back()); } } @@ -15668,7 +15654,7 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->aggregateFunctions.push_back(make_udf_proxy( + this->aggregateFunctions.emplace_back( std::move(name), argsCount, std::move(constructAt), @@ -15693,21 +15679,21 @@ namespace sqlite_orm { F& udf = *static_cast(udfHandle); auto result = udf.fin(); statement_binder().result(context, result); - })); + }, + allocate_udf_storage()); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_aggregate_function(db, *this->aggregateFunctions.back()); + try_to_create_aggregate_function(db, this->aggregateFunctions.back()); } } - void delete_function_impl(const std::string& name, - std::vector>& functions) const { + void delete_function_impl(const std::string& name, std::list& functions) const { #if __cpp_lib_ranges >= 201911L auto it = std::ranges::find(functions, name, &udf_proxy::name); #else auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { - return udfProxy->name == name; + return udfProxy.name == name; }); #endif if(it != functions.end()) { @@ -15715,7 +15701,7 @@ namespace sqlite_orm { sqlite3* db = this->connection->get(); int rc = sqlite3_create_function_v2(db, name.c_str(), - (*it)->argumentsCount, + it->argumentsCount, SQLITE_UTF8, nullptr, nullptr, @@ -15849,8 +15835,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::list scalarFunctions; + std::list aggregateFunctions; }; } } From 7833a70ce1204164bb0bdaff8dfedfa02b57860a Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 20 Nov 2023 20:34:47 +0200 Subject: [PATCH 28/50] Collected function traits in a separate header file --- dev/ast_iterator.h | 8 +- dev/function.h | 44 +++---- dev/functional/function_traits.h | 63 ++++++++++ dev/statement_serializer.h | 8 +- dev/storage_base.h | 1 + include/sqlite_orm/sqlite_orm.h | 126 +++++++++++++------ tests/static_tests/function_static_tests.cpp | 12 +- 7 files changed, 180 insertions(+), 82 deletions(-) create mode 100644 dev/functional/function_traits.h diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index 047d468ce..e36a9bab4 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -479,13 +479,13 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = function_call; + template + struct ast_iterator, void> { + using node_type = function_call; template void operator()(const node_type& f, L& lambda) const { - iterate_ast(f.args, lambda); + iterate_ast(f.callArgs, lambda); } }; diff --git a/dev/function.h b/dev/function.h index bb525121a..747860aea 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/function_traits.h" #include "tags.h" namespace sqlite_orm { @@ -44,47 +45,30 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; - template - struct member_function_arguments; - - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...) const; - using tuple_type = std::tuple...>; - using return_type = R; - }; - - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...); - using tuple_type = std::tuple...>; - using return_type = R; - }; - template struct callable_arguments_impl; template struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; template struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; template struct callable_arguments : callable_arguments_impl {}; - template + template struct function_call { using udf_type = UDF; - using args_tuple = std::tuple; + using args_tuple = std::tuple; - args_tuple args; + args_tuple callArgs; }; template @@ -95,8 +79,8 @@ namespace sqlite_orm { struct unpacked_arg { using type = T; }; - template - struct unpacked_arg> { + template + struct unpacked_arg> { using type = typename callable_arguments::return_type; }; template @@ -185,9 +169,9 @@ namespace sqlite_orm { */ template struct function : polyfill::type_identity { - template - function_call operator()(Args... args) const { - using args_tuple = std::tuple; + 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; @@ -197,7 +181,7 @@ namespace sqlite_orm { polyfill::index_constant{})) || std::is_same>::value, "The number of arguments does not match"); - return {{std::forward(args)...}}; + return {{std::forward(callArgs)...}}; } }; } diff --git a/dev/functional/function_traits.h b/dev/functional/function_traits.h new file mode 100644 index 000000000..5668001b3 --- /dev/null +++ b/dev/functional/function_traits.h @@ -0,0 +1,63 @@ +#pragma once +#include "cxx_type_traits_polyfill.h" +#include "mpl.h" + +namespace sqlite_orm { + namespace internal { + template + struct function_traits; + + /* + * A function's return type + */ + template + using function_return_type_t = typename function_traits::return_type; + + /* + * A function's arguments tuple + */ + template + class Tuple, + template class ProjectOp = polyfill::type_identity_t> + using function_arguments = typename function_traits::template arguments_tuple; + + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + */ + template + struct function_traits { + using return_type = R; + + template class Tuple, template class ProjectOp> + using arguments_tuple = Tuple...>; + }; + + // non-exhaustive partial specializations of `function_traits` + + template + struct function_traits : function_traits {}; + +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct function_traits : function_traits {}; + + template + struct function_traits : function_traits {}; +#endif + + /* + * Pick signature of function pointer + */ + template + struct function_traits : function_traits {}; + + /* + * Pick signature of pointer-to-member function + */ + template + struct function_traits : function_traits {}; + } +} diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index dc16046a5..dc13a87b4 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -363,14 +363,14 @@ namespace sqlite_orm { struct statement_serializer, void> : statement_serializer, void> {}; - template - struct statement_serializer, void> { - using statement_type = function_call; + template + struct statement_serializer, void> { + using statement_type = function_call; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << F::name() << "(" << streaming_expressions_tuple(statement.args, context) << ")"; + ss << F::name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; diff --git a/dev/storage_base.h b/dev/storage_base.h index 46c682f26..a84c37515 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -674,6 +674,7 @@ namespace sqlite_orm { auto result = polyfill::apply(udf, std::move(argsTuple)); statement_binder().result(context, result); }, + /* finalCall = */ nullptr, allocate_udf_storage()); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index da248d065..465dc02f2 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10883,6 +10883,72 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" +// #include "functional/function_traits.h" + +// #include "cxx_type_traits_polyfill.h" + +// #include "mpl.h" + +namespace sqlite_orm { + namespace internal { + template + struct function_traits; + + /* + * A function's return type + */ + template + using function_return_type_t = typename function_traits::return_type; + + /* + * A function's arguments tuple + */ + template + class Tuple, + template class ProjectOp = polyfill::type_identity_t> + using function_arguments = typename function_traits::template arguments_tuple; + + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + */ + template + struct function_traits { + using return_type = R; + + template class Tuple, template class ProjectOp> + using arguments_tuple = Tuple...>; + }; + + // non-exhaustive partial specializations of `function_traits` + + template + struct function_traits : function_traits {}; + +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct function_traits : function_traits {}; + + template + struct function_traits : function_traits {}; +#endif + + /* + * Pick signature of function pointer + */ + template + struct function_traits : function_traits {}; + + /* + * Pick signature of pointer-to-member function + */ + template + struct function_traits : function_traits {}; + } +} + // #include "tags.h" namespace sqlite_orm { @@ -10920,47 +10986,30 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; - template - struct member_function_arguments; - - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...) const; - using tuple_type = std::tuple...>; - using return_type = R; - }; - - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...); - using tuple_type = std::tuple...>; - using return_type = R; - }; - template struct callable_arguments_impl; template struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; template struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; template struct callable_arguments : callable_arguments_impl {}; - template + template struct function_call { using udf_type = UDF; - using args_tuple = std::tuple; + using args_tuple = std::tuple; - args_tuple args; + args_tuple callArgs; }; template @@ -10971,8 +11020,8 @@ namespace sqlite_orm { struct unpacked_arg { using type = T; }; - template - struct unpacked_arg> { + template + struct unpacked_arg> { using type = typename callable_arguments::return_type; }; template @@ -11061,9 +11110,9 @@ namespace sqlite_orm { */ template struct function : polyfill::type_identity { - template - function_call operator()(Args... args) const { - using args_tuple = std::tuple; + 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; @@ -11073,7 +11122,7 @@ namespace sqlite_orm { polyfill::index_constant{})) || std::is_same>::value, "The number of arguments does not match"); - return {{std::forward(args)...}}; + return {{std::forward(callArgs)...}}; } }; } @@ -13341,13 +13390,13 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = function_call; + template + struct ast_iterator, void> { + using node_type = function_call; template void operator()(const node_type& f, L& lambda) const { - iterate_ast(f.args, lambda); + iterate_ast(f.callArgs, lambda); } }; @@ -15635,6 +15684,7 @@ namespace sqlite_orm { auto result = polyfill::apply(udf, std::move(argsTuple)); statement_binder().result(context, result); }, + /* finalCall = */ nullptr, allocate_udf_storage()); @@ -16592,14 +16642,14 @@ namespace sqlite_orm { struct statement_serializer, void> : statement_serializer, void> {}; - template - struct statement_serializer, void> { - using statement_type = function_call; + template + struct statement_serializer, void> { + using statement_type = function_call; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << F::name() << "(" << streaming_expressions_tuple(statement.args, context) << ")"; + ss << F::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 f29092cb4..da78a04d9 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -58,7 +58,7 @@ TEST_CASE("function static") { using ExpectedType = double (Function::*)(double) const; STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); @@ -80,7 +80,7 @@ TEST_CASE("function static") { using ExpectedType = double (Function::*)(double); STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); @@ -102,7 +102,7 @@ TEST_CASE("function static") { using ExpectedType = int (Function::*)(std::string) const; STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); @@ -124,7 +124,7 @@ TEST_CASE("function static") { using ExpectedType = int (Function::*)(std::string); STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); @@ -146,7 +146,7 @@ TEST_CASE("function static") { using ExpectedType = std::string (Function::*)(const std::string&, const std::string&) const; STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); @@ -168,7 +168,7 @@ TEST_CASE("function static") { using ExpectedType = std::string (Function::*)(const std::string&, const std::string&); STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); From a44d14680b72d1bc27b683275f5e708cc1e2c540 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 22 Nov 2023 01:36:05 +0200 Subject: [PATCH 29/50] 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 30/50] 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 31/50] 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 32/50] 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 33/50] 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 34/50] 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 35/50] 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 36/50] 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 37/50] 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 38/50] 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 39/50] 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 40/50] 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 41/50] 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); From 65e2c4ad1d93e91bbd44d7b5f42867210346285f Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 11:26:56 +0200 Subject: [PATCH 42/50] Create a stateless user-defined function only once --- dev/functional/config.h | 6 ++ dev/storage_base.h | 37 +++++--- dev/tuple_helper/tuple_iteration.h | 6 +- dev/udf_proxy.h | 5 + include/sqlite_orm/sqlite_orm.h | 54 +++++++---- tests/user_defined_functions.cpp | 147 +++++++++++++++++++---------- 6 files changed, 169 insertions(+), 86 deletions(-) diff --git a/dev/functional/config.h b/dev/functional/config.h index 2c88944ca..bdfe8ca58 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -18,6 +18,12 @@ #define SQLITE_ORM_INLINE_VAR #endif +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_IF constexpr +#else +#define SQLITE_ORM_CONSTEXPR_IF +#endif + #if __cpp_lib_constexpr_functional >= 201907L #define SQLITE_ORM_CONSTEXPR_CPP20 constexpr #else diff --git a/dev/storage_base.h b/dev/storage_base.h index 067b3123d..5db8e039e 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -242,7 +242,8 @@ namespace sqlite_orm { * * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. * * T - function class. T must have operator() overload and static name function like this: @@ -280,7 +281,8 @@ namespace sqlite_orm { * * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. @@ -709,10 +711,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); + constexpr bool isStateless = std::is_empty::value; + auto udfStorage = allocate_udf_storage(); + if SQLITE_ORM_CONSTEXPR_IF(isStateless) { + constructAt(udfStorage.first); + } this->scalarFunctions.emplace_back( udfName(), argsCount, - std::move(constructAt), + isStateless ? nullptr : std::move(constructAt), /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ @@ -724,7 +731,7 @@ namespace sqlite_orm { }, /* finalCall = */ nullptr, - allocate_udf_storage()); + udfStorage); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); @@ -805,16 +812,18 @@ namespace sqlite_orm { } static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { - int rc = sqlite3_create_function_v2(db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - udfProxy.constructAt ? scalar_function_callback - : quoted_scalar_function_callback, - nullptr, - nullptr, - nullptr); + int rc = sqlite3_create_function_v2( + db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + !udfProxy.constructAt && !udfProxy.destroy ? quoted_scalar_function_callback + : !udfProxy.constructAt && udfProxy.destroy ? stateless_scalar_function_callback + : scalar_function_callback, + nullptr, + nullptr, + nullptr); if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } diff --git a/dev/tuple_helper/tuple_iteration.h b/dev/tuple_helper/tuple_iteration.h index 7ce9c1a7b..9fd10015a 100644 --- a/dev/tuple_helper/tuple_iteration.h +++ b/dev/tuple_helper/tuple_iteration.h @@ -25,11 +25,7 @@ namespace sqlite_orm { template void iterate_tuple(Tpl& tpl, std::index_sequence, L&& lambda) { -#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED - if constexpr(reversed) { -#else - if(reversed) { -#endif + if SQLITE_ORM_CONSTEXPR_IF(reversed) { iterate_tuple(tpl, std::index_sequence{}, std::forward(lambda)); lambda(std::get(tpl)); } else { diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index 591c7f3a9..2a474ede6 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -78,6 +78,9 @@ namespace sqlite_orm { finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { + if(!constructAt && destroy) { + destroy(udfMemory.first); + } if(udfMemory.second) { udfMemory.second(udfMemory.first); } @@ -141,6 +144,8 @@ namespace sqlite_orm { proxy->func(udfHandle(proxy), context, argsCount, values); } + SQLITE_ORM_INLINE_VAR constexpr auto stateless_scalar_function_callback = quoted_scalar_function_callback; + 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 089babca4..8983bfcdc 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -183,6 +183,12 @@ using std::nullptr_t; #define SQLITE_ORM_INLINE_VAR #endif +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_IF constexpr +#else +#define SQLITE_ORM_CONSTEXPR_IF +#endif + #if __cpp_lib_constexpr_functional >= 201907L #define SQLITE_ORM_CONSTEXPR_CPP20 constexpr #else @@ -7134,11 +7140,7 @@ namespace sqlite_orm { template void iterate_tuple(Tpl& tpl, std::index_sequence, L&& lambda) { -#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED - if constexpr(reversed) { -#else - if(reversed) { -#endif + if SQLITE_ORM_CONSTEXPR_IF(reversed) { iterate_tuple(tpl, std::index_sequence{}, std::forward(lambda)); lambda(std::get(tpl)); } else { @@ -15290,6 +15292,9 @@ namespace sqlite_orm { finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { + if(!constructAt && destroy) { + destroy(udfMemory.first); + } if(udfMemory.second) { udfMemory.second(udfMemory.first); } @@ -15353,6 +15358,8 @@ namespace sqlite_orm { proxy->func(udfHandle(proxy), context, argsCount, values); } + SQLITE_ORM_INLINE_VAR constexpr auto stateless_scalar_function_callback = quoted_scalar_function_callback; + 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); @@ -15582,7 +15589,8 @@ namespace sqlite_orm { * * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. * * T - function class. T must have operator() overload and static name function like this: @@ -15620,7 +15628,8 @@ namespace sqlite_orm { * * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. * * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. @@ -16049,10 +16058,15 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); + constexpr bool isStateless = std::is_empty::value; + auto udfStorage = allocate_udf_storage(); + if SQLITE_ORM_CONSTEXPR_IF(isStateless) { + constructAt(udfStorage.first); + } this->scalarFunctions.emplace_back( udfName(), argsCount, - std::move(constructAt), + isStateless ? nullptr : std::move(constructAt), /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ @@ -16064,7 +16078,7 @@ namespace sqlite_orm { }, /* finalCall = */ nullptr, - allocate_udf_storage()); + udfStorage); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); @@ -16145,16 +16159,18 @@ namespace sqlite_orm { } static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { - int rc = sqlite3_create_function_v2(db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - udfProxy.constructAt ? scalar_function_callback - : quoted_scalar_function_callback, - nullptr, - nullptr, - nullptr); + int rc = sqlite3_create_function_v2( + db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + !udfProxy.constructAt && !udfProxy.destroy ? quoted_scalar_function_callback + : !udfProxy.constructAt && udfProxy.destroy ? stateless_scalar_function_callback + : scalar_function_callback, + nullptr, + nullptr, + nullptr); if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index c951537bb..0f56a5af9 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -18,28 +18,52 @@ struct SqrtFunction { int SqrtFunction::callsCount = 0; -struct HasPrefixFunction { +struct StatelessHasPrefixFunction { static int callsCount; static int objectsCount; - HasPrefixFunction() { + StatelessHasPrefixFunction() { ++objectsCount; } - HasPrefixFunction(const HasPrefixFunction&) { - ++objectsCount; + StatelessHasPrefixFunction(const StatelessHasPrefixFunction&) = delete; + + ~StatelessHasPrefixFunction() { + --objectsCount; } - HasPrefixFunction(HasPrefixFunction&&) { - ++objectsCount; + bool operator()(const std::string& str, const std::string& prefix) { + ++callsCount; + return str.compare(0, prefix.size(), prefix) == 0; + } + + static std::string name() { + return "STATELESS_HAS_PREFIX"; + } +}; + +int StatelessHasPrefixFunction::callsCount = 0; +int StatelessHasPrefixFunction::objectsCount = 0; + +struct HasPrefixFunction { + static int callsCount; + static int objectsCount; + + int& staticCallsCount; + int& staticObjectsCount; + + HasPrefixFunction() : staticCallsCount{callsCount}, staticObjectsCount{objectsCount} { + ++staticObjectsCount; } + HasPrefixFunction(const HasPrefixFunction&) = delete; + ~HasPrefixFunction() { - --objectsCount; + --staticObjectsCount; } bool operator()(const std::string& str, const std::string& prefix) { - ++callsCount; + ++staticCallsCount; return str.compare(0, prefix.size(), prefix) == 0; } @@ -61,13 +85,7 @@ struct MeanFunction { ++objectsCount; } - MeanFunction(const MeanFunction&) { - ++objectsCount; - } - - MeanFunction(MeanFunction&&) { - ++objectsCount; - } + MeanFunction(const MeanFunction&) = delete; ~MeanFunction() { --objectsCount; @@ -93,24 +111,21 @@ struct FirstFunction { static int objectsCount; static int callsCount; - FirstFunction() { - ++objectsCount; - } + int& staticObjectsCount; + int& staticCallsCount; - FirstFunction(const MeanFunction&) { - ++objectsCount; + FirstFunction() : staticObjectsCount{objectsCount}, staticCallsCount{callsCount} { + ++staticObjectsCount; } - FirstFunction(MeanFunction&&) { - ++objectsCount; - } + FirstFunction(const FirstFunction&) = delete; ~FirstFunction() { - --objectsCount; + --staticObjectsCount; } std::string operator()(const arg_values& args) const { - ++callsCount; + ++staticCallsCount; std::string res; res.reserve(args.size()); for(auto value: args) { @@ -232,6 +247,7 @@ TEST_CASE("custom functions") { using Catch::Matchers::ContainsSubstring; SqrtFunction::callsCount = 0; + StatelessHasPrefixFunction::callsCount = 0; HasPrefixFunction::callsCount = 0; FirstFunction::callsCount = 0; @@ -278,33 +294,68 @@ TEST_CASE("custom functions") { REQUIRE(rows == expected); } - // create function - REQUIRE(HasPrefixFunction::callsCount == 0); - REQUIRE(HasPrefixFunction::objectsCount == 0); - storage.create_scalar_function(); - REQUIRE(HasPrefixFunction::callsCount == 0); - REQUIRE(HasPrefixFunction::objectsCount == 0); - - // call after creation { - auto rows = storage.select(func("one", "o")); - decltype(rows) expected; - expected.push_back(true); - REQUIRE(rows == expected); + // create function + REQUIRE(StatelessHasPrefixFunction::callsCount == 0); + REQUIRE(StatelessHasPrefixFunction::objectsCount == 0); + storage.create_scalar_function(); + REQUIRE(StatelessHasPrefixFunction::callsCount == 0); + // function object created + REQUIRE(StatelessHasPrefixFunction::objectsCount == 1); + + // call after creation + { + auto rows = storage.select(func("one", "o")); + decltype(rows) expected; + expected.push_back(true); + REQUIRE(rows == expected); + } + REQUIRE(StatelessHasPrefixFunction::callsCount == 1); + REQUIRE(StatelessHasPrefixFunction::objectsCount == 1); + { + auto rows = storage.select(func("two", "b")); + decltype(rows) expected; + expected.push_back(false); + REQUIRE(rows == expected); + } + REQUIRE(StatelessHasPrefixFunction::callsCount == 2); + REQUIRE(StatelessHasPrefixFunction::objectsCount == 1); + + // delete function + storage.delete_scalar_function(); + // function object destroyed + REQUIRE(StatelessHasPrefixFunction::objectsCount == 0); } - REQUIRE(HasPrefixFunction::callsCount == 1); - REQUIRE(HasPrefixFunction::objectsCount == 0); + { - auto rows = storage.select(func("two", "b")); - decltype(rows) expected; - expected.push_back(false); - REQUIRE(rows == expected); - } - REQUIRE(HasPrefixFunction::callsCount == 2); - REQUIRE(HasPrefixFunction::objectsCount == 0); + // create function + REQUIRE(HasPrefixFunction::callsCount == 0); + REQUIRE(HasPrefixFunction::objectsCount == 0); + storage.create_scalar_function(); + REQUIRE(HasPrefixFunction::callsCount == 0); + REQUIRE(HasPrefixFunction::objectsCount == 0); + + // call after creation + { + auto rows = storage.select(func("one", "o")); + decltype(rows) expected; + expected.push_back(true); + REQUIRE(rows == expected); + } + REQUIRE(HasPrefixFunction::callsCount == 1); + REQUIRE(HasPrefixFunction::objectsCount == 0); + { + auto rows = storage.select(func("two", "b")); + decltype(rows) expected; + expected.push_back(false); + REQUIRE(rows == expected); + } + REQUIRE(HasPrefixFunction::callsCount == 2); + REQUIRE(HasPrefixFunction::objectsCount == 0); - // delete function - storage.delete_scalar_function(); + // delete function + storage.delete_scalar_function(); + } // delete function storage.delete_scalar_function(); From ecc971d0d9342a902ac1bd6609586a076da25124 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 12:20:14 +0200 Subject: [PATCH 43/50] Function identifiers may contain spaces or quotation marks. --- dev/statement_serializer.h | 3 ++- dev/storage_base.h | 10 ---------- include/sqlite_orm/sqlite_orm.h | 13 ++---------- .../column_names.cpp | 20 +++++++++++++++---- tests/user_defined_functions.cpp | 10 ++++++++++ 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index b0ea3882b..598e2398f 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -370,7 +370,8 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << statement.name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; + stream_identifier(ss, statement.name()); + ss << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; diff --git a/dev/storage_base.h b/dev/storage_base.h index 5db8e039e..1a7d90914 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -259,8 +259,6 @@ namespace sqlite_orm { * } * }; * ``` - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function(Args&&... constructorArgs) { @@ -284,8 +282,6 @@ namespace sqlite_orm { * If `F` is a stateless function object, an instance of the function object is created once, otherwise * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function(Args&&... constructorArgs) { @@ -299,8 +295,6 @@ namespace sqlite_orm { * 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() { @@ -363,8 +357,6 @@ namespace sqlite_orm { * } * }; * ``` - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_aggregate_function(Args&&... constructorArgs) { @@ -387,8 +379,6 @@ namespace sqlite_orm { * together with a copy of the passed initialization arguments. * An instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_aggregate_function(Args&&... constructorArgs) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 8983bfcdc..425c2e994 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15606,8 +15606,6 @@ namespace sqlite_orm { * } * }; * ``` - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function(Args&&... constructorArgs) { @@ -15631,8 +15629,6 @@ namespace sqlite_orm { * If `F` is a stateless function object, an instance of the function object is created once, otherwise * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_scalar_function(Args&&... constructorArgs) { @@ -15646,8 +15642,6 @@ namespace sqlite_orm { * 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() { @@ -15710,8 +15704,6 @@ namespace sqlite_orm { * } * }; * ``` - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_aggregate_function(Args&&... constructorArgs) { @@ -15734,8 +15726,6 @@ namespace sqlite_orm { * together with a copy of the passed initialization arguments. * An instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. - * - * Attention: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. */ template void create_aggregate_function(Args&&... constructorArgs) { @@ -17042,7 +17032,8 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << statement.name() << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; + stream_identifier(ss, statement.name()); + ss << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; diff --git a/tests/statement_serializer_tests/column_names.cpp b/tests/statement_serializer_tests/column_names.cpp index 1e3e11a69..84436be7c 100644 --- a/tests/statement_serializer_tests/column_names.cpp +++ b/tests/statement_serializer_tests/column_names.cpp @@ -169,6 +169,14 @@ TEST_CASE("statement_serializer column names") { return R"(a"s)"; } }; + struct always42_function { + static const char* name() { + return R"("always 42")"; + } + int operator()() const { + return 42; + } + }; auto table1 = make_table(R"(object1"")", make_column(R"(i"d)", &Object1::id)); auto table2 = make_table(R"(ob"ject2)", make_column(R"(i"d)", &Object2::id)); using db_objects_t = internal::db_objects_tuple; @@ -179,14 +187,18 @@ TEST_CASE("statement_serializer column names") { using als_d = alias_d; auto expression = - select(columns(&Object1::id, as(&Object1::id), alias_column(&Object2::id)), + select(columns(&Object1::id, + as(&Object1::id), + alias_column(&Object2::id), + func()), join(using_(&Object1::id)), multi_order_by(order_by(get()), order_by(alias_column(&Object2::id)))); expression.highest_level = true; auto value = serialize(expression, context); - REQUIRE(value == - R"(SELECT "object1"""""."i""d", "object1"""""."i""d" AS "a""s", "d"."i""d" FROM "object1""""" )" - R"(JOIN "ob""ject2" "d" USING ("i""d") ORDER BY "a""s", "d"."i""d")"); + REQUIRE( + value == + R"(SELECT "object1"""""."i""d", "object1"""""."i""d" AS "a""s", "d"."i""d", """always 42"""() FROM "object1""""" )" + R"(JOIN "ob""ject2" "d" USING ("i""d") ORDER BY "a""s", "d"."i""d")"); } } } diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 0f56a5af9..5cf87c1bc 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -582,5 +582,15 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } + SECTION("escaped function identifier") { + constexpr auto clamp_f = R"("clamp int")"_scalar.quote(std::clamp); + storage.create_scalar_function(); + { + auto rows = storage.select(columns(clamp_f(0, 1, 1))); + decltype(rows) expected{{1}}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } } #endif From 7be2a4dccd4fbe96ab8e044b51a70512017bc267 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 20:26:19 +0200 Subject: [PATCH 44/50] Direct call of quoted scalar function --- dev/storage_base.h | 55 ++++++++-------- dev/udf_proxy.h | 55 +++++++++------- include/sqlite_orm/sqlite_orm.h | 110 ++++++++++++++++++-------------- 3 files changed, 126 insertions(+), 94 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 1a7d90914..2ec45175e 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -312,11 +312,15 @@ namespace sqlite_orm { /* 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); - }, + udf_proxy::func_type{.direct = + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + assert_args_count(context, argsCount); + 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}); @@ -713,12 +717,13 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ - [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(udf, std::move(argsTuple)); - statement_binder().result(context, result); - }, + udf_proxy::func_type{ + [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { + F& udf = *static_cast(udfHandle); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(udf, std::move(argsTuple)); + statement_binder().result(context, result); + }}, /* finalCall = */ nullptr, udfStorage); @@ -744,7 +749,7 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* step = */ - [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { + udf_proxy::func_type{[](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); args_tuple argsTuple = tuple_from_values{}(values, argsCount); #if __cpp_lib_bind_front >= 201907L @@ -756,7 +761,7 @@ namespace sqlite_orm { }, std::move(argsTuple)); #endif - }, + }}, /* finalCall = */ [](void* udfHandle, sqlite3_context* context) { F& udf = *static_cast(udfHandle); @@ -802,18 +807,18 @@ namespace sqlite_orm { } static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { - int rc = sqlite3_create_function_v2( - db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - !udfProxy.constructAt && !udfProxy.destroy ? quoted_scalar_function_callback - : !udfProxy.constructAt && udfProxy.destroy ? stateless_scalar_function_callback - : scalar_function_callback, - nullptr, - nullptr, - nullptr); + int rc = sqlite3_create_function_v2(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + !udfProxy.constructAt && !udfProxy.destroy ? udfProxy.func.direct + : !udfProxy.constructAt && udfProxy.destroy + ? stateless_scalar_function_dispatch + : scalar_function_callback, + nullptr, + nullptr, + nullptr); if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index 2a474ede6..e3ada5076 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -1,6 +1,7 @@ #pragma once #include +#include // assert #include // std::allocator, std::allocator_traits, std::unique_ptr #include // std::string #include // std::function @@ -37,12 +38,17 @@ namespace sqlite_orm { * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. */ struct udf_proxy { + using sqlite_callback_fn_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); using func_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); using memory_space = std::pair; + using func_type = union { + func_call_fn_t intermediate; + sqlite_callback_fn_t direct; + }; struct destruct_only_deleter { template @@ -57,7 +63,7 @@ namespace sqlite_orm { int argumentsCount; std::function constructAt; xdestroy_fn_t destroy; - func_call_fn_t func; + func_type func; final_call_fn_t finalAggregateCall; // flag whether the UDF has been constructed at `udfHandle`; @@ -70,7 +76,7 @@ namespace sqlite_orm { int argumentsCount, std::function constructAt, xdestroy_fn_t destroy, - func_call_fn_t func, + func_type func, final_call_fn_t finalAggregateCall, memory_space udfMemory) : name{std::move(name)}, @@ -94,14 +100,20 @@ namespace sqlite_orm { udf_proxy& operator=(const udf_proxy&) = delete; }; - inline void check_args_count(const udf_proxy* proxy, int argsCount) { - if(proxy->argumentsCount != -1) { - if(proxy->argumentsCount != argsCount && - /*check fin call*/ argsCount != -1) - SQLITE_ORM_CPP_UNLIKELY { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - } + // safety net of doing a triple check at runtime + inline void assert_args_count(const udf_proxy* proxy, int argsCount) { + assert((proxy->argumentsCount == -1) || (proxy->argumentsCount == argsCount || + /*check fin call*/ argsCount == -1)); + (void)proxy; + (void)argsCount; + } + + // safety net of doing a triple check at runtime + inline void assert_args_count(sqlite3_context* context, int argsCount) { + udf_proxy* proxy; + assert((proxy = static_cast(sqlite3_user_data(context))) != nullptr); + assert_args_count(proxy, argsCount); + (void)context; } inline void ensure_udf(udf_proxy* proxy, int argsCount) { @@ -109,7 +121,7 @@ namespace sqlite_orm { SQLITE_ORM_CPP_LIKELY { return; } - check_args_count(proxy, argsCount); + assert_args_count(proxy, argsCount); // Note on the use of the `udfHandle` pointer after the object construction: // since we only ever cast between void* and UDF* pointer types and // only use the memory space for one type during the entire lifetime of a proxy, @@ -123,9 +135,16 @@ namespace sqlite_orm { proxy->destroy(udfHandle(proxy)); } + inline void + stateless_scalar_function_dispatch(sqlite3_context* context, int argsCount, sqlite3_value** values) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + assert_args_count(proxy, argsCount); + proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + } + inline void 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); + assert_args_count(proxy, argsCount); // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory space. // The `udf_proxy` is one instance per database connection, // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. @@ -135,21 +154,13 @@ namespace sqlite_orm { // we can use `udfHandle` interconvertibly without laundering its provenance. proxy->constructAt(udfHandle(proxy)); const std::unique_ptr udfGuard{udfHandle(proxy), proxy->destroy}; - proxy->func(udfHandle(proxy), context, argsCount, values); + proxy->func.intermediate(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); - } - - SQLITE_ORM_INLINE_VAR constexpr auto stateless_scalar_function_callback = quoted_scalar_function_callback; - 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); - proxy->func(udfHandle(proxy), context, argsCount, values); + proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); } inline void aggregate_function_final_callback(sqlite3_context* context) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 425c2e994..87fad5e3d 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15215,6 +15215,7 @@ namespace sqlite_orm { // #include "udf_proxy.h" #include +#include // assert #include // std::allocator, std::allocator_traits, std::unique_ptr #include // std::string #include // std::function @@ -15251,12 +15252,17 @@ namespace sqlite_orm { * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. */ struct udf_proxy { + using sqlite_callback_fn_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); using func_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); using memory_space = std::pair; + using func_type = union { + func_call_fn_t intermediate; + sqlite_callback_fn_t direct; + }; struct destruct_only_deleter { template @@ -15271,7 +15277,7 @@ namespace sqlite_orm { int argumentsCount; std::function constructAt; xdestroy_fn_t destroy; - func_call_fn_t func; + func_type func; final_call_fn_t finalAggregateCall; // flag whether the UDF has been constructed at `udfHandle`; @@ -15284,7 +15290,7 @@ namespace sqlite_orm { int argumentsCount, std::function constructAt, xdestroy_fn_t destroy, - func_call_fn_t func, + func_type func, final_call_fn_t finalAggregateCall, memory_space udfMemory) : name{std::move(name)}, @@ -15308,14 +15314,20 @@ namespace sqlite_orm { udf_proxy& operator=(const udf_proxy&) = delete; }; - inline void check_args_count(const udf_proxy* proxy, int argsCount) { - if(proxy->argumentsCount != -1) { - if(proxy->argumentsCount != argsCount && - /*check fin call*/ argsCount != -1) - SQLITE_ORM_CPP_UNLIKELY { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - } + // safety net of doing a triple check at runtime + inline void assert_args_count(const udf_proxy* proxy, int argsCount) { + assert((proxy->argumentsCount == -1) || (proxy->argumentsCount == argsCount || + /*check fin call*/ argsCount == -1)); + (void)proxy; + (void)argsCount; + } + + // safety net of doing a triple check at runtime + inline void assert_args_count(sqlite3_context* context, int argsCount) { + udf_proxy* proxy; + assert((proxy = static_cast(sqlite3_user_data(context))) != nullptr); + assert_args_count(proxy, argsCount); + (void)context; } inline void ensure_udf(udf_proxy* proxy, int argsCount) { @@ -15323,7 +15335,7 @@ namespace sqlite_orm { SQLITE_ORM_CPP_LIKELY { return; } - check_args_count(proxy, argsCount); + assert_args_count(proxy, argsCount); // Note on the use of the `udfHandle` pointer after the object construction: // since we only ever cast between void* and UDF* pointer types and // only use the memory space for one type during the entire lifetime of a proxy, @@ -15337,9 +15349,16 @@ namespace sqlite_orm { proxy->destroy(udfHandle(proxy)); } + inline void + stateless_scalar_function_dispatch(sqlite3_context* context, int argsCount, sqlite3_value** values) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + assert_args_count(proxy, argsCount); + proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + } + inline void 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); + assert_args_count(proxy, argsCount); // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory space. // The `udf_proxy` is one instance per database connection, // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. @@ -15349,21 +15368,13 @@ namespace sqlite_orm { // we can use `udfHandle` interconvertibly without laundering its provenance. proxy->constructAt(udfHandle(proxy)); const std::unique_ptr udfGuard{udfHandle(proxy), proxy->destroy}; - 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); + proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); } - SQLITE_ORM_INLINE_VAR constexpr auto stateless_scalar_function_callback = quoted_scalar_function_callback; - 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); - proxy->func(udfHandle(proxy), context, argsCount, values); + proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); } inline void aggregate_function_final_callback(sqlite3_context* context) { @@ -15659,11 +15670,15 @@ namespace sqlite_orm { /* 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); - }, + udf_proxy::func_type{.direct = + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + assert_args_count(context, argsCount); + 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}); @@ -16060,12 +16075,13 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ - [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(udf, std::move(argsTuple)); - statement_binder().result(context, result); - }, + udf_proxy::func_type{ + [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { + F& udf = *static_cast(udfHandle); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(udf, std::move(argsTuple)); + statement_binder().result(context, result); + }}, /* finalCall = */ nullptr, udfStorage); @@ -16091,7 +16107,7 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* step = */ - [](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { + udf_proxy::func_type{[](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { F& udf = *static_cast(udfHandle); args_tuple argsTuple = tuple_from_values{}(values, argsCount); #if __cpp_lib_bind_front >= 201907L @@ -16103,7 +16119,7 @@ namespace sqlite_orm { }, std::move(argsTuple)); #endif - }, + }}, /* finalCall = */ [](void* udfHandle, sqlite3_context* context) { F& udf = *static_cast(udfHandle); @@ -16149,18 +16165,18 @@ namespace sqlite_orm { } static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { - int rc = sqlite3_create_function_v2( - db, - udfProxy.name.c_str(), - udfProxy.argumentsCount, - SQLITE_UTF8, - &udfProxy, - !udfProxy.constructAt && !udfProxy.destroy ? quoted_scalar_function_callback - : !udfProxy.constructAt && udfProxy.destroy ? stateless_scalar_function_callback - : scalar_function_callback, - nullptr, - nullptr, - nullptr); + int rc = sqlite3_create_function_v2(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + !udfProxy.constructAt && !udfProxy.destroy ? udfProxy.func.direct + : !udfProxy.constructAt && udfProxy.destroy + ? stateless_scalar_function_dispatch + : scalar_function_callback, + nullptr, + nullptr, + nullptr); if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } From c238e41d835ce760a3dc624ca73422e6d0fce523 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 21:19:02 +0200 Subject: [PATCH 45/50] Direct call of scalar/step functions --- dev/storage_base.h | 47 ++++++--------- dev/udf_proxy.h | 57 ++++++++--------- include/sqlite_orm/sqlite_orm.h | 104 ++++++++++++++------------------ 3 files changed, 92 insertions(+), 116 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 2ec45175e..743a14e7f 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -312,15 +312,12 @@ namespace sqlite_orm { /* destroy = */ nullptr, /* call = */ - udf_proxy::func_type{.direct = - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { - assert_args_count(context, argsCount); - args_tuple argsTuple = - tuple_from_values{}(values, argsCount); - auto result = - polyfill::apply(quotedF.callable(), std::move(argsTuple)); - statement_binder().result(context, result); - }}, + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + proxy_assert_args_count(context, argsCount); + 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}); @@ -705,25 +702,24 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - constexpr bool isStateless = std::is_empty::value; + using is_stateless = std::is_empty; auto udfStorage = allocate_udf_storage(); - if SQLITE_ORM_CONSTEXPR_IF(isStateless) { + if SQLITE_ORM_CONSTEXPR_IF(is_stateless::value) { constructAt(udfStorage.first); } this->scalarFunctions.emplace_back( udfName(), argsCount, - isStateless ? nullptr : std::move(constructAt), + is_stateless::value ? nullptr : std::move(constructAt), /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ - udf_proxy::func_type{ - [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(udf, std::move(argsTuple)); - statement_binder().result(context, result); - }}, + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, argsCount); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(*udfPointer, std::move(argsTuple)); + statement_binder().result(context, result); + }, /* finalCall = */ nullptr, udfStorage); @@ -749,8 +745,8 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* step = */ - udf_proxy::func_type{[](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + F& udf = *proxy_get_aggregate_step_udf(context, argsCount); args_tuple argsTuple = tuple_from_values{}(values, argsCount); #if __cpp_lib_bind_front >= 201907L std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); @@ -761,7 +757,7 @@ namespace sqlite_orm { }, std::move(argsTuple)); #endif - }}, + }, /* finalCall = */ [](void* udfHandle, sqlite3_context* context) { F& udf = *static_cast(udfHandle); @@ -812,10 +808,7 @@ namespace sqlite_orm { udfProxy.argumentsCount, SQLITE_UTF8, &udfProxy, - !udfProxy.constructAt && !udfProxy.destroy ? udfProxy.func.direct - : !udfProxy.constructAt && udfProxy.destroy - ? stateless_scalar_function_dispatch - : scalar_function_callback, + udfProxy.func, nullptr, nullptr, nullptr); @@ -831,7 +824,7 @@ namespace sqlite_orm { SQLITE_UTF8, &udfProxy, nullptr, - aggregate_function_step_callback, + udfProxy.func, aggregate_function_final_callback); if(rc != SQLITE_OK) { throw_translated_sqlite_error(rc); diff --git a/dev/udf_proxy.h b/dev/udf_proxy.h index e3ada5076..229b200dc 100644 --- a/dev/udf_proxy.h +++ b/dev/udf_proxy.h @@ -38,17 +38,9 @@ namespace sqlite_orm { * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. */ struct udf_proxy { - using sqlite_callback_fn_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); - using func_call_fn_t = void (*)(void* udfHandle, - sqlite3_context* context, - int argsCount, - sqlite3_value** values); + using sqlite_func_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); using memory_space = std::pair; - using func_type = union { - func_call_fn_t intermediate; - sqlite_callback_fn_t direct; - }; struct destruct_only_deleter { template @@ -63,7 +55,7 @@ namespace sqlite_orm { int argumentsCount; std::function constructAt; xdestroy_fn_t destroy; - func_type func; + sqlite_func_t func; final_call_fn_t finalAggregateCall; // flag whether the UDF has been constructed at `udfHandle`; @@ -76,7 +68,7 @@ namespace sqlite_orm { int argumentsCount, std::function constructAt, xdestroy_fn_t destroy, - func_type func, + sqlite_func_t func, final_call_fn_t finalAggregateCall, memory_space udfMemory) : name{std::move(name)}, @@ -84,7 +76,7 @@ namespace sqlite_orm { finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { - if(!constructAt && destroy) { + if(/*bool constructedOnce = */ !constructAt && destroy) { destroy(udfMemory.first); } if(udfMemory.second) { @@ -109,7 +101,7 @@ namespace sqlite_orm { } // safety net of doing a triple check at runtime - inline void assert_args_count(sqlite3_context* context, int argsCount) { + inline void proxy_assert_args_count(sqlite3_context* context, int argsCount) { udf_proxy* proxy; assert((proxy = static_cast(sqlite3_user_data(context))) != nullptr); assert_args_count(proxy, argsCount); @@ -135,32 +127,35 @@ namespace sqlite_orm { proxy->destroy(udfHandle(proxy)); } - inline void - stateless_scalar_function_dispatch(sqlite3_context* context, int argsCount, sqlite3_value** values) { + inline auto new_scalar_udf_handle(sqlite3_context* context, int argsCount) { + proxy_assert_args_count(context, argsCount); udf_proxy* proxy = static_cast(sqlite3_user_data(context)); - assert_args_count(proxy, argsCount); - proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + // Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. + proxy->constructAt(udfHandle(proxy)); + return std::unique_ptr{udfHandle(proxy), proxy->destroy}; } - inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + template + inline UDF* proxy_get_scalar_udf(std::true_type /*is_stateless*/, sqlite3_context* context, int argsCount) { + proxy_assert_args_count(context, argsCount); udf_proxy* proxy = static_cast(sqlite3_user_data(context)); - assert_args_count(proxy, argsCount); - // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory space. - // The `udf_proxy` is one instance per database connection, - // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. - // 2. Note on the use of the `udfHandle` pointer after the object construction: - // since we only ever cast between void* and UDF* pointer types and - // only use the memory space for one type during the entire lifetime of a proxy, - // we can use `udfHandle` interconvertibly without laundering its provenance. - proxy->constructAt(udfHandle(proxy)); - const std::unique_ptr udfGuard{udfHandle(proxy), proxy->destroy}; - proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + return static_cast(udfHandle(proxy)); } - inline void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + template + inline auto proxy_get_scalar_udf(std::false_type /*is_stateless*/, sqlite3_context* context, int argsCount) { + std::unique_ptr p = new_scalar_udf_handle(context, argsCount); + return std::unique_ptr{static_cast(p.release()), p.get_deleter()}; + } + + template + inline UDF* proxy_get_aggregate_step_udf(sqlite3_context* context, int argsCount) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); ensure_udf(proxy, argsCount); - proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + return static_cast(udfHandle(proxy)); } inline void aggregate_function_final_callback(sqlite3_context* context) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 87fad5e3d..989784427 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -15252,17 +15252,9 @@ namespace sqlite_orm { * As such, it also serves as a context for aggregation operations instead of using `sqlite3_aggregate_context()`. */ struct udf_proxy { - using sqlite_callback_fn_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); - using func_call_fn_t = void (*)(void* udfHandle, - sqlite3_context* context, - int argsCount, - sqlite3_value** values); + using sqlite_func_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); using memory_space = std::pair; - using func_type = union { - func_call_fn_t intermediate; - sqlite_callback_fn_t direct; - }; struct destruct_only_deleter { template @@ -15277,7 +15269,7 @@ namespace sqlite_orm { int argumentsCount; std::function constructAt; xdestroy_fn_t destroy; - func_type func; + sqlite_func_t func; final_call_fn_t finalAggregateCall; // flag whether the UDF has been constructed at `udfHandle`; @@ -15290,7 +15282,7 @@ namespace sqlite_orm { int argumentsCount, std::function constructAt, xdestroy_fn_t destroy, - func_type func, + sqlite_func_t func, final_call_fn_t finalAggregateCall, memory_space udfMemory) : name{std::move(name)}, @@ -15298,7 +15290,7 @@ namespace sqlite_orm { finalAggregateCall{finalAggregateCall}, udfConstructed{false}, udfMemory{udfMemory} {} ~udf_proxy() { - if(!constructAt && destroy) { + if(/*bool constructedOnce = */ !constructAt && destroy) { destroy(udfMemory.first); } if(udfMemory.second) { @@ -15323,7 +15315,7 @@ namespace sqlite_orm { } // safety net of doing a triple check at runtime - inline void assert_args_count(sqlite3_context* context, int argsCount) { + inline void proxy_assert_args_count(sqlite3_context* context, int argsCount) { udf_proxy* proxy; assert((proxy = static_cast(sqlite3_user_data(context))) != nullptr); assert_args_count(proxy, argsCount); @@ -15349,32 +15341,35 @@ namespace sqlite_orm { proxy->destroy(udfHandle(proxy)); } - inline void - stateless_scalar_function_dispatch(sqlite3_context* context, int argsCount, sqlite3_value** values) { + inline auto new_scalar_udf_handle(sqlite3_context* context, int argsCount) { + proxy_assert_args_count(context, argsCount); udf_proxy* proxy = static_cast(sqlite3_user_data(context)); - assert_args_count(proxy, argsCount); - proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + // Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. + proxy->constructAt(udfHandle(proxy)); + return std::unique_ptr{udfHandle(proxy), proxy->destroy}; } - inline void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + template + inline UDF* proxy_get_scalar_udf(std::true_type /*is_stateless*/, sqlite3_context* context, int argsCount) { + proxy_assert_args_count(context, argsCount); udf_proxy* proxy = static_cast(sqlite3_user_data(context)); - assert_args_count(proxy, argsCount); - // 1. Thread-safe with regard to the construction/destruction of the function object in the same memory space. - // The `udf_proxy` is one instance per database connection, - // and SQLite internally locks access to the database object during the generation of a result row with `sqlite3_step()`. - // 2. Note on the use of the `udfHandle` pointer after the object construction: - // since we only ever cast between void* and UDF* pointer types and - // only use the memory space for one type during the entire lifetime of a proxy, - // we can use `udfHandle` interconvertibly without laundering its provenance. - proxy->constructAt(udfHandle(proxy)); - const std::unique_ptr udfGuard{udfHandle(proxy), proxy->destroy}; - proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + return static_cast(udfHandle(proxy)); + } + + template + inline auto proxy_get_scalar_udf(std::false_type /*is_stateless*/, sqlite3_context* context, int argsCount) { + std::unique_ptr p = new_scalar_udf_handle(context, argsCount); + return std::unique_ptr{static_cast(p.release()), p.get_deleter()}; } - inline void aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { + template + inline UDF* proxy_get_aggregate_step_udf(sqlite3_context* context, int argsCount) { udf_proxy* proxy = static_cast(sqlite3_user_data(context)); ensure_udf(proxy, argsCount); - proxy->func.intermediate(udfHandle(proxy), context, argsCount, values); + return static_cast(udfHandle(proxy)); } inline void aggregate_function_final_callback(sqlite3_context* context) { @@ -15670,15 +15665,12 @@ namespace sqlite_orm { /* destroy = */ nullptr, /* call = */ - udf_proxy::func_type{.direct = - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { - assert_args_count(context, argsCount); - args_tuple argsTuple = - tuple_from_values{}(values, argsCount); - auto result = - polyfill::apply(quotedF.callable(), std::move(argsTuple)); - statement_binder().result(context, result); - }}, + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + proxy_assert_args_count(context, argsCount); + 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}); @@ -16063,25 +16055,24 @@ namespace sqlite_orm { constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - constexpr bool isStateless = std::is_empty::value; + using is_stateless = std::is_empty; auto udfStorage = allocate_udf_storage(); - if SQLITE_ORM_CONSTEXPR_IF(isStateless) { + if SQLITE_ORM_CONSTEXPR_IF(is_stateless::value) { constructAt(udfStorage.first); } this->scalarFunctions.emplace_back( udfName(), argsCount, - isStateless ? nullptr : std::move(constructAt), + is_stateless::value ? nullptr : std::move(constructAt), /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* call = */ - udf_proxy::func_type{ - [](void* udfHandle, sqlite3_context* context, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(udf, std::move(argsTuple)); - statement_binder().result(context, result); - }}, + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, argsCount); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(*udfPointer, std::move(argsTuple)); + statement_binder().result(context, result); + }, /* finalCall = */ nullptr, udfStorage); @@ -16107,8 +16098,8 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_proxy::destruct_only_deleter{}), /* step = */ - udf_proxy::func_type{[](void* udfHandle, sqlite3_context*, int argsCount, sqlite3_value** values) { - F& udf = *static_cast(udfHandle); + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + F& udf = *proxy_get_aggregate_step_udf(context, argsCount); args_tuple argsTuple = tuple_from_values{}(values, argsCount); #if __cpp_lib_bind_front >= 201907L std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple)); @@ -16119,7 +16110,7 @@ namespace sqlite_orm { }, std::move(argsTuple)); #endif - }}, + }, /* finalCall = */ [](void* udfHandle, sqlite3_context* context) { F& udf = *static_cast(udfHandle); @@ -16170,10 +16161,7 @@ namespace sqlite_orm { udfProxy.argumentsCount, SQLITE_UTF8, &udfProxy, - !udfProxy.constructAt && !udfProxy.destroy ? udfProxy.func.direct - : !udfProxy.constructAt && udfProxy.destroy - ? stateless_scalar_function_dispatch - : scalar_function_callback, + udfProxy.func, nullptr, nullptr, nullptr); @@ -16189,7 +16177,7 @@ namespace sqlite_orm { SQLITE_UTF8, &udfProxy, nullptr, - aggregate_function_step_callback, + udfProxy.func, aggregate_function_final_callback); if(rc != SQLITE_OK) { throw_translated_sqlite_error(rc); From 7377177fb2f454d641b8d6f7ea7139538d3ad6bc Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 21:45:25 +0200 Subject: [PATCH 46/50] Move-capture construction arguments for user-defined functions --- dev/functional/cxx_core_features.h | 4 +++ dev/storage_base.h | 36 ++++++++++++++++++--------- include/sqlite_orm/sqlite_orm.h | 40 +++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index ee804439a..7002beb39 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -54,6 +54,10 @@ #else #endif +#if __cpp_init_captures >= 201803L +#define SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED +#endif + #if __cpp_consteval >= 201811L #define SQLITE_ORM_CONSTEVAL_SUPPORTED #endif diff --git a/dev/storage_base.h b/dev/storage_base.h index 743a14e7f..e8ac706cb 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -264,12 +264,18 @@ 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(udf_holder{}, - /* 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{}, +#ifdef SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED + /* constructAt */ [... constructorArgs = std::move(constructorArgs)](void* location) { +#else + /* constructAt */ + [constructorArgs...](void* location) { +#endif + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -363,12 +369,18 @@ 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(udf_holder{}, /* 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 = */ +#ifdef SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED + /* constructAt */ [... constructorArgs = std::move(constructorArgs)](void* location) { +#else + /* constructAt */ + [constructorArgs...](void* location) { +#endif + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 989784427..71b222217 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -82,6 +82,10 @@ using std::nullptr_t; #else #endif +#if __cpp_init_captures >= 201803L +#define SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED +#endif + #if __cpp_consteval >= 201811L #define SQLITE_ORM_CONSTEVAL_SUPPORTED #endif @@ -15617,12 +15621,18 @@ 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(udf_holder{}, - /* 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{}, +#ifdef SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED + /* constructAt */ [... constructorArgs = std::move(constructorArgs)](void* location) { +#else + /* constructAt */ + [constructorArgs...](void* location) { +#endif + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -15716,12 +15726,18 @@ 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(udf_holder{}, /* 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 = */ +#ifdef SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED + /* constructAt */ [... constructorArgs = std::move(constructorArgs)](void* location) { +#else + /* constructAt */ + [constructorArgs...](void* location) { +#endif + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES From 2cac29e58f806a0cadec3e77af63934af159c268 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 22:24:23 +0200 Subject: [PATCH 47/50] Considered user-defined function `name()` returning a string view --- dev/statement_serializer.h | 2 +- include/sqlite_orm/sqlite_orm.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index 598e2398f..344148bc3 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; - stream_identifier(ss, statement.name()); + stream_identifier(ss, "", statement.name(), ""); ss << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 71b222217..7c3dcc27e 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -17052,7 +17052,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - stream_identifier(ss, statement.name()); + stream_identifier(ss, "", statement.name(), ""); ss << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } From 6bde0acfbf4474b5252e58d0332c02ab8d5017a2 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 22:32:23 +0200 Subject: [PATCH 48/50] Simplified unit test for escaped scalar function name --- tests/user_defined_functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 5cf87c1bc..c278901f4 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -586,8 +586,8 @@ TEST_CASE("generalized scalar udf") { constexpr auto clamp_f = R"("clamp int")"_scalar.quote(std::clamp); storage.create_scalar_function(); { - auto rows = storage.select(columns(clamp_f(0, 1, 1))); - decltype(rows) expected{{1}}; + auto rows = storage.select(clamp_f(0, 1, 1)); + decltype(rows) expected{1}; REQUIRE(rows == expected); } storage.delete_scalar_function(); From 574d30f04144dbf1c099457fbad718e2a39b71c7 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 23:01:38 +0200 Subject: [PATCH 49/50] Correct unit test that serializes a function name --- tests/statement_serializer_tests/select_constraints.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/statement_serializer_tests/select_constraints.cpp b/tests/statement_serializer_tests/select_constraints.cpp index 4b7321ac3..514c7f333 100644 --- a/tests/statement_serializer_tests/select_constraints.cpp +++ b/tests/statement_serializer_tests/select_constraints.cpp @@ -149,7 +149,7 @@ TEST_CASE("statement_serializer select constraints") { }; auto expression = func(&User::id); value = serialize(expression, context); - expected = R"(EVEN("id"))"; + expected = R"("EVEN"("id"))"; } SECTION("exists") { // EXISTS must use parentheses in a new context From f67816cf4aeb62e21b25ce80dffcdcad244d55db Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Nov 2023 23:43:34 +0200 Subject: [PATCH 50/50] Runner-up: Corrected unit tests that serialize function names --- tests/statement_serializer_tests/bindables.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/statement_serializer_tests/bindables.cpp b/tests/statement_serializer_tests/bindables.cpp index 1cd0210da..73ff0b257 100644 --- a/tests/statement_serializer_tests/bindables.cpp +++ b/tests/statement_serializer_tests/bindables.cpp @@ -283,12 +283,12 @@ TEST_CASE("bindables") { SECTION("null as function argument") { auto ast = func(1, statically_bindable_pointer(nullptr)); value = serialize(ast, context); - expected = "remember(1, null)"; + expected = R"("remember"(1, null))"; } SECTION("null as function argument 2") { auto ast = func(1, nullptr); value = serialize(ast, context); - expected = "remember(1, null)"; + expected = R"("remember"(1, null))"; } REQUIRE(value == expected);