Skip to content

Commit

Permalink
Merge pull request #1325 from fnc12/fix-aggregate-functions
Browse files Browse the repository at this point in the history
Fixed aggregate functions
  • Loading branch information
trueqbit authored Jun 21, 2024
2 parents 6c2f2f9 + 43d3103 commit 8f71f41
Show file tree
Hide file tree
Showing 14 changed files with 565 additions and 347 deletions.
4 changes: 2 additions & 2 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
AttributeMacros: [SQLITE_ORM_CPP_LIKELY, SQLITE_ORM_CPP_UNLIKELY]
StatementMacros:
- __pragma
- _Pragma
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
TabWidth: 4
UseTab: Never
...

6 changes: 1 addition & 5 deletions dev/carray.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ namespace sqlite_orm {
#endif

/**
* Generalized form of the 'remember' SQL function that is a pass-through for values
* Base for a generalized form of the 'remember' SQL function that is a pass-through for values
* (it returns its argument unchanged using move semantics) but also saves the
* value that is passed through into a bound variable.
*/
Expand All @@ -131,10 +131,6 @@ namespace sqlite_orm {
}
return std::move(value);
}

static constexpr const char* name() {
return "note_value";
}
};

/**
Expand Down
102 changes: 61 additions & 41 deletions dev/function.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#pragma once

#include <type_traits> // 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 <type_traits> // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type
#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED
#include <concepts> // std::copy_constructible
#endif
#include <tuple> // std::tuple, std::tuple_size, std::tuple_element
#include <algorithm> // std::min, std::copy_n
#include <utility> // std::move, std::forward

#include "functional/cxx_universal.h" // ::size_t
#include "functional/cxx_universal.h" // ::size_t, ::nullptr_t
#include "functional/cxx_type_traits_polyfill.h"
#include "functional/cstring_literal.h"
#include "functional/function_traits.h"
Expand Down Expand Up @@ -229,98 +229,115 @@ namespace sqlite_orm {
template<class T>
using unpacked_arg_t = typename unpacked_arg<T>::type;

template<size_t I, class FnArg, class CallArg>
template<size_t I, class FnParam, class CallArg>
SQLITE_ORM_CONSTEVAL bool expected_pointer_value() {
static_assert(polyfill::always_false_v<FnArg, CallArg>, "Expected a pointer value for I-th argument");
static_assert(polyfill::always_false_v<FnParam, CallArg>, "Expected a pointer value for I-th argument");
return false;
}

template<size_t I, class FnArg, class CallArg, class EnableIfTag = void>
constexpr bool is_same_pvt_v = expected_pointer_value<I, FnArg, CallArg>();
template<size_t I, class FnParam, class CallArg, class EnableIfTag = void>
constexpr bool is_same_pvt_v = expected_pointer_value<I, FnParam, CallArg>();

// Always allow binding nullptr to a pointer argument
template<size_t I, class PointerArg>
constexpr bool is_same_pvt_v<I, PointerArg, nullptr_t, polyfill::void_t<typename PointerArg::tag>> = true;
// Always allow binding nullptr to a pointer argument
template<size_t I, class P, class T, class D>
constexpr bool is_same_pvt_v<I, pointer_arg<P, T>, pointer_binding<nullptr_t, T, D>, void> = true;

template<size_t I, class PointerArgDataType, class BindingDataType>
SQLITE_ORM_CONSTEVAL bool assert_same_pointer_data_type() {
constexpr bool valid = std::is_convertible<BindingDataType*, PointerArgDataType*>::value;
static_assert(valid, "Pointer data types of I-th argument do not match");
return valid;
}

#if __cplusplus >= 201703L // C++17 or later
template<size_t I, const char* PointerArg, const char* Binding>
SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() {
SQLITE_ORM_CONSTEVAL bool assert_same_pointer_tag() {
constexpr bool valid = Binding == PointerArg;
static_assert(valid, "Pointer value types of I-th argument do not match");
static_assert(valid, "Pointer types (tags) of I-th argument do not match");
return valid;
}

template<size_t I, class PointerArg, class Binding>
constexpr bool
is_same_pvt_v<I, PointerArg, Binding, polyfill::void_t<typename PointerArg::tag, typename Binding::tag>> =
assert_same_pointer_type<I, PointerArg::tag::value, Binding::tag::value>();
assert_same_pointer_tag<I, PointerArg::tag::value, Binding::tag::value>() &&
assert_same_pointer_data_type<I,
typename PointerArg::qualified_type,
typename Binding::qualified_type>();
#else
template<size_t I, class PointerArg, class Binding>
SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() {
constexpr bool assert_same_pointer_tag() {
constexpr bool valid = Binding::value == PointerArg::value;
static_assert(valid, "Pointer value types of I-th argument do not match");
static_assert(valid, "Pointer types (tags) of I-th argument do not match");
return valid;
}

template<size_t I, class PointerArg, class Binding>
constexpr bool
is_same_pvt_v<I, PointerArg, Binding, polyfill::void_t<typename PointerArg::tag, typename Binding::tag>> =
assert_same_pointer_type<I, typename PointerArg::tag, typename Binding::tag>();
assert_same_pointer_tag<I, typename PointerArg::tag, typename Binding::tag>();
#endif

template<size_t I, class FnArg, class CallArg>
// not a pointer value, currently leave it unchecked
template<size_t I, class FnParam, class CallArg>
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::false_type) {
return true;
}

template<size_t I, class FnArg, class CallArg>
// check the type of pointer values
template<size_t I, class FnParam, class CallArg>
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::true_type) {
return is_same_pvt_v<I, FnArg, CallArg>;
return is_same_pvt_v<I, FnParam, CallArg>;
}

template<class FnArgs, class CallArgs>
template<class FnParams, class CallArgs>
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant<size_t(-1)>) {
return true;
}
template<class FnArgs, class CallArgs, size_t I>
template<class FnParams, class CallArgs, size_t I>
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant<I>) {
using func_arg_t = std::tuple_element_t<I, FnArgs>;
using passed_arg_t = unpacked_arg_t<std::tuple_element_t<I, CallArgs>>;
using func_param_type = std::tuple_element_t<I, FnParams>;
using call_arg_type = unpacked_arg_t<std::tuple_element_t<I, CallArgs>>;

#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED
constexpr bool valid = validate_pointer_value_type<I,
std::tuple_element_t<I, FnArgs>,
std::tuple_element_t<I, FnParams>,
unpacked_arg_t<std::tuple_element_t<I, CallArgs>>>(
polyfill::bool_constant < (polyfill::is_specialization_of_v<func_arg_t, pointer_arg>) ||
(polyfill::is_specialization_of_v<passed_arg_t, pointer_binding>) > {});
polyfill::bool_constant < (polyfill::is_specialization_of_v<func_param_type, pointer_arg>) ||
(polyfill::is_specialization_of_v<call_arg_type, pointer_binding>) > {});

return validate_pointer_value_types<FnArgs, CallArgs>(polyfill::index_constant<I - 1>{}) && valid;
return validate_pointer_value_types<FnParams, CallArgs>(polyfill::index_constant<I - 1>{}) && valid;
#else
return validate_pointer_value_types<FnArgs, CallArgs>(polyfill::index_constant<I - 1>{}) &&
return validate_pointer_value_types<FnParams, CallArgs>(polyfill::index_constant<I - 1>{}) &&
validate_pointer_value_type<I,
std::tuple_element_t<I, FnArgs>,
std::tuple_element_t<I, FnParams>,
unpacked_arg_t<std::tuple_element_t<I, CallArgs>>>(
polyfill::bool_constant < (polyfill::is_specialization_of_v<func_arg_t, pointer_arg>) ||
(polyfill::is_specialization_of_v<passed_arg_t, pointer_binding>) > {});
polyfill::bool_constant < (polyfill::is_specialization_of_v<func_param_type, pointer_arg>) ||
(polyfill::is_specialization_of_v<call_arg_type, pointer_binding>) > {});
#endif
}

/*
* Note: Currently the number of call arguments is checked and whether the types of pointer values match,
* but other call argument types are not checked against the parameter types of the function.
*/
template<typename UDF, typename... CallArgs>
#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED
SQLITE_ORM_CONSTEVAL
SQLITE_ORM_CONSTEVAL void check_function_call() {
#else
void check_function_call() {
#endif
void
check_function_call() {
using args_tuple = std::tuple<CallArgs...>;
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");
using call_args_tuple = std::tuple<CallArgs...>;
using function_params_tuple = typename callable_arguments<UDF>::args_tuple;
constexpr size_t callArgsCount = std::tuple_size<call_args_tuple>::value;
constexpr size_t functionParamsCount = std::tuple_size<function_params_tuple>::value;
static_assert(std::is_same<function_params_tuple, std::tuple<arg_values>>::value ||
(callArgsCount == functionParamsCount &&
validate_pointer_value_types<function_params_tuple, call_args_tuple>(
polyfill::index_constant<std::min(functionParamsCount, callArgsCount) - 1>{})),
"Check the number and types of the function call arguments");
}

/*
Expand Down Expand Up @@ -475,6 +492,9 @@ namespace sqlite_orm {

/** @short Call a user-defined function.
*
* Note: Currently the number of call arguments is checked and whether the types of pointer values match,
* but other call argument types are not checked against the parameter types of the function.
*
* Example:
* struct IdFunc { int oeprator(int arg)() const { return arg; } };
* // inline:
Expand Down
7 changes: 3 additions & 4 deletions dev/mapped_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,9 @@ namespace sqlite_orm {
mapped_iterator& operator=(mapped_iterator&&) = default;

value_type& operator*() const {
if(!this->stmt)
SQLITE_ORM_CPP_UNLIKELY {
throw std::system_error{orm_error_code::trying_to_dereference_null_iterator};
}
if(!this->stmt) SQLITE_ORM_CPP_UNLIKELY {
throw std::system_error{orm_error_code::trying_to_dereference_null_iterator};
}
return *this->current;
}

Expand Down
2 changes: 2 additions & 0 deletions dev/pointer_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ namespace sqlite_orm {
#endif

using tag = T;
using qualified_type = P;

P* p_;

P* ptr() const noexcept {
Expand Down
9 changes: 4 additions & 5 deletions dev/serializing_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ namespace sqlite_orm {
for(size_t offset = 0, next; true; offset = next + 1) {
next = str.find(char2Escape, offset);

if(next == str.npos)
SQLITE_ORM_CPP_LIKELY {
os.write(str.data() + offset, str.size() - offset);
break;
}
if(next == str.npos) SQLITE_ORM_CPP_LIKELY {
os.write(str.data() + offset, str.size() - offset);
break;
}

os.write(str.data() + offset, next - offset + 1);
os.write(&char2Escape, 1);
Expand Down
28 changes: 16 additions & 12 deletions dev/storage_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -735,26 +735,24 @@ namespace sqlite_orm {
? -1
: int(std::tuple_size<args_tuple>::value);
using is_stateless = std::is_empty<F>;
auto udfStorage = allocate_udf_storage<F>();
auto udfMemorySpace = preallocate_udf_memory<F>();
if SQLITE_ORM_CONSTEXPR_IF(is_stateless::value) {
constructAt(udfStorage.first);
constructAt(udfMemorySpace.first);
}
this->scalarFunctions.emplace_back(
udfName(),
argsCount,
is_stateless::value ? nullptr : std::move(constructAt),
/* destroy = */
obtain_xdestroy_for<F>(udf_proxy::destruct_only_deleter{}),
obtain_xdestroy_for<F>(udf_destruct_only_deleter{}),
/* call = */
[](sqlite3_context* context, int argsCount, sqlite3_value** values) {
auto udfPointer = proxy_get_scalar_udf<F>(is_stateless{}, context, argsCount);
args_tuple argsTuple = tuple_from_values<args_tuple>{}(values, argsCount);
auto result = polyfill::apply(*udfPointer, std::move(argsTuple));
statement_binder<return_type>().result(context, result);
},
/* finalCall = */
nullptr,
udfStorage);
udfMemorySpace);

if(this->connection->retain_count() > 0) {
sqlite3* db = this->connection->get();
Expand All @@ -775,17 +773,23 @@ namespace sqlite_orm {
argsCount,
std::move(constructAt),
/* destroy = */
obtain_xdestroy_for<F>(udf_proxy::destruct_only_deleter{}),
obtain_xdestroy_for<F>(udf_destruct_only_deleter{}),
/* step = */
[](sqlite3_context* context, int argsCount, sqlite3_value** values) {
F& udf = *proxy_get_aggregate_step_udf<F>(context, argsCount);
F* udfPointer;
try {
udfPointer = proxy_get_aggregate_step_udf<F>(context, argsCount);
} catch(const std::bad_alloc&) {
sqlite3_result_error_nomem(context);
return;
}
args_tuple argsTuple = tuple_from_values<args_tuple>{}(values, argsCount);
#if __cpp_lib_bind_front >= 201907L
std::apply(std::bind_front(&F::step, &udf), std::move(argsTuple));
std::apply(std::bind_front(&F::step, udfPointer), std::move(argsTuple));
#else
polyfill::apply(
[&udf](auto&&... args) {
udf.step(std::forward<decltype(args)>(args)...);
[udfPointer](auto&&... args) {
udfPointer->step(std::forward<decltype(args)>(args)...);
},
std::move(argsTuple));
#endif
Expand All @@ -796,7 +800,7 @@ namespace sqlite_orm {
auto result = udf.fin();
statement_binder<return_type>().result(context, result);
},
allocate_udf_storage<F>());
obtain_udf_allocator<F>());

if(this->connection->retain_count() > 0) {
sqlite3* db = this->connection->get();
Expand Down
Loading

0 comments on commit 8f71f41

Please sign in to comment.