diff --git a/dev/column_names_getter.h b/dev/column_names_getter.h index 63ea1082a..05820ed83 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -30,7 +30,7 @@ namespace sqlite_orm { const Ctx& context) { if(definedOrder) { auto& table = pick_table>(context.db_objects); - collectedExpressions.reserve(collectedExpressions.size() + table.count_columns_amount()); + collectedExpressions.reserve(collectedExpressions.size() + table.template count_of()); table.for_each_column([qualified = !context.skip_table_name, &tableName = table.name, &collectedExpressions](const column_identifier& column) { diff --git a/dev/constraints.h b/dev/constraints.h index 4d54c81ad..67c36f32e 100644 --- a/dev/constraints.h +++ b/dev/constraints.h @@ -44,12 +44,15 @@ namespace sqlite_orm { }; template - struct primary_key_with_autoincrement { + struct primary_key_with_autoincrement : T { using primary_key_type = T; - primary_key_type primary_key; - - primary_key_with_autoincrement(primary_key_type primary_key_) : primary_key(primary_key_) {} + const primary_key_type& as_base() const { + return *this; + } +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + primary_key_with_autoincrement(primary_key_type primary_key) : primary_key_type{primary_key} {} +#endif }; /** @@ -65,7 +68,7 @@ namespace sqlite_orm { columns_tuple columns; - primary_key_t(decltype(columns) columns) : columns(std::move(columns)) {} + primary_key_t(columns_tuple columns) : columns(std::move(columns)) {} self asc() const { auto res = *this; @@ -379,6 +382,7 @@ namespace sqlite_orm { expression_type expression; }; +#if SQLITE_VERSION_NUMBER >= 3031000 struct basic_generated_always { enum class storage_type { not_specified, @@ -411,6 +415,7 @@ namespace sqlite_orm { return {std::move(this->expression), this->full, storage_type::stored}; } }; +#endif struct null_t {}; @@ -420,28 +425,32 @@ namespace sqlite_orm { namespace internal { template - SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = +#if SQLITE_VERSION_NUMBER >= 3006019 + polyfill::is_specialization_of_v; +#else + false; +#endif template using is_foreign_key = polyfill::bool_constant>; template - struct is_primary_key : std::false_type {}; - - template - struct is_primary_key> : std::true_type {}; - - template - struct is_primary_key> : std::true_type {}; + SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = std::is_base_of::value; template - SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = is_primary_key::value; + using is_primary_key = polyfill::bool_constant>; template - using is_generated_always = polyfill::is_specialization_of; + SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = +#if SQLITE_VERSION_NUMBER >= 3031000 + polyfill::is_specialization_of_v; +#else + false; +#endif template - SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always::value; + using is_generated_always = polyfill::bool_constant>; /** * PRIMARY KEY INSERTABLE traits. @@ -458,22 +467,16 @@ namespace sqlite_orm { }; template - using is_constraint = - mpl::instantiate, - check_if, - check_if_is_type, - check_if_is_type, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_type, -#if SQLITE_VERSION_NUMBER >= 3031000 - check_if, -#endif - // dummy tail because of SQLITE_VERSION_NUMBER checks above - mpl::always>, - T>; + using is_constraint = mpl::instantiate, + check_if, + check_if_is_type, + check_if_is_type, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_type, + check_if>, + T>; } #if SQLITE_VERSION_NUMBER >= 3031000 diff --git a/dev/functional/index_sequence_util.h b/dev/functional/index_sequence_util.h index b13343663..7d7a40d73 100644 --- a/dev/functional/index_sequence_util.h +++ b/dev/functional/index_sequence_util.h @@ -1,6 +1,6 @@ #pragma once -#include // std::index_sequence, std::make_index_sequence +#include // std::index_sequence #include "../functional/cxx_universal.h" diff --git a/dev/implementations/storage_definitions.h b/dev/implementations/storage_definitions.h index 89e464ef2..30d498b13 100644 --- a/dev/implementations/storage_definitions.h +++ b/dev/implementations/storage_definitions.h @@ -120,7 +120,7 @@ namespace sqlite_orm { const Table& table, const std::vector& columnsToIgnore) const { // must ignore generated columns std::vector> columnNames; - columnNames.reserve(table.count_columns_amount()); + columnNames.reserve(table.template count_of()); table.for_each_column([&columnNames, &columnsToIgnore](const column_identifier& column) { auto& columnName = column.name; #if __cpp_lib_ranges >= 201911L diff --git a/dev/implementations/table_definitions.h b/dev/implementations/table_definitions.h index 8e3c24268..a869d2272 100644 --- a/dev/implementations/table_definitions.h +++ b/dev/implementations/table_definitions.h @@ -31,7 +31,7 @@ namespace sqlite_orm { column.is_not_null(), std::move(dft), column.template is(), - column.is_generated()); + column.template is()); }); auto compositeKeyColumnNames = this->composite_key_columns_names(); for(size_t i = 0; i < compositeKeyColumnNames.size(); ++i) { diff --git a/dev/schema/column.h b/dev/schema/column.h index b723a5205..f7654a1cf 100644 --- a/dev/schema/column.h +++ b/dev/schema/column.h @@ -76,21 +76,13 @@ namespace sqlite_orm { constraints_type constraints; /** - * Checks whether contraints are of trait `Trait` + * Checks whether contraints contain specified type. */ template class Trait> - constexpr bool is() const { + constexpr static bool is() { return tuple_has::value; } - constexpr bool is_generated() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - return is(); -#else - return false; -#endif - } - /** * Simplified interface for `DEFAULT` constraint * @return string representation of default value if it exists otherwise nullptr diff --git a/dev/schema/table.h b/dev/schema/table.h index c8a1a55f2..32ca06970 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -15,6 +15,7 @@ #include "../tuple_helper/tuple_filter.h" #include "../tuple_helper/tuple_traits.h" #include "../tuple_helper/tuple_iteration.h" +#include "../tuple_helper/tuple_transformer.h" #include "../member_traits/member_traits.h" #include "../typed_comparator.h" #include "../type_traits.h" @@ -43,6 +44,7 @@ namespace sqlite_orm { using elements_type = std::tuple; static constexpr bool is_without_rowid_v = WithoutRowId; + using is_without_rowid = polyfill::bool_constant; elements_type elements; @@ -56,16 +58,31 @@ namespace sqlite_orm { return {this->name, this->elements}; } - /** - * Returns foreign keys count in table definition + /* + * Returns the number of elements of the specified type. */ - constexpr int foreign_keys_count() const { -#if SQLITE_VERSION_NUMBER >= 3006019 - using fk_index_sequence = filter_tuple_sequence_t; - return int(fk_index_sequence::size()); -#else - return 0; -#endif + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using filtered_index_sequence = col_index_sequence_with; + return int(filtered_index_sequence::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); } /** @@ -188,27 +205,6 @@ namespace sqlite_orm { return res; } - /** - * Counts and returns amount of columns without GENERATED ALWAYS constraints. Skips table constraints. - */ - constexpr int non_generated_columns_count() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - using non_generated_col_index_sequence = - col_index_sequence_excluding; - return int(non_generated_col_index_sequence::size()); -#else - return this->count_columns_amount(); -#endif - } - - /** - * Counts and returns amount of columns. Skips constraints. - */ - constexpr int count_columns_amount() const { - using col_index_sequence = filter_tuple_sequence_t; - return int(col_index_sequence::size()); - } - /** * Call passed lambda with all defined foreign keys. * @param lambda Lambda called for each column. Function signature: `void(auto& column)` diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index ca380c76d..ddaad6feb 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -907,13 +907,13 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = primary_key_with_autoincrement; + template + struct statement_serializer, void> { + using statement_type = primary_key_with_autoincrement; template std::string operator()(const statement_type& statement, const Ctx& context) const { - return serialize(statement.primary_key, context) + " AUTOINCREMENT"; + return serialize(statement.as_base(), context) + " AUTOINCREMENT"; } }; @@ -1430,7 +1430,7 @@ namespace sqlite_orm { ss << "REPLACE INTO " << streaming_identifier(table.name) << " (" << streaming_non_generated_column_names(table) << ")"; const auto valuesCount = std::distance(rep.range.first, rep.range.second); - const auto columnsCount = table.non_generated_columns_count(); + const auto columnsCount = table.template count_of_columns_excluding(); ss << " VALUES " << streaming_values_placeholders(columnsCount, valuesCount); return ss.str(); } diff --git a/dev/storage.h b/dev/storage.h index 854a8c385..729d9ccc0 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -20,6 +20,7 @@ #include "functional/mpl.h" #include "tuple_helper/tuple_traits.h" #include "tuple_helper/tuple_filter.h" +#include "tuple_helper/tuple_transformer.h" #include "tuple_helper/tuple_iteration.h" #include "type_traits.h" #include "alias.h" @@ -171,6 +172,27 @@ namespace sqlite_orm { "type is not mapped to storage"); } + template + void assert_updatable_type() const { +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + using Table = storage_pick_table_t; + using elements_type = elements_type_t; + using col_index_sequence = filter_tuple_sequence_t; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + constexpr size_t dedicatedPrimaryKeyColumnsCount = + nested_tuple_size_for_t::value; + + constexpr size_t primaryKeyColumnsCount = + dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); + constexpr ptrdiff_t nonPrimaryKeysColumnsCount = col_index_sequence::size() - primaryKeyColumnsCount; + static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); + static_assert( + nonPrimaryKeysColumnsCount > 0, + "A table with only primary keys cannot be updated. You need at least 1 non-primary key column"); +#endif + } + template, std::enable_if_t = true> @@ -1081,8 +1103,11 @@ namespace sqlite_orm { #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template - prepared_statement_t> prepare(update_t upd) { - return prepare_impl>(std::move(upd)); + prepared_statement_t> prepare(update_t statement) { + using object_type = typename expression_object_type::type; + this->assert_mapped_type(); + this->assert_updatable_type(); + return prepare_impl>(std::move(statement)); } template diff --git a/dev/storage_impl.h b/dev/storage_impl.h index eb0d8ea07..5c923917b 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -21,7 +21,7 @@ namespace sqlite_orm { int foreign_keys_count(const DBOs& dbObjects) { int res = 0; iterate_tuple(dbObjects, tables_index_sequence{}, [&res](const auto& table) { - res += table.foreign_keys_count(); + res += table.template count_of(); }); return res; } diff --git a/dev/tuple_helper/tuple_filter.h b/dev/tuple_helper/tuple_filter.h index 2fea55d8e..0cd049d67 100644 --- a/dev/tuple_helper/tuple_filter.h +++ b/dev/tuple_helper/tuple_filter.h @@ -3,7 +3,7 @@ #include // std::integral_constant, std::index_sequence, std::conditional, std::declval #include // std::tuple -#include "../functional/cxx_universal.h" +#include "../functional/cxx_universal.h" // ::size_t #include "../functional/index_sequence_util.h" namespace sqlite_orm { diff --git a/dev/tuple_helper/tuple_iteration.h b/dev/tuple_helper/tuple_iteration.h index a0154a927..7c40910fe 100644 --- a/dev/tuple_helper/tuple_iteration.h +++ b/dev/tuple_helper/tuple_iteration.h @@ -1,12 +1,10 @@ #pragma once #include // std::tuple, std::get, std::tuple_element, std::tuple_size -#include // std::remove_reference, std::index_sequence, std::make_index_sequence +#include // std::remove_reference, std::index_sequence, std::make_index_sequence, std::forward, std::move #include // std::forward, std::move #include "../functional/cxx_universal.h" // ::size_t -#include "../functional/cxx_type_traits_polyfill.h" -#include "../functional/cxx_functional_polyfill.h" namespace sqlite_orm { namespace internal { @@ -92,19 +90,6 @@ namespace sqlite_orm { iterate_tuple(std::make_index_sequence::value>{}, std::forward(lambda)); } - template - R create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { - return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; - } - - template - R create_from_tuple(Tpl&& tpl, Projection project = {}) { - return create_from_tuple( - std::forward(tpl), - std::make_index_sequence>::value>{}, - std::forward(project)); - } - template class Base, class L> struct lambda_as_template_base : L { #ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED diff --git a/dev/tuple_helper/tuple_transformer.h b/dev/tuple_helper/tuple_transformer.h index 9c7a73c43..2e3b723a5 100644 --- a/dev/tuple_helper/tuple_transformer.h +++ b/dev/tuple_helper/tuple_transformer.h @@ -1,7 +1,11 @@ #pragma once -#include // std::tuple +#include // std::remove_reference, std::common_type, std::index_sequence, std::make_index_sequence, std::forward, std::move, std::integral_constant, std::declval +#include // std::tuple_size, std::get +#include "../functional/cxx_universal.h" // ::size_t +#include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/cxx_functional_polyfill.h" #include "../functional/mpl.h" namespace sqlite_orm { @@ -10,9 +14,9 @@ namespace sqlite_orm { template class Op> struct tuple_transformer; - template class Op> - struct tuple_transformer, Op> { - using type = std::tuple...>; + template class Pack, class... Types, template class Op> + struct tuple_transformer, Op> { + using type = Pack...>; }; /* @@ -22,5 +26,81 @@ namespace sqlite_orm { */ template class Op> using transform_tuple_t = typename tuple_transformer::type; + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + /* + * Apply a projection to a tuple's elements filtered by the specified indexes, and combine the results. + * + * @note It's a glorified version of `std::apply()` and a variant of `std::accumulate()`. + * It combines filtering the tuple (indexes), transforming the elements (projection) and finally applying the callable (combine). + */ + template + constexpr auto recombine_tuple(CombineOp combine, + const Tpl& tpl, + std::index_sequence, + Projector project, + Init initial) { + return combine(initial, polyfill::invoke(project, std::get(tpl))...); + } + + /* + * Apply a projection to a tuple's elements, and combine the results. + * + * @note It's a glorified version of `std::apply()` and a variant of `std::accumulate()`. + * It combines filtering the tuple (indexes), transforming the elements (projection) and finally applying the callable (combine). + */ + template + constexpr auto recombine_tuple(CombineOp combine, const Tpl& tpl, Projector project, Init initial) { + return recombine_tuple(std::move(combine), + std::forward(tpl), + std::make_index_sequence::value>{}, + std::move(project), + std::move(initial)); + } + + /* + * Function object that takes integral constants and returns the sum of their values as an integral constant. + * Because it's a "transparent" functor, it must be called with at least one argument, otherwise it cannot deduce the integral constant type. + */ + struct plus_fold_integrals { + template + constexpr auto operator()(const Integrals&...) const { + using integral_type = std::common_type_t; + return std::integral_constant{}; + } + }; + + /* + * Function object that takes a type, applies a projection on it, and returns the tuple size of the projected type (as an integral constant). + * The projection is applied on the argument type, not the argument value/object. + */ + template class NestedProject> + struct project_nested_tuple_size { + template + constexpr auto operator()(const T&) const { + return typename std::tuple_size>::type{}; + } + }; + + template class NestedProject, class Tpl, class IdxSeq> + using nested_tuple_size_for_t = decltype(recombine_tuple(plus_fold_integrals{}, + std::declval(), + IdxSeq{}, + project_nested_tuple_size{}, + std::integral_constant{})); +#endif + + template + R create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { + return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; + } + + template + R create_from_tuple(Tpl&& tpl, Projection project = {}) { + return create_from_tuple( + std::forward(tpl), + std::make_index_sequence>::value>{}, + std::forward(project)); + } } } diff --git a/dev/type_traits.h b/dev/type_traits.h index 64bf1ccdb..dac8df271 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -49,6 +49,9 @@ namespace sqlite_orm { template using constraints_type_t = typename T::constraints_type; + template + using columns_tuple_t = typename T::columns_tuple; + template using object_type_t = typename T::object_type; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 95a4a61fe..eb3f56d88 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -390,6 +390,9 @@ namespace sqlite_orm { template using constraints_type_t = typename T::constraints_type; + template + using columns_tuple_t = typename T::columns_tuple; + template using object_type_t = typename T::object_type; @@ -1129,10 +1132,10 @@ namespace sqlite_orm { #include // std::tuple // #include "../functional/cxx_universal.h" - +// ::size_t // #include "../functional/index_sequence_util.h" -#include // std::index_sequence, std::make_index_sequence +#include // std::index_sequence // #include "../functional/cxx_universal.h" @@ -1348,12 +1351,15 @@ namespace sqlite_orm { }; template - struct primary_key_with_autoincrement { + struct primary_key_with_autoincrement : T { using primary_key_type = T; - primary_key_type primary_key; - - primary_key_with_autoincrement(primary_key_type primary_key_) : primary_key(primary_key_) {} + const primary_key_type& as_base() const { + return *this; + } +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + primary_key_with_autoincrement(primary_key_type primary_key) : primary_key_type{primary_key} {} +#endif }; /** @@ -1369,7 +1375,7 @@ namespace sqlite_orm { columns_tuple columns; - primary_key_t(decltype(columns) columns) : columns(std::move(columns)) {} + primary_key_t(columns_tuple columns) : columns(std::move(columns)) {} self asc() const { auto res = *this; @@ -1683,6 +1689,7 @@ namespace sqlite_orm { expression_type expression; }; +#if SQLITE_VERSION_NUMBER >= 3031000 struct basic_generated_always { enum class storage_type { not_specified, @@ -1715,6 +1722,7 @@ namespace sqlite_orm { return {std::move(this->expression), this->full, storage_type::stored}; } }; +#endif struct null_t {}; @@ -1724,28 +1732,32 @@ namespace sqlite_orm { namespace internal { template - SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = +#if SQLITE_VERSION_NUMBER >= 3006019 + polyfill::is_specialization_of_v; +#else + false; +#endif template using is_foreign_key = polyfill::bool_constant>; template - struct is_primary_key : std::false_type {}; - - template - struct is_primary_key> : std::true_type {}; + SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = std::is_base_of::value; template - struct is_primary_key> : std::true_type {}; + using is_primary_key = polyfill::bool_constant>; template - SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = is_primary_key::value; - - template - using is_generated_always = polyfill::is_specialization_of; + SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = +#if SQLITE_VERSION_NUMBER >= 3031000 + polyfill::is_specialization_of_v; +#else + false; +#endif template - SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always::value; + using is_generated_always = polyfill::bool_constant>; /** * PRIMARY KEY INSERTABLE traits. @@ -1762,22 +1774,16 @@ namespace sqlite_orm { }; template - using is_constraint = - mpl::instantiate, - check_if, - check_if_is_type, - check_if_is_type, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_type, -#if SQLITE_VERSION_NUMBER >= 3031000 - check_if, -#endif - // dummy tail because of SQLITE_VERSION_NUMBER checks above - mpl::always>, - T>; + using is_constraint = mpl::instantiate, + check_if, + check_if_is_type, + check_if_is_type, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_type, + check_if>, + T>; } #if SQLITE_VERSION_NUMBER >= 3031000 @@ -2479,21 +2485,13 @@ namespace sqlite_orm { constraints_type constraints; /** - * Checks whether contraints are of trait `Trait` + * Checks whether contraints contain specified type. */ template class Trait> - constexpr bool is() const { + constexpr static bool is() { return tuple_has::value; } - constexpr bool is_generated() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - return is(); -#else - return false; -#endif - } - /** * Simplified interface for `DEFAULT` constraint * @return string representation of default value if it exists otherwise nullptr @@ -9670,14 +9668,11 @@ 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 +#include // std::remove_reference, std::index_sequence, std::make_index_sequence, std::forward, std::move #include // std::forward, std::move // #include "../functional/cxx_universal.h" // ::size_t -// #include "../functional/cxx_type_traits_polyfill.h" - -// #include "../functional/cxx_functional_polyfill.h" namespace sqlite_orm { namespace internal { @@ -9763,19 +9758,6 @@ namespace sqlite_orm { iterate_tuple(std::make_index_sequence::value>{}, std::forward(lambda)); } - template - R create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { - return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; - } - - template - R create_from_tuple(Tpl&& tpl, Projection project = {}) { - return create_from_tuple( - std::forward(tpl), - std::make_index_sequence>::value>{}, - std::forward(project)); - } - template class Base, class L> struct lambda_as_template_base : L { #ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED @@ -9805,6 +9787,116 @@ namespace sqlite_orm { } } +// #include "../tuple_helper/tuple_transformer.h" + +#include // std::remove_reference, std::common_type, std::index_sequence, std::make_index_sequence, std::forward, std::move, std::integral_constant, std::declval +#include // std::tuple_size, std::get + +// #include "../functional/cxx_universal.h" +// ::size_t +// #include "../functional/cxx_type_traits_polyfill.h" + +// #include "../functional/cxx_functional_polyfill.h" + +// #include "../functional/mpl.h" + +namespace sqlite_orm { + namespace internal { + + template class Op> + struct tuple_transformer; + + template class Pack, class... Types, template class Op> + struct tuple_transformer, Op> { + using type = Pack...>; + }; + + /* + * Transform specified tuple. + * + * `Op` is a metafunction operation. + */ + template class Op> + using transform_tuple_t = typename tuple_transformer::type; + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + /* + * Apply a projection to a tuple's elements filtered by the specified indexes, and combine the results. + * + * @note It's a glorified version of `std::apply()` and a variant of `std::accumulate()`. + * It combines filtering the tuple (indexes), transforming the elements (projection) and finally applying the callable (combine). + */ + template + constexpr auto recombine_tuple(CombineOp combine, + const Tpl& tpl, + std::index_sequence, + Projector project, + Init initial) { + return combine(initial, polyfill::invoke(project, std::get(tpl))...); + } + + /* + * Apply a projection to a tuple's elements, and combine the results. + * + * @note It's a glorified version of `std::apply()` and a variant of `std::accumulate()`. + * It combines filtering the tuple (indexes), transforming the elements (projection) and finally applying the callable (combine). + */ + template + constexpr auto recombine_tuple(CombineOp combine, const Tpl& tpl, Projector project, Init initial) { + return recombine_tuple(std::move(combine), + std::forward(tpl), + std::make_index_sequence::value>{}, + std::move(project), + std::move(initial)); + } + + /* + * Function object that takes integral constants and returns the sum of their values as an integral constant. + * Because it's a "transparent" functor, it must be called with at least one argument, otherwise it cannot deduce the integral constant type. + */ + struct plus_fold_integrals { + template + constexpr auto operator()(const Integrals&...) const { + using integral_type = std::common_type_t; + return std::integral_constant{}; + } + }; + + /* + * Function object that takes a type, applies a projection on it, and returns the tuple size of the projected type (as an integral constant). + * The projection is applied on the argument type, not the argument value/object. + */ + template class NestedProject> + struct project_nested_tuple_size { + template + constexpr auto operator()(const T&) const { + return typename std::tuple_size>::type{}; + } + }; + + template class NestedProject, class Tpl, class IdxSeq> + using nested_tuple_size_for_t = decltype(recombine_tuple(plus_fold_integrals{}, + std::declval(), + IdxSeq{}, + project_nested_tuple_size{}, + std::integral_constant{})); +#endif + + template + R create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { + return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; + } + + template + R create_from_tuple(Tpl&& tpl, Projection project = {}) { + return create_from_tuple( + std::forward(tpl), + std::make_index_sequence>::value>{}, + std::forward(project)); + } + } +} + // #include "../member_traits/member_traits.h" // #include "../typed_comparator.h" @@ -9838,6 +9930,7 @@ namespace sqlite_orm { using elements_type = std::tuple; static constexpr bool is_without_rowid_v = WithoutRowId; + using is_without_rowid = polyfill::bool_constant; elements_type elements; @@ -9851,16 +9944,31 @@ namespace sqlite_orm { return {this->name, this->elements}; } - /** - * Returns foreign keys count in table definition + /* + * Returns the number of elements of the specified type. */ - constexpr int foreign_keys_count() const { -#if SQLITE_VERSION_NUMBER >= 3006019 - using fk_index_sequence = filter_tuple_sequence_t; - return int(fk_index_sequence::size()); -#else - return 0; -#endif + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using filtered_index_sequence = col_index_sequence_with; + return int(filtered_index_sequence::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); } /** @@ -9983,27 +10091,6 @@ namespace sqlite_orm { return res; } - /** - * Counts and returns amount of columns without GENERATED ALWAYS constraints. Skips table constraints. - */ - constexpr int non_generated_columns_count() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - using non_generated_col_index_sequence = - col_index_sequence_excluding; - return int(non_generated_col_index_sequence::size()); -#else - return this->count_columns_amount(); -#endif - } - - /** - * Counts and returns amount of columns. Skips constraints. - */ - constexpr int count_columns_amount() const { - using col_index_sequence = filter_tuple_sequence_t; - return int(col_index_sequence::size()); - } - /** * Call passed lambda with all defined foreign keys. * @param lambda Lambda called for each column. Function signature: `void(auto& column)` @@ -10383,7 +10470,7 @@ namespace sqlite_orm { int foreign_keys_count(const DBOs& dbObjects) { int res = 0; iterate_tuple(dbObjects, tables_index_sequence{}, [&res](const auto& table) { - res += table.foreign_keys_count(); + res += table.template count_of(); }); return res; } @@ -10483,6 +10570,8 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_filter.h" +// #include "tuple_helper/tuple_transformer.h" + // #include "tuple_helper/tuple_iteration.h" // #include "type_traits.h" @@ -10594,31 +10683,6 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_transformer.h" -#include // std::tuple - -// #include "../functional/mpl.h" - -namespace sqlite_orm { - namespace internal { - - template class Op> - struct tuple_transformer; - - template class Op> - struct tuple_transformer, Op> { - using type = std::tuple...>; - }; - - /* - * Transform specified tuple. - * - * `Op` is a metafunction operation. - */ - template class Op> - using transform_tuple_t = typename tuple_transformer::type; - } -} - // #include "type_traits.h" // #include "storage_lookup.h" @@ -15584,7 +15648,7 @@ namespace sqlite_orm { const Ctx& context) { if(definedOrder) { auto& table = pick_table>(context.db_objects); - collectedExpressions.reserve(collectedExpressions.size() + table.count_columns_amount()); + collectedExpressions.reserve(collectedExpressions.size() + table.template count_of()); table.for_each_column([qualified = !context.skip_table_name, &tableName = table.name, &collectedExpressions](const column_identifier& column) { @@ -16624,13 +16688,13 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = primary_key_with_autoincrement; + template + struct statement_serializer, void> { + using statement_type = primary_key_with_autoincrement; template std::string operator()(const statement_type& statement, const Ctx& context) const { - return serialize(statement.primary_key, context) + " AUTOINCREMENT"; + return serialize(statement.as_base(), context) + " AUTOINCREMENT"; } }; @@ -17147,7 +17211,7 @@ namespace sqlite_orm { ss << "REPLACE INTO " << streaming_identifier(table.name) << " (" << streaming_non_generated_column_names(table) << ")"; const auto valuesCount = std::distance(rep.range.first, rep.range.second); - const auto columnsCount = table.non_generated_columns_count(); + const auto columnsCount = table.template count_of_columns_excluding(); ss << " VALUES " << streaming_values_placeholders(columnsCount, valuesCount); return ss.str(); } @@ -18021,6 +18085,27 @@ namespace sqlite_orm { "type is not mapped to storage"); } + template + void assert_updatable_type() const { +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + using Table = storage_pick_table_t; + using elements_type = elements_type_t
; + using col_index_sequence = filter_tuple_sequence_t; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + constexpr size_t dedicatedPrimaryKeyColumnsCount = + nested_tuple_size_for_t::value; + + constexpr size_t primaryKeyColumnsCount = + dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); + constexpr ptrdiff_t nonPrimaryKeysColumnsCount = col_index_sequence::size() - primaryKeyColumnsCount; + static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); + static_assert( + nonPrimaryKeysColumnsCount > 0, + "A table with only primary keys cannot be updated. You need at least 1 non-primary key column"); +#endif + } + template, std::enable_if_t = true> @@ -18931,8 +19016,11 @@ namespace sqlite_orm { #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template - prepared_statement_t> prepare(update_t upd) { - return prepare_impl>(std::move(upd)); + prepared_statement_t> prepare(update_t statement) { + using object_type = typename expression_object_type::type; + this->assert_mapped_type(); + this->assert_updatable_type(); + return prepare_impl>(std::move(statement)); } template @@ -20112,7 +20200,7 @@ namespace sqlite_orm { column.is_not_null(), std::move(dft), column.template is(), - column.is_generated()); + column.template is()); }); auto compositeKeyColumnNames = this->composite_key_columns_names(); for(size_t i = 0; i < compositeKeyColumnNames.size(); ++i) { @@ -20262,7 +20350,7 @@ namespace sqlite_orm { const Table& table, const std::vector& columnsToIgnore) const { // must ignore generated columns std::vector> columnNames; - columnNames.reserve(table.count_columns_amount()); + columnNames.reserve(table.template count_of()); table.for_each_column([&columnNames, &columnsToIgnore](const column_identifier& column) { auto& columnName = column.name; #if __cpp_lib_ranges >= 201911L diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ff037642b..b15a6baf6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(unit_tests static_tests/is_primary_key_insertable.cpp static_tests/is_column_with_insertable_primary_key.cpp static_tests/operators_adl.cpp + static_tests/table_static_tests.cpp tuple_iteration.cpp sync_schema_tests.cpp tests.cpp diff --git a/tests/schema/column_tests.cpp b/tests/schema/column_tests.cpp index da245ad3e..ee661a7c7 100644 --- a/tests/schema/column_tests.cpp +++ b/tests/schema/column_tests.cpp @@ -2,32 +2,33 @@ #include using namespace sqlite_orm; +using internal::is_generated_always; -TEST_CASE("column tests is_generated") { +TEST_CASE("column tests is") { struct User { int id = 0; int age = 0; }; SECTION("no constraints") { auto column = make_column("id", &User::id); - REQUIRE_FALSE(column.is_generated()); + REQUIRE_FALSE(column.is()); } #if SQLITE_VERSION_NUMBER >= 3031000 SECTION("1 constraint: generated") { SECTION("full") { auto column = make_column("age", &User::age, generated_always_as(add(&User::id, 5))); - REQUIRE(column.is_generated()); + REQUIRE(column.is()); } SECTION("not full") { auto column = make_column("age", &User::age, as(add(&User::id, 5))); - REQUIRE(column.is_generated()); + REQUIRE(column.is()); } } #endif SECTION("1 constraint: primary key") { auto column = make_column("id", &User::id, primary_key()); - REQUIRE_FALSE(column.is_generated()); + REQUIRE_FALSE(column.is()); } } diff --git a/tests/static_tests/table_static_tests.cpp b/tests/static_tests/table_static_tests.cpp new file mode 100644 index 000000000..ff782f178 --- /dev/null +++ b/tests/static_tests/table_static_tests.cpp @@ -0,0 +1,125 @@ +#include +#include + +using namespace sqlite_orm; +using internal::is_column; +using internal::is_primary_key; + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) +template +using dedicated_pk_columns_count_t = + internal::nested_tuple_size_for_t>; +#endif + +TEST_CASE("table static count_of()") { + struct User { + int id = 0; + std::string name; + }; + { // 1 column no pk + auto table = make_table("users", make_column("id", &User::id)); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 1 column with 1 inline pk + auto table = make_table("users", make_column("id", &User::id, primary_key())); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 1 column with 1 inline pk autoincrement + auto table = make_table("users", make_column("id", &User::id, primary_key().autoincrement())); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 1 column with 1 dedicated pk + auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id)); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 1 column with 1 dedicated pk autoincrement + auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id).autoincrement()); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 2 columns no pk + auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 2 columns with 1 inline id pk + auto table = make_table("users", make_column("id", &User::id, primary_key()), make_column("id", &User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 2 columns with 1 inline name pk + auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name, primary_key())); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 2 columns with 1 dedicated id pk + auto table = + make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::id)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 2 columns with 1 dedicated name pk + auto table = + make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 2 columns with 2 dedicated pks + auto table = make_table("users", + make_column("id", &User::id), + make_column("id", &User::name), + primary_key(&User::id, &User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 2); +#endif + } +}