From 5d75d1b1f2c955f3a39b42692b1b46a407c1da58 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 12 Nov 2023 00:00:32 +0200 Subject: [PATCH] Uniform syntax for column pointer expressions through table references --- dev/alias_traits.h | 37 +- dev/column_pointer.h | 47 ++- dev/conditions.h | 29 +- dev/core_functions.h | 11 + dev/mapped_type_proxy.h | 9 +- dev/prepared_statement.h | 31 +- dev/select_constraints.h | 11 +- dev/storage.h | 137 +++++--- dev/table_type_of.h | 2 + examples/column_aliases.cpp | 27 +- include/sqlite_orm/sqlite_orm.h | 315 +++++++++++++----- tests/CMakeLists.txt | 1 + tests/builtin_tables.cpp | 6 + tests/prepared_statement_tests/get_all.cpp | 8 +- .../aggregate_functions.cpp | 8 + .../select_constraints.cpp | 6 + tests/static_tests/alias.cpp | 35 ++ tests/static_tests/column_pointer.cpp | 119 +++++++ tests/tests.cpp | 3 - 19 files changed, 673 insertions(+), 169 deletions(-) create mode 100644 tests/static_tests/column_pointer.cpp diff --git a/dev/alias_traits.h b/dev/alias_traits.h index b83960740..489bbba86 100644 --- a/dev/alias_traits.h +++ b/dev/alias_traits.h @@ -1,6 +1,6 @@ #pragma once -#include // std::remove_const, std::is_base_of, std::is_same +#include // std::remove_const, std::is_base_of, std::is_same, std::type_identity #ifdef SQLITE_ORM_WITH_CPP20_ALIASES #include #endif @@ -50,6 +50,24 @@ namespace sqlite_orm { template using is_table_alias = polyfill::bool_constant>; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. + */ + template + struct table_reference : std::type_identity {}; + + template + struct decay_table_reference : std::remove_const {}; + template + struct decay_table_reference> : std::type_identity {}; + template + struct decay_table_reference> : std::type_identity {}; + + template + using decay_table_reference_t = typename decay_table_reference::type; +#endif } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -82,5 +100,22 @@ 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. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + + template + concept orm_refers_to_table = (orm_table_reference || orm_table_alias); + + template + concept orm_refers_to_recordset = (orm_table_reference || orm_recordset_alias); + + template + concept orm_mapped_recordset = (orm_table_reference); #endif } diff --git a/dev/column_pointer.h b/dev/column_pointer.h index f479ff10e..bb8d5af76 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -1,10 +1,11 @@ #pragma once -#include // std::enable_if +#include // std::enable_if, std::remove_const #include // std::move #include "functional/cxx_type_traits_polyfill.h" #include "tags.h" +#include "alias_traits.h" namespace sqlite_orm { namespace internal { @@ -35,8 +36,46 @@ namespace sqlite_orm { * struct MyType : BaseType { ... }; * storage.select(column(&BaseType::id)); */ - template = true> - constexpr internal::column_pointer column(F field) { - return {std::move(field)}; + template = true> + constexpr internal::column_pointer column(F O::*field) { + static_assert(internal::is_field_of_v, "Column must be from derived class"); + return {field}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicitly refer to a column. + */ + template + constexpr auto column(F O::*field) { + using R = std::remove_const_t; + return column(field); + } + + /** + * Explicitly refer to a column. + */ + template + constexpr auto operator->*(const R& /*table*/, F O::*field) { + return column(field); + } + + /** + * Make table reference. + */ + template + requires(!orm_recordset_alias) + constexpr internal::table_reference column() { + return {}; + } + + /** + * Make table reference. + */ + template + requires(!orm_recordset_alias) + constexpr internal::table_reference c() { + return {}; + } +#endif } diff --git a/dev/conditions.h b/dev/conditions.h index 6cd5c300a..043d6dd25 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -834,10 +834,9 @@ namespace sqlite_orm { * Explicit FROM function. Usage: * `storage.select(&User::id, from<"a"_alias.for_>());` */ - template + template auto from() { - static_assert(sizeof...(tables) > 0); - return internal::from_t...>{}; + return from...>(); } #endif @@ -954,12 +953,12 @@ namespace sqlite_orm { } template - internal::using_t using_(F O::*p) { - return {p}; + internal::using_t using_(F O::*field) { + return {field}; } template - internal::using_t using_(internal::column_pointer cp) { - return {std::move(cp)}; + internal::using_t using_(internal::column_pointer field) { + return {std::move(field)}; } template @@ -983,9 +982,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto left_join(On on) { - return internal::left_join_t, On>{std::move(on)}; + return left_join, On>(std::move(on)); } #endif @@ -995,9 +994,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto join(On on) { - return internal::join_t, On>{std::move(on)}; + return join, On>(std::move(on)); } #endif @@ -1007,9 +1006,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto left_outer_join(On on) { - return internal::left_outer_join_t, On>{std::move(on)}; + return left_outer_join, On>(std::move(on)); } #endif @@ -1019,9 +1018,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto inner_join(On on) { - return internal::inner_join_t, On>{std::move(on)}; + return inner_join, On>(std::move(on)); } #endif diff --git a/dev/core_functions.h b/dev/core_functions.h index d6b467877..51a767e22 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -1845,6 +1845,17 @@ namespace sqlite_orm { return {}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * COUNT(*) with FROM function. Specified recordset will be serialized as + * a from argument. + */ + template + auto count() { + return count>(); + } +#endif + /** * AVG(X) aggregate function. */ diff --git a/dev/mapped_type_proxy.h b/dev/mapped_type_proxy.h index fd914d0b7..dca68216d 100644 --- a/dev/mapped_type_proxy.h +++ b/dev/mapped_type_proxy.h @@ -16,8 +16,13 @@ namespace sqlite_orm { template struct mapped_type_proxy : std::remove_const {}; - template - struct mapped_type_proxy> : std::remove_const> {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct mapped_type_proxy : R {}; +#endif + + template + struct mapped_type_proxy> : std::remove_const> {}; template using mapped_type_proxy_t = typename mapped_type_proxy::type; diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index 0fd9f1b45..50e309996 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -616,10 +616,21 @@ namespace sqlite_orm { */ template internal::get_t get(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get statement. + * T is an object type mapped to a storage. + * Usage: get(5); + */ + template + auto get(Ids... ids) { + return get>(std::forward(ids)...); + } +#endif + /** * Create a get pointer statement. * T is an object type mapped to a storage. @@ -627,8 +638,7 @@ namespace sqlite_orm { */ template internal::get_pointer_t get_pointer(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED @@ -639,8 +649,7 @@ namespace sqlite_orm { */ template internal::get_optional_t get_optional(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED @@ -673,17 +682,15 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create a get all statement. - * `alias` is an explicitly specified table alias of an object to be extracted. + * `als` is an explicitly specified table proxy of an object to be extracted. * `R` is the container return type, which must have a `R::push_back(T&&)` method, and defaults to `std::vector` * Usage: storage.get_all(...); */ - template>, + template>, class... Args> auto get_all(Args&&... conditions) { - using expression_type = internal::get_all_t, R, std::decay_t...>; - internal::validate_conditions(); - return expression_type{{std::forward(conditions)...}}; + return get_all, R>(std::forward(conditions)...); } #endif diff --git a/dev/select_constraints.h b/dev/select_constraints.h index f38ffc0e0..415ee9296 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -408,9 +408,9 @@ namespace sqlite_orm { * auto reportingTo = * storage.select(asterisk(), inner_join(on(m->*&Employee::reportsTo == &Employee::employeeId))); */ - template + template auto asterisk(bool definedOrder = false) { - return internal::asterisk_t>{definedOrder}; + return asterisk>(definedOrder); } #endif @@ -429,4 +429,11 @@ namespace sqlite_orm { internal::object_t object(bool definedOrder = false) { return {definedOrder}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto object(bool definedOrder = false) { + return object>(definedOrder); + } +#endif } diff --git a/dev/storage.h b/dev/storage.h index 85b1e8c33..a0ebb932e 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -319,18 +319,18 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * SELECT * routine. - * `alias` is an explicitly specified table alias of an object to be extracted. + * `als` is an explicitly specified table proxy of an object to be extracted. * `R` is the container return type, which must have a `R::push_back(O&&)` method, and defaults to `std::vector` * @return All objects stored in database. * @example: storage.get_all>(); - SELECT sqlite_schema.* FROM sqlite_master AS sqlite_schema */ - template>, + template>, class... Args> R get_all(Args&&... args) { - using A = decltype(alias); + using A = decltype(als); this->assert_mapped_type>(); - auto statement = this->prepare(sqlite_orm::get_all(std::forward(args)...)); + auto statement = this->prepare(sqlite_orm::get_all(std::forward(args)...)); return this->execute(statement); } #endif @@ -382,6 +382,13 @@ namespace sqlite_orm { return this->execute(statement); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto get(Ids... ids) { + return this->get>(std::forward(ids)...); + } +#endif + /** * The same as `get` function but doesn't throw an exception if noting found but returns std::unique_ptr * with null value. throws std::system_error in case of db error. @@ -428,8 +435,9 @@ namespace sqlite_orm { * SELECT COUNT(*) https://www.sqlite.org/lang_aggfunc.html#count * @return Number of O object in table. */ - template> + template int count(Args&&... args) { + using R = mapped_type_proxy_t; this->assert_mapped_type(); auto rows = this->select(sqlite_orm::count(), std::forward(args)...); if(!rows.empty()) { @@ -439,15 +447,25 @@ namespace sqlite_orm { } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + int count(Args&&... args) { + return this->count>(std::forward(args)...); + } +#endif + /** * SELECT COUNT(X) https://www.sqlite.org/lang_aggfunc.html#count * @param m member pointer to class mapped to the storage. * @return count of `m` values from database. */ - template - int count(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::count(m), std::forward(args)...); + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + int count(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::count(std::move(field)), std::forward(args)...); if(!rows.empty()) { return rows.front(); } else { @@ -460,10 +478,13 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return average value from database. */ - template - double avg(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::avg(m), std::forward(args)...); + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + double avg(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::avg(std::move(field)), std::forward(args)...); if(!rows.empty()) { return rows.front(); } else { @@ -471,9 +492,11 @@ namespace sqlite_orm { } } - template - std::string group_concat(F O::*m) { - return this->group_concat_internal(m, {}); + template< + class F, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field) { + return this->group_concat_internal(std::move(field), {}); } /** @@ -481,13 +504,14 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return group_concat query result. */ - template, - std::enable_if_t::value >= 1, bool> = true> - std::string group_concat(F O::*m, Args&&... args) { - return this->group_concat_internal(m, {}, std::forward(args)...); + template< + class F, + class... Args, + class Tuple = std::tuple, + std::enable_if_t::value >= 1, bool> = true, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field, Args&&... args) { + return this->group_concat_internal(std::move(field), {}, std::forward(args)...); } /** @@ -495,22 +519,28 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return group_concat query result. */ - template - std::string group_concat(F O::*m, std::string y, Args&&... args) { - return this->group_concat_internal(m, + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field, std::string y, Args&&... args) { + return this->group_concat_internal(std::move(field), std::make_unique(std::move(y)), std::forward(args)...); } - template - std::string group_concat(F O::*m, const char* y, Args&&... args) { + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field, const char* y, Args&&... args) { std::unique_ptr str; if(y) { str = std::make_unique(y); } else { str = std::make_unique(); } - return this->group_concat_internal(m, std::move(str), std::forward(args)...); + return this->group_concat_internal(std::move(field), std::move(str), std::forward(args)...); } /** @@ -518,10 +548,14 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return std::unique_ptr with max value or null if sqlite engine returned null. */ - template> - std::unique_ptr max(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::max(m), std::forward(args)...); + template< + class F, + class... Args, + class Ret = column_result_of_t, + std::enable_if_t, is_column_pointer>, bool> = true> + std::unique_ptr max(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::max(std::move(field)), std::forward(args)...); if(!rows.empty()) { return std::move(rows.front()); } else { @@ -534,10 +568,14 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return std::unique_ptr with min value or null if sqlite engine returned null. */ - template> - std::unique_ptr min(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::min(m), std::forward(args)...); + template< + class F, + class... Args, + class Ret = column_result_of_t, + std::enable_if_t, is_column_pointer>, bool> = true> + std::unique_ptr min(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::min(std::move(field)), std::forward(args)...); if(!rows.empty()) { return std::move(rows.front()); } else { @@ -550,11 +588,15 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return std::unique_ptr with sum value or null if sqlite engine returned null. */ - template> - std::unique_ptr sum(F O::*m, Args&&... args) { - this->assert_mapped_type(); + template< + class F, + class... Args, + class Ret = column_result_of_t, + std::enable_if_t, is_column_pointer>, bool> = true> + std::unique_ptr sum(F field, Args&&... args) { + this->assert_mapped_type>(); std::vector> rows = - this->select(sqlite_orm::sum(m), std::forward(args)...); + this->select(sqlite_orm::sum(std::move(field)), std::forward(args)...); if(!rows.empty()) { if(rows.front()) { return std::make_unique(std::move(*rows.front())); @@ -572,10 +614,13 @@ namespace sqlite_orm { * @return total value (the same as SUM but not nullable. More details here * https://www.sqlite.org/lang_aggfunc.html) */ - template - double total(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::total(m), std::forward(args)...); + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + double total(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::total(std::move(field)), std::forward(args)...); if(!rows.empty()) { return std::move(rows.front()); } else { diff --git a/dev/table_type_of.h b/dev/table_type_of.h index 32fab610f..5061b4815 100644 --- a/dev/table_type_of.h +++ b/dev/table_type_of.h @@ -20,6 +20,8 @@ namespace sqlite_orm { * - `table_type_of::type` is `User` * - `table_type_of::type` is `User` * - `table_type_of::type` is `User` + * - `table_type_of(&User::id))>::type` is `User` + * - `table_type_of*&User::id)>::type` is `User` */ template struct table_type_of; diff --git a/examples/column_aliases.cpp b/examples/column_aliases.cpp index 1cba0a2b1..fc52e26f6 100644 --- a/examples/column_aliases.cpp +++ b/examples/column_aliases.cpp @@ -66,7 +66,7 @@ void marvel_hero_ordered_by_o_pos() { // WHERE i > 0 // ORDER BY i auto rows = storage.select(columns(&MarvelHero::name, as(instr(&MarvelHero::abilities, "o"))), - where(i > c(0)), + where(i > 0), order_by(i)); for(auto& row: rows) { cout << get<0>(row) << '\t' << get<1>(row) << '\n'; @@ -86,9 +86,34 @@ void marvel_hero_ordered_by_o_pos() { cout << endl; } +void cpp20_column_pointer() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + struct Result { + int64 id; + time_t stamp; + }; + + struct LastResult : Result {}; + constexpr auto last_result = c(); + + auto storage = make_storage( + "", + make_table("result", make_column("id", &Result::id, primary_key()), make_column("stamp", &Result::stamp)), + make_table("last_result", + make_column("id", &LastResult::id, primary_key()), + make_column("stamp", &LastResult::stamp))); + storage.sync_schema(); + + // SELECT "last_result"."id", "last_result"."stamp" FROM "last_result" + std::string sql = storage.dump(select(columns(last_result->*&LastResult::id, last_result->*&LastResult::stamp))); + cout << sql << endl; +#endif +} + int main() { try { marvel_hero_ordered_by_o_pos(); + cpp20_column_pointer(); } catch(const system_error& e) { cout << "[" << e.code() << "] " << e.what(); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 638e8e64b..bb20e33b0 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1308,6 +1308,8 @@ namespace sqlite_orm { * - `table_type_of::type` is `User` * - `table_type_of::type` is `User` * - `table_type_of::type` is `User` + * - `table_type_of(&User::id))>::type` is `User` + * - `table_type_of*&User::id)>::type` is `User` */ template struct table_type_of; @@ -2931,7 +2933,7 @@ namespace sqlite_orm { // #include "alias_traits.h" -#include // std::remove_const, std::is_base_of, std::is_same +#include // std::remove_const, std::is_base_of, std::is_same, std::type_identity #ifdef SQLITE_ORM_WITH_CPP20_ALIASES #include #endif @@ -2983,6 +2985,24 @@ namespace sqlite_orm { template using is_table_alias = polyfill::bool_constant>; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. + */ + template + struct table_reference : std::type_identity {}; + + template + struct decay_table_reference : std::remove_const {}; + template + struct decay_table_reference> : std::type_identity {}; + template + struct decay_table_reference> : std::type_identity {}; + + template + using decay_table_reference_t = typename decay_table_reference::type; +#endif } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -3015,6 +3035,23 @@ 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. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + + template + concept orm_refers_to_table = (orm_table_reference || orm_table_alias); + + template + concept orm_refers_to_recordset = (orm_table_reference || orm_recordset_alias); + + template + concept orm_mapped_recordset = (orm_table_reference); #endif } @@ -3115,13 +3152,15 @@ namespace sqlite_orm { // #include "column_pointer.h" -#include // std::enable_if +#include // std::enable_if, std::remove_const #include // std::move // #include "functional/cxx_type_traits_polyfill.h" // #include "tags.h" +// #include "alias_traits.h" + namespace sqlite_orm { namespace internal { /** @@ -3151,10 +3190,48 @@ namespace sqlite_orm { * struct MyType : BaseType { ... }; * storage.select(column(&BaseType::id)); */ - template = true> - constexpr internal::column_pointer column(F field) { - return {std::move(field)}; + template = true> + constexpr internal::column_pointer column(F O::*field) { + static_assert(internal::is_field_of_v, "Column must be from derived class"); + return {field}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicitly refer to a column. + */ + template + constexpr auto column(F O::*field) { + using R = std::remove_const_t; + return column(field); + } + + /** + * Explicitly refer to a column. + */ + template + constexpr auto operator->*(const R& /*table*/, F O::*field) { + return column(field); + } + + /** + * Make table reference. + */ + template + requires(!orm_recordset_alias) + constexpr internal::table_reference column() { + return {}; } + + /** + * Make table reference. + */ + template + requires(!orm_recordset_alias) + constexpr internal::table_reference c() { + return {}; + } +#endif } // #include "tags.h" @@ -3990,10 +4067,9 @@ namespace sqlite_orm { * Explicit FROM function. Usage: * `storage.select(&User::id, from<"a"_alias.for_>());` */ - template + template auto from() { - static_assert(sizeof...(tables) > 0); - return internal::from_t...>{}; + return from...>(); } #endif @@ -4110,12 +4186,12 @@ namespace sqlite_orm { } template - internal::using_t using_(F O::*p) { - return {p}; + internal::using_t using_(F O::*field) { + return {field}; } template - internal::using_t using_(internal::column_pointer cp) { - return {std::move(cp)}; + internal::using_t using_(internal::column_pointer field) { + return {std::move(field)}; } template @@ -4139,9 +4215,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto left_join(On on) { - return internal::left_join_t, On>{std::move(on)}; + return left_join, On>(std::move(on)); } #endif @@ -4151,9 +4227,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto join(On on) { - return internal::join_t, On>{std::move(on)}; + return join, On>(std::move(on)); } #endif @@ -4163,9 +4239,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto left_outer_join(On on) { - return internal::left_outer_join_t, On>{std::move(on)}; + return left_outer_join, On>(std::move(on)); } #endif @@ -4175,9 +4251,9 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template + template auto inner_join(On on) { - return internal::inner_join_t, On>{std::move(on)}; + return inner_join, On>(std::move(on)); } #endif @@ -6694,6 +6770,17 @@ namespace sqlite_orm { return {}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * COUNT(*) with FROM function. Specified recordset will be serialized as + * a from argument. + */ + template + auto count() { + return count>(); + } +#endif + /** * AVG(X) aggregate function. */ @@ -7594,9 +7681,9 @@ namespace sqlite_orm { * auto reportingTo = * storage.select(asterisk(), inner_join(on(m->*&Employee::reportsTo == &Employee::employeeId))); */ - template + template auto asterisk(bool definedOrder = false) { - return internal::asterisk_t>{definedOrder}; + return asterisk>(definedOrder); } #endif @@ -7615,6 +7702,13 @@ namespace sqlite_orm { internal::object_t object(bool definedOrder = false) { return {definedOrder}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto object(bool definedOrder = false) { + return object>(definedOrder); + } +#endif } #pragma once @@ -10701,8 +10795,13 @@ namespace sqlite_orm { template struct mapped_type_proxy : std::remove_const {}; - template - struct mapped_type_proxy> : std::remove_const> {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct mapped_type_proxy : R {}; +#endif + + template + struct mapped_type_proxy> : std::remove_const> {}; template using mapped_type_proxy_t = typename mapped_type_proxy::type; @@ -12543,10 +12642,21 @@ namespace sqlite_orm { */ template internal::get_t get(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get statement. + * T is an object type mapped to a storage. + * Usage: get(5); + */ + template + auto get(Ids... ids) { + return get>(std::forward(ids)...); + } +#endif + /** * Create a get pointer statement. * T is an object type mapped to a storage. @@ -12554,8 +12664,7 @@ namespace sqlite_orm { */ template internal::get_pointer_t get_pointer(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED @@ -12566,8 +12675,7 @@ namespace sqlite_orm { */ template internal::get_optional_t get_optional(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED @@ -12600,17 +12708,15 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create a get all statement. - * `alias` is an explicitly specified table alias of an object to be extracted. + * `als` is an explicitly specified table proxy of an object to be extracted. * `R` is the container return type, which must have a `R::push_back(T&&)` method, and defaults to `std::vector` * Usage: storage.get_all(...); */ - template>, + template>, class... Args> auto get_all(Args&&... conditions) { - using expression_type = internal::get_all_t, R, std::decay_t...>; - internal::validate_conditions(); - return expression_type{{std::forward(conditions)...}}; + return get_all, R>(std::forward(conditions)...); } #endif @@ -18298,18 +18404,18 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * SELECT * routine. - * `alias` is an explicitly specified table alias of an object to be extracted. + * `als` is an explicitly specified table proxy of an object to be extracted. * `R` is the container return type, which must have a `R::push_back(O&&)` method, and defaults to `std::vector` * @return All objects stored in database. * @example: storage.get_all>(); - SELECT sqlite_schema.* FROM sqlite_master AS sqlite_schema */ - template>, + template>, class... Args> R get_all(Args&&... args) { - using A = decltype(alias); + using A = decltype(als); this->assert_mapped_type>(); - auto statement = this->prepare(sqlite_orm::get_all(std::forward(args)...)); + auto statement = this->prepare(sqlite_orm::get_all(std::forward(args)...)); return this->execute(statement); } #endif @@ -18361,6 +18467,13 @@ namespace sqlite_orm { return this->execute(statement); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto get(Ids... ids) { + return this->get>(std::forward(ids)...); + } +#endif + /** * The same as `get` function but doesn't throw an exception if noting found but returns std::unique_ptr * with null value. throws std::system_error in case of db error. @@ -18407,8 +18520,9 @@ namespace sqlite_orm { * SELECT COUNT(*) https://www.sqlite.org/lang_aggfunc.html#count * @return Number of O object in table. */ - template> + template int count(Args&&... args) { + using R = mapped_type_proxy_t; this->assert_mapped_type(); auto rows = this->select(sqlite_orm::count(), std::forward(args)...); if(!rows.empty()) { @@ -18418,15 +18532,25 @@ namespace sqlite_orm { } } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + int count(Args&&... args) { + return this->count>(std::forward(args)...); + } +#endif + /** * SELECT COUNT(X) https://www.sqlite.org/lang_aggfunc.html#count * @param m member pointer to class mapped to the storage. * @return count of `m` values from database. */ - template - int count(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::count(m), std::forward(args)...); + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + int count(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::count(std::move(field)), std::forward(args)...); if(!rows.empty()) { return rows.front(); } else { @@ -18439,10 +18563,13 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return average value from database. */ - template - double avg(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::avg(m), std::forward(args)...); + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + double avg(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::avg(std::move(field)), std::forward(args)...); if(!rows.empty()) { return rows.front(); } else { @@ -18450,9 +18577,11 @@ namespace sqlite_orm { } } - template - std::string group_concat(F O::*m) { - return this->group_concat_internal(m, {}); + template< + class F, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field) { + return this->group_concat_internal(std::move(field), {}); } /** @@ -18460,13 +18589,14 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return group_concat query result. */ - template, - std::enable_if_t::value >= 1, bool> = true> - std::string group_concat(F O::*m, Args&&... args) { - return this->group_concat_internal(m, {}, std::forward(args)...); + template< + class F, + class... Args, + class Tuple = std::tuple, + std::enable_if_t::value >= 1, bool> = true, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field, Args&&... args) { + return this->group_concat_internal(std::move(field), {}, std::forward(args)...); } /** @@ -18474,22 +18604,28 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return group_concat query result. */ - template - std::string group_concat(F O::*m, std::string y, Args&&... args) { - return this->group_concat_internal(m, + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field, std::string y, Args&&... args) { + return this->group_concat_internal(std::move(field), std::make_unique(std::move(y)), std::forward(args)...); } - template - std::string group_concat(F O::*m, const char* y, Args&&... args) { + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + std::string group_concat(F field, const char* y, Args&&... args) { std::unique_ptr str; if(y) { str = std::make_unique(y); } else { str = std::make_unique(); } - return this->group_concat_internal(m, std::move(str), std::forward(args)...); + return this->group_concat_internal(std::move(field), std::move(str), std::forward(args)...); } /** @@ -18497,10 +18633,14 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return std::unique_ptr with max value or null if sqlite engine returned null. */ - template> - std::unique_ptr max(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::max(m), std::forward(args)...); + template< + class F, + class... Args, + class Ret = column_result_of_t, + std::enable_if_t, is_column_pointer>, bool> = true> + std::unique_ptr max(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::max(std::move(field)), std::forward(args)...); if(!rows.empty()) { return std::move(rows.front()); } else { @@ -18513,10 +18653,14 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return std::unique_ptr with min value or null if sqlite engine returned null. */ - template> - std::unique_ptr min(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::min(m), std::forward(args)...); + template< + class F, + class... Args, + class Ret = column_result_of_t, + std::enable_if_t, is_column_pointer>, bool> = true> + std::unique_ptr min(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::min(std::move(field)), std::forward(args)...); if(!rows.empty()) { return std::move(rows.front()); } else { @@ -18529,11 +18673,15 @@ namespace sqlite_orm { * @param m is a class member pointer (the same you passed into make_column). * @return std::unique_ptr with sum value or null if sqlite engine returned null. */ - template> - std::unique_ptr sum(F O::*m, Args&&... args) { - this->assert_mapped_type(); + template< + class F, + class... Args, + class Ret = column_result_of_t, + std::enable_if_t, is_column_pointer>, bool> = true> + std::unique_ptr sum(F field, Args&&... args) { + this->assert_mapped_type>(); std::vector> rows = - this->select(sqlite_orm::sum(m), std::forward(args)...); + this->select(sqlite_orm::sum(std::move(field)), std::forward(args)...); if(!rows.empty()) { if(rows.front()) { return std::make_unique(std::move(*rows.front())); @@ -18551,10 +18699,13 @@ namespace sqlite_orm { * @return total value (the same as SUM but not nullable. More details here * https://www.sqlite.org/lang_aggfunc.html) */ - template - double total(F O::*m, Args&&... args) { - this->assert_mapped_type(); - auto rows = this->select(sqlite_orm::total(m), std::forward(args)...); + template< + class F, + class... Args, + std::enable_if_t, is_column_pointer>, bool> = true> + double total(F field, Args&&... args) { + this->assert_mapped_type>(); + auto rows = this->select(sqlite_orm::total(std::move(field)), std::forward(args)...); if(!rows.empty()) { return std::move(rows.front()); } else { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b15a6baf6..a0e811893 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(unit_tests static_tests/aggregate_function_return_types.cpp static_tests/core_function_return_types.cpp static_tests/column_result_t.cpp + static_tests/column_pointer.cpp static_tests/alias.cpp static_tests/is_primary_key_insertable.cpp static_tests/is_column_with_insertable_primary_key.cpp diff --git a/tests/builtin_tables.cpp b/tests/builtin_tables.cpp index 8dce23c74..e4e38d0e9 100644 --- a/tests/builtin_tables.cpp +++ b/tests/builtin_tables.cpp @@ -16,6 +16,12 @@ TEST_CASE("builtin tables") { STATIC_REQUIRE(std::is_same_v); REQUIRE_THAT(schemaRows, Equals(masterRows)); + + constexpr auto schema = c(); + auto schemaRows2 = storage.get_all(); + + STATIC_REQUIRE(std::is_same_v); + REQUIRE_THAT(schemaRows2, Equals(masterRows)); #endif } diff --git a/tests/prepared_statement_tests/get_all.cpp b/tests/prepared_statement_tests/get_all.cpp index 915ba108b..eec69cd70 100644 --- a/tests/prepared_statement_tests/get_all.cpp +++ b/tests/prepared_statement_tests/get_all.cpp @@ -215,7 +215,13 @@ TEST_CASE("Prepared get all") { } } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - SECTION("from alias") { + SECTION("from table reference") { + constexpr auto schema = c(); + auto statement = storage.prepare(get_all(where(schema->*&sqlite_master::type == "table"))); + auto str = storage.dump(statement); + testSerializing(statement); + } + SECTION("from aliased table") { auto statement = storage.prepare(get_all(where(sqlite_schema->*&sqlite_master::type == "table"))); auto str = storage.dump(statement); diff --git a/tests/statement_serializer_tests/aggregate_functions.cpp b/tests/statement_serializer_tests/aggregate_functions.cpp index 76f06c63a..d1f9726b0 100644 --- a/tests/statement_serializer_tests/aggregate_functions.cpp +++ b/tests/statement_serializer_tests/aggregate_functions.cpp @@ -86,6 +86,14 @@ TEST_CASE("statement_serializer aggregate functions") { value = serialize(expression, context); expected = R"(COUNT(*) FILTER (WHERE ("id" < 10)))"; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("with table reference") { + constexpr auto user = c(); + auto expression = count(); + value = serialize(expression, context); + expected = R"(COUNT(*))"; + } +#endif } } SECTION("group_concat(X)") { diff --git a/tests/statement_serializer_tests/select_constraints.cpp b/tests/statement_serializer_tests/select_constraints.cpp index 60c51c45e..4b7321ac3 100644 --- a/tests/statement_serializer_tests/select_constraints.cpp +++ b/tests/statement_serializer_tests/select_constraints.cpp @@ -73,6 +73,12 @@ TEST_CASE("statement_serializer select constraints") { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES { + SECTION("with table reference") { + constexpr auto user = c(); + auto expression = from(); + value = serialize(expression, context); + expected = R"(FROM "users")"; + } SECTION("with alias 2") { auto expression = from.for_()>(); value = serialize(expression, context); diff --git a/tests/static_tests/alias.cpp b/tests/static_tests/alias.cpp index c7548a2d7..b0e8e4dbd 100644 --- a/tests/static_tests/alias.cpp +++ b/tests/static_tests/alias.cpp @@ -9,6 +9,7 @@ using internal::as_t; using internal::column_alias; using internal::column_pointer; using internal::recordset_alias; +using internal::using_t; template void do_assert() { @@ -20,6 +21,14 @@ void runTest(ColAlias /*colRef*/) { do_assert(); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +template +concept storage_table_alias_callable = requires(S& storage) { + { storage.get_all() }; + { storage.count() }; +}; +#endif + TEST_CASE("aliases") { struct User { int id; @@ -59,4 +68,30 @@ TEST_CASE("aliases") { runTest, column_pointer>>(d_alias->*&User::id); #endif } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("table alias expressions") { + constexpr auto derived_user = c(); + constexpr auto d_alias = "d"_alias.for_(); + using d_alias_type = decltype("d"_alias.for_()); + runTest>(from()); + runTest>(asterisk()); + runTest>(object()); + runTest>(count()); + 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_alias_callable); + } +#endif } diff --git a/tests/static_tests/column_pointer.cpp b/tests/static_tests/column_pointer.cpp new file mode 100644 index 000000000..d0d4950a4 --- /dev/null +++ b/tests/static_tests/column_pointer.cpp @@ -0,0 +1,119 @@ +#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 +} diff --git a/tests/tests.cpp b/tests/tests.cpp index d0e13dc57..dd5bdc63d 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -3,10 +3,7 @@ #include // std::vector #include // std::string -#include // std::unique_ptr #include // remove -#include // std::iota -#include // std::fill using namespace sqlite_orm;