Skip to content

Commit

Permalink
Merge pull request #1247 from FireDaemon/function_syntactic_sugar
Browse files Browse the repository at this point in the history
"Syntactic sugar" for user-defined function calls
  • Loading branch information
trueqbit authored Nov 15, 2023
2 parents 2e1916b + cc85bc4 commit 8243dc5
Show file tree
Hide file tree
Showing 19 changed files with 894 additions and 590 deletions.
8 changes: 4 additions & 4 deletions dev/alias.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <type_traits> // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const
#include <type_traits> // std::enable_if
#include <utility> // std::index_sequence, std::make_index_sequence
#include <string> // std::string
#include <sstream> // std::stringstream
Expand Down Expand Up @@ -219,7 +219,7 @@ namespace sqlite_orm {
template<orm_table_alias auto als, class C>
constexpr auto alias_column(C field) {
using namespace ::sqlite_orm::internal;
using A = std::remove_const_t<decltype(als)>;
using A = decltype(als);
using aliased_type = type_t<A>;
static_assert(is_field_of_v<C, aliased_type>, "Column must be from aliased table");

Expand Down Expand Up @@ -263,7 +263,7 @@ namespace sqlite_orm {
*/
template<orm_column_alias auto als, class E>
auto as(E expression) {
return internal::as_t<std::remove_const_t<decltype(als)>, E>{std::move(expression)};
return internal::as_t<decltype(als), E>{std::move(expression)};
}

/**
Expand All @@ -283,7 +283,7 @@ namespace sqlite_orm {
#ifdef SQLITE_ORM_WITH_CPP20_ALIASES
template<orm_column_alias auto als>
auto get() {
return internal::alias_holder<std::remove_const_t<decltype(als)>>{};
return internal::alias_holder<decltype(als)>{};
}
#endif

Expand Down
14 changes: 10 additions & 4 deletions dev/alias_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace sqlite_orm {
template<class A>
concept orm_alias = std::derived_from<A, alias_tag>;

/** @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`
Expand All @@ -83,7 +83,7 @@ namespace sqlite_orm {
template<class A>
concept orm_column_alias = (orm_alias<A> && !orm_names_type<A>);

/** @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`.
Expand All @@ -92,7 +92,7 @@ namespace sqlite_orm {
template<class A>
concept orm_recordset_alias = (orm_alias<A> && orm_names_type<A>);

/** @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`.
Expand All @@ -101,20 +101,26 @@ namespace sqlite_orm {
template<class A>
concept orm_table_alias = (orm_recordset_alias<A> && !std::same_as<typename A::type, std::remove_const_t<A>>);

/** @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.
*/
template<class R>
concept orm_table_reference = polyfill::is_specialization_of_v<std::remove_const_t<R>, internal::table_reference>;

/** @short Specifies that a type refers to a mapped table (possibly aliased).
*/
template<class T>
concept orm_refers_to_table = (orm_table_reference<T> || orm_table_alias<T>);

/** @short Specifies that a type refers to a recordset.
*/
template<class T>
concept orm_refers_to_recordset = (orm_table_reference<T> || orm_recordset_alias<T>);

/** @short Specifies that a type is a mapped recordset (table reference).
*/
template<class T>
concept orm_mapped_recordset = (orm_table_reference<T>);
#endif
Expand Down
9 changes: 4 additions & 5 deletions dev/column_pointer.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <type_traits> // std::enable_if, std::remove_const
#include <type_traits> // std::enable_if
#include <utility> // std::move

#include "functional/cxx_type_traits_polyfill.h"
Expand Down Expand Up @@ -48,8 +48,7 @@ namespace sqlite_orm {
*/
template<orm_table_reference auto table, class O, class F>
constexpr auto column(F O::*field) {
using R = std::remove_const_t<decltype(table)>;
return column<typename R::type>(field);
return column<internal::auto_type_t<table>>(field);
}

/**
Expand All @@ -65,7 +64,7 @@ namespace sqlite_orm {
*/
template<class O>
requires(!orm_recordset_alias<O>)
constexpr internal::table_reference<O> column() {
consteval internal::table_reference<O> column() {
return {};
}

Expand All @@ -74,7 +73,7 @@ namespace sqlite_orm {
*/
template<class O>
requires(!orm_recordset_alias<O>)
constexpr internal::table_reference<O> c() {
consteval internal::table_reference<O> c() {
return {};
}
#endif
Expand Down
177 changes: 113 additions & 64 deletions dev/function.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#pragma once

#include <sqlite3.h>
#include <type_traits>
#include <type_traits> // std::is_member_function_pointer, std::remove_const, std::decay, std::is_same, std::false_type, std::true_type
#include <string> // std::string
#include <tuple> // std::tuple
#include <tuple> // std::tuple, std::tuple_size, std::tuple_element
#include <functional> // std::function
#include <algorithm> // std::min
#include <utility> // std::move, std::forward
Expand All @@ -22,50 +22,45 @@ namespace sqlite_orm {

namespace internal {

struct user_defined_function_base {
using func_call = std::function<
void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>;
using final_call = std::function<void(sqlite3_context* context, void* functionPointer)>;
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);

std::string name;
int argumentsCount = 0;
std::function<int*()> create;
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_) :
name(std::move(name_)),
argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {}
#endif
std::function<void*()> 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<void*()> 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<void*()> 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 user_defined_scalar_function_t : user_defined_function_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_},
run(std::move(run_)) {}
struct scalar_udf_proxy : udf_proxy_base {
using udf_proxy_base::udf_proxy_base;
};

struct user_defined_aggregate_function_t : user_defined_function_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_},
step(std::move(step_)), finalCall(std::move(finalCall_)) {}
struct aggregate_udf_proxy : udf_proxy_base {
using udf_proxy_base::udf_proxy_base;
};

template<class F>
Expand All @@ -78,15 +73,14 @@ namespace sqlite_orm {
using aggregate_fin_function_t = decltype(&F::fin);

template<class F, class SFINAE = void>
SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v = false;
SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v = false;
template<class F>
SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v<F, polyfill::void_t<scalar_call_function_t<F>>> =
true;
SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v<F, polyfill::void_t<scalar_call_function_t<F>>> = true;

template<class F, class SFINAE = void>
SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v = false;
SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v = false;
template<class F>
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_step_function_t<F>,
aggregate_fin_function_t<F>,
Expand Down Expand Up @@ -115,23 +109,23 @@ namespace sqlite_orm {
struct callable_arguments_impl;

template<class F>
struct callable_arguments_impl<F, std::enable_if_t<is_scalar_function_v<F>>> {
struct callable_arguments_impl<F, std::enable_if_t<is_scalar_udf_v<F>>> {
using args_tuple = typename member_function_arguments<scalar_call_function_t<F>>::tuple_type;
using return_type = typename member_function_arguments<scalar_call_function_t<F>>::return_type;
};

template<class F>
struct callable_arguments_impl<F, std::enable_if_t<is_aggregate_function_v<F>>> {
struct callable_arguments_impl<F, std::enable_if_t<is_aggregate_udf_v<F>>> {
using args_tuple = typename member_function_arguments<aggregate_step_function_t<F>>::tuple_type;
using return_type = typename member_function_arguments<aggregate_fin_function_t<F>>::return_type;
};

template<class F>
struct callable_arguments : callable_arguments_impl<F> {};

template<class F, class... Args>
template<class UDF, class... Args>
struct function_call {
using function_type = F;
using udf_type = UDF;
using args_tuple = std::tuple<Args...>;

args_tuple args;
Expand Down Expand Up @@ -223,24 +217,79 @@ namespace sqlite_orm {
(polyfill::is_specialization_of_v<passed_arg_t, pointer_binding>) > {});
#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<class UDF>
struct function : polyfill::type_identity<UDF> {
template<typename... Args>
function_call<UDF, Args...> operator()(Args... args) const {
using args_tuple = std::tuple<Args...>;
using function_args_tuple = typename callable_arguments<UDF>::args_tuple;
constexpr size_t argsCount = std::tuple_size<args_tuple>::value;
constexpr size_t functionArgsCount = std::tuple_size<function_args_tuple>::value;
static_assert((argsCount == functionArgsCount &&
!std::is_same<function_args_tuple, std::tuple<arg_values>>::value &&
validate_pointer_value_types<function_args_tuple, args_tuple>(
polyfill::index_constant<std::min(functionArgsCount, argsCount) - 1>{})) ||
std::is_same<function_args_tuple, std::tuple<arg_values>>::value,
"The number of arguments does not match");
return {{std::forward<Args>(args)...}};
}
};
}

/**
* Used to call user defined function: `func<MyFunc>(...);`
#ifdef SQLITE_ORM_WITH_CPP20_ALIASES
/** @short Specifies that a type is a user-defined scalar function.
*/
template<class F, class... Args>
internal::function_call<F, Args...> func(Args... args) {
using args_tuple = std::tuple<Args...>;
using function_args_tuple = typename internal::callable_arguments<F>::args_tuple;
constexpr auto argsCount = std::tuple_size<args_tuple>::value;
constexpr auto functionArgsCount = std::tuple_size<function_args_tuple>::value;
static_assert((argsCount == functionArgsCount &&
!std::is_same<function_args_tuple, std::tuple<arg_values>>::value &&
internal::validate_pointer_value_types<function_args_tuple, args_tuple>(
polyfill::index_constant<std::min<>(functionArgsCount, argsCount) - 1>{})) ||
std::is_same<function_args_tuple, std::tuple<arg_values>>::value,
"Number of arguments does not match");
return {std::make_tuple(std::forward<Args>(args)...)};
}
template<class UDF>
concept orm_scalar_udf = requires {
UDF::name();
typename internal::scalar_call_function_t<UDF>;
};

/** @short Specifies that a type is a user-defined aggregate function.
*/
template<class UDF>
concept orm_aggregate_udf = requires {
UDF::name();
typename internal::aggregate_step_function_t<UDF>;
typename internal::aggregate_fin_function_t<UDF>;
requires std::is_member_function_pointer_v<internal::aggregate_step_function_t<UDF>>;
requires std::is_member_function_pointer_v<internal::aggregate_fin_function_t<UDF>>;
};

/** @short Specifies that a type is a framed user-defined scalar function.
*/
template<class F>
concept orm_scalar_function = (polyfill::is_specialization_of_v<std::remove_const_t<F>, internal::function> &&
orm_scalar_udf<typename F::type>);

/** @short Specifies that a type is a framed user-defined aggregate function.
*/
template<class F>
concept orm_aggregate_function = (polyfill::is_specialization_of_v<std::remove_const_t<F>, internal::function> &&
orm_aggregate_udf<typename F::type>);
#endif

/**
* Call a user-defined function.
*
* Example:
* struct IdFunc { int oeprator(int arg)() const { return arg; } };
* // inline:
* select(func<IdFunc>(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<IdFunc>;
* select(idfunc(42));
*
*/
template<class UDF>
#ifdef SQLITE_ORM_WITH_CPP20_ALIASES
requires(orm_scalar_udf<UDF> || orm_aggregate_udf<UDF>)
#endif
SQLITE_ORM_INLINE_VAR constexpr internal::function<UDF> func{};
}
13 changes: 13 additions & 0 deletions dev/schema/table.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -408,6 +409,18 @@ namespace sqlite_orm {
return {std::move(name), std::make_tuple<Cs...>(std::forward<Cs>(args)...)});
}

#ifdef SQLITE_ORM_WITH_CPP20_ALIASES
/**
* Factory function for a table definition.
*
* The mapped object type is explicitly specified.
*/
template<orm_table_reference auto table, class... Cs>
auto make_table(std::string name, Cs... args) {
return make_table<internal::decay_table_reference_t<table>>(std::move(name), std::forward<Cs>(args)...);
}
#endif

template<class M>
internal::virtual_table_t<M> make_virtual_table(std::string name, M module_details) {
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)});
Expand Down
Loading

0 comments on commit 8243dc5

Please sign in to comment.