Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Syntactic sugar" for user-defined function calls #1247

Merged
merged 12 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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