diff --git a/dev/column_expression.h b/dev/column_expression.h index af4c41439..9f592d0c7 100644 --- a/dev/column_expression.h +++ b/dev/column_expression.h @@ -54,7 +54,7 @@ namespace sqlite_orm { // No CTE for object expression. template struct column_expression_type, void> { - static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed"); }; /** diff --git a/dev/column_result.h b/dev/column_result.h index fe502c6ba..a1a3266d6 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -254,7 +254,7 @@ namespace sqlite_orm { using colalias_index = find_tuple_type>; static_assert(colalias_index::value < std::tuple_size_v, - "No such column mapped into the CTE."); + "No such column mapped into the CTE"); using type = std::tuple_element_t; }; #endif diff --git a/dev/constraints.h b/dev/constraints.h index 3c45749be..77afe967c 100644 --- a/dev/constraints.h +++ b/dev/constraints.h @@ -502,20 +502,52 @@ namespace sqlite_orm { template struct is_generated_always : polyfill::bool_constant> {}; - /** - * PRIMARY KEY INSERTABLE traits. + // Custom type: programmer's responsibility to garantee data integrity in the value range of a 64-bit signed integer + template + inline constexpr bool is_rowid_alias_capable_v = std::is_base_of>::value; + + // For integer types: further checks + template + inline constexpr bool is_rowid_alias_capable_v< + F, + std::enable_if_t::value && + (sizeof(F) == sizeof(sqlite_int64) && + std::is_signed::value == std::is_signed::value)>> = true; + + // [Deprecation notice] For integral types other than 64-bit signed integer, AUTOINCREMENT is deprecated on PRIMARY KEY columns + // and will be turned into a static_assert failure in v1.11 + template + [[deprecated(R"(Use a 64-bit signed integer for the "rowid" key alias)")]] + inline constexpr bool is_rowid_alias_capable_v< + F, + std::enable_if_t::value && + (sizeof(F) != sizeof(sqlite_int64) || + std::is_signed::value != std::is_signed::value)>> = true; + + // For non-integer types: static_assert failure + template + inline constexpr bool + is_rowid_alias_capable_v>::value>> = + false; + + /** + * COLUMN PRIMARY KEY INSERTABLE traits. + * + * A column primary key is considered implicitly insertable if: + * - it is an INTEGER PRIMARY KEY (and thus an alias for the "rowid" key), + * - or has a default value. + * + * In terms of C++ types, this means that the field type must be capable of representing a 64-bit signed integer, + * or the column is declared with a DEFAULT constraint. + * + * Implementation note: using a struct template in favor of a template alias so that the stack leading to a deprecation message is shorter. */ template - struct is_primary_key_insertable - : polyfill::disjunction< - mpl::invoke_t, - check_if_has_template>, - constraints_type_t>, - std::is_base_of>>> { - - static_assert(tuple_has, is_primary_key>::value, - "an unexpected type was passed"); - }; + struct is_pkcol_implicitly_insertable + : mpl::invoke_t< + mpl::disjunction>>>, + check_if_has_template>, + constraints_type_t> {}; template using is_column_constraint = mpl::invoke_t>, @@ -595,7 +627,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {{}}; } -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) /** * UNINDEXED column constraint builder function. Used in FTS virtual tables. * diff --git a/dev/core_functions.h b/dev/core_functions.h index fd984dc54..010c7f43f 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -2158,6 +2158,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } } +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) struct fts5; /** @@ -2222,4 +2223,5 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {std::move(x), std::move(y), std::move(z)}; } #endif +#endif } diff --git a/dev/cte_column_names_collector.h b/dev/cte_column_names_collector.h index 106d547bf..3ef60b7d6 100644 --- a/dev/cte_column_names_collector.h +++ b/dev/cte_column_names_collector.h @@ -118,13 +118,13 @@ namespace sqlite_orm { // No CTE for object expressions. template struct cte_column_names_collector> { - static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed"); }; // No CTE for object expressions. template struct cte_column_names_collector> { - static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed."); + static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed"); }; template diff --git a/dev/cte_storage.h b/dev/cte_storage.h index d0511b0bd..5f92d17eb 100644 --- a/dev/cte_storage.h +++ b/dev/cte_storage.h @@ -269,7 +269,7 @@ namespace sqlite_orm { (!is_builtin_numeric_column_alias_v< alias_holder_type_or_none_t>> && ...), - "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + "Numeric column aliases are reserved for referencing columns locally within a single CTE"); return std::tuple{ determine_cte_colref(dbObjects, get(subselectColRefs), get(explicitColRefs))...}; diff --git a/dev/error_code.h b/dev/error_code.h index c53268fae..f9f4d823e 100644 --- a/dev/error_code.h +++ b/dev/error_code.h @@ -26,7 +26,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { too_many_tables_specified, incorrect_set_fields_specified, column_not_found, - table_has_no_primary_key_column, cannot_start_a_transaction_within_a_transaction, no_active_transaction, incorrect_journal_mode_string, @@ -75,8 +74,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return "Incorrect set fields specified"; case orm_error_code::column_not_found: return "Column not found"; - case orm_error_code::table_has_no_primary_key_column: - return "Table has no primary key column"; case orm_error_code::cannot_start_a_transaction_within_a_transaction: return "Cannot start a transaction within a transaction"; case orm_error_code::no_active_transaction: diff --git a/dev/functional/mpl.h b/dev/functional/mpl.h index 37bcc0d7e..0fadca3c7 100644 --- a/dev/functional/mpl.h +++ b/dev/functional/mpl.h @@ -357,7 +357,7 @@ namespace sqlite_orm { template struct invoke_this_fn { static_assert(polyfill::always_false_v, - "`finds` must be invoked with a type list as first argument."); + "`finds` must be invoked with a type list as first argument"); }; template class Pack, class... T, class ProjectQ> @@ -384,7 +384,7 @@ namespace sqlite_orm { template struct invoke_this_fn { static_assert(polyfill::always_false_v, - "`counts` must be invoked with a type list as first argument."); + "`counts` must be invoked with a type list as first argument"); }; template class Pack, class... T, class ProjectQ> @@ -411,7 +411,7 @@ namespace sqlite_orm { template struct invoke_this_fn { static_assert(polyfill::always_false_v, - "`contains` must be invoked with a type list as first argument."); + "`contains` must be invoked with a type list as first argument"); }; template class Pack, class... T, class ProjectQ> diff --git a/dev/implementations/table_definitions.h b/dev/implementations/table_definitions.h index 57c7e1982..3e55c70ba 100644 --- a/dev/implementations/table_definitions.h +++ b/dev/implementations/table_definitions.h @@ -44,17 +44,17 @@ namespace sqlite_orm { column.template is(), column.template is()); }); - auto compositeKeyColumnNames = this->composite_key_columns_names(); + const auto tableKeyColumnNames = this->table_key_columns_names(); #if defined(SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_CPP20_RANGES_SUPPORTED) - for (int n = 1; const std::string& columnName: compositeKeyColumnNames) { + for (int n = 1; const std::string& columnName: tableKeyColumnNames) { if (auto it = std::ranges::find(res, columnName, &table_xinfo::name); it != res.end()) { it->pk = n; } ++n; } #else - for (size_t i = 0; i < compositeKeyColumnNames.size(); ++i) { - const std::string& columnName = compositeKeyColumnNames[i]; + for (size_t i = 0; i < tableKeyColumnNames.size(); ++i) { + const std::string& columnName = tableKeyColumnNames[i]; auto it = std::find_if(res.begin(), res.end(), [&columnName](const table_xinfo& ti) { return ti.name == columnName; }); diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index e2909bfc7..ba81bb863 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -4,7 +4,7 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::unique_ptr #include // std::string -#include // std::integral_constant, std::declval +#include // std::integral_constant, std::declval, std::is_convertible #include // std::move, std::forward, std::exchange, std::pair #include // std::tuple #endif @@ -582,6 +582,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::replace_range_t replace_range(It from, It to, Projection project = {}) { + // validate up front that projected type is convertible to mapped object type, avoiding hard to read error messages later; + // note: we use `is_convertible` instead of `is_invocable_r` because we do not create dangling references in `storage_t<>::execute()` + using projected_type = decltype(polyfill::invoke(std::declval(), *std::declval())); + static_assert(std::is_convertible::value, + "Projected type must be convertible to mapped object type"); + return {{std::move(from), std::move(to)}, std::move(project)}; } @@ -616,6 +622,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::insert_range_t insert_range(It from, It to, Projection project = {}) { + // validate up front that projected type is convertible to mapped object type, avoiding hard to read error messages later; + // note: we use `is_convertible` instead of `is_invocable_r` because we do not create dangling references in `storage_t<>::execute()` + using projected_type = decltype(polyfill::invoke(std::declval(), *std::declval())); + static_assert(std::is_convertible::value, + "Projected type must be convertible to mapped object type"); + return {{std::move(from), std::move(to)}, std::move(project)}; } diff --git a/dev/schema/column.h b/dev/schema/column.h index 62e0dad68..05a6e0557 100644 --- a/dev/schema/column.h +++ b/dev/schema/column.h @@ -1,18 +1,19 @@ #pragma once +#include // sqlite_int64 #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::tuple #include // std::string #include // std::unique_ptr -#include // std::is_same, std::is_member_object_pointer +#include // std::enable_if, std::is_same, std::is_member_object_pointer, std::is_signed #include // std::move #endif #include "../functional/cxx_type_traits_polyfill.h" #include "../tuple_helper/tuple_traits.h" #include "../tuple_helper/tuple_filter.h" -#include "../type_traits.h" #include "../member_traits/member_traits.h" +#include "../type_traits.h" #include "../type_is_nullable.h" #include "../constraints.h" @@ -84,6 +85,14 @@ namespace sqlite_orm { return tuple_has::value; } + /** + * Checks whether contraints contain specified class template. + */ + template class Primary> + constexpr static bool is_template() { + return tuple_has_template::value; + } + /** * Simplified interface for `DEFAULT` constraint * @return string representation of default value if it exists otherwise nullptr @@ -165,19 +174,65 @@ namespace sqlite_orm { field_type_t, filter_tuple_sequence_t::template fn>>; -#if SQLITE_VERSION_NUMBER >= 3031000 + // Custom type: programmer's responsibility to garantee data integrity in the value range of a 64-bit signed integer + template + struct check_pkcol { + static constexpr void validate_column_primary_key_with_autoincrement() {} + }; + + // For integer types: further checks + template + struct check_pkcol::value>> { + // For 64-bit signed integer type: valid + template::value == std::is_signed::value, + bool> = true> + static constexpr void validate_column_primary_key_with_autoincrement() {} + + // [Deprecation notice] For integral types other than 64-bit signed integer, AUTOINCREMENT is deprecated on PRIMARY KEY columns + // and will be turned into a static_assert failure in v1.11 + template::value != std::is_signed::value, + bool> = true> + [[deprecated( + R"(Use a 64-bit signed integer for AUTOINCREMENT on an INTEGER PRIMARY KEY as an alias for the "rowid" key)")]] static constexpr void + validate_column_primary_key_with_autoincrement() {} + }; + + // For non-integer types: static_assert failure + template + struct check_pkcol>::value>> { + static constexpr void validate_column_primary_key_with_autoincrement() { + static_assert( + polyfill::always_false_v, + R"(AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY as an alias for the "rowid" key)"); + } + }; + + template + constexpr void validate_column_definition() { + using constraints_type = std::tuple; + + static_assert(polyfill::conjunction_v...>, "Incorrect column constraints"); + + if constexpr (tuple_has_template::value) { + check_pkcol>::validate_column_primary_key_with_autoincrement(); + } + } + /** * Factory function for a column definition from a member object pointer for hidden virtual table columns. */ template = true> hidden_column make_hidden_column(std::string name, M memberPointer, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + static_assert(polyfill::conjunction_v...>, "Incorrect column constraints"); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}; } -#endif } } @@ -188,7 +243,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template = true> internal::column_t make_column(std::string name, M memberPointer, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::validate_column_definition(); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! @@ -206,7 +261,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::column_t make_column(std::string name, S setter, G getter, Op... constraints) { static_assert(std::is_same, internal::getter_field_type_t>::value, "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::validate_column_definition(); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! @@ -224,7 +279,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::column_t make_column(std::string name, G getter, S setter, Op... constraints) { static_assert(std::is_same, internal::getter_field_type_t>::value, "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::validate_column_definition(); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! diff --git a/dev/schema/table.h b/dev/schema/table.h index 9d97e3962..8f45e90cc 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -10,10 +10,8 @@ #include "../functional/cxx_type_traits_polyfill.h" #include "../functional/mpl.h" -#include "../tuple_helper/tuple_iteration.h" #include "../tuple_helper/tuple_filter.h" -#include "../member_traits/member_traits.h" -#include "../field_of.h" +#include "../tuple_helper/tuple_transformer.h" #include "../type_traits.h" #include "../constraints.h" #include "../table_info.h" @@ -117,6 +115,25 @@ namespace sqlite_orm { template using is_base_table = polyfill::bool_constant>; + + template + constexpr void validate_base_table_definition() { + static_assert(polyfill::conjunction_v...>, + "Incorrect base table elements or constraints"); + + using elements_type = std::tuple; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + + static_assert(pk_index_sequence::size() + pkcol_index_sequence::size() <= 1, + "A base table can only have 1 primary key definition"); + if constexpr (pk_index_sequence::size() > 0) { + constexpr size_t nTablePrimaryKeyColumns = + nested_tuple_size_for_t::value; + + static_assert(nTablePrimaryKeyColumns > 0, "Tabel primary key definition must contain one column"); + } + } } } @@ -128,10 +145,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template>::object_type> internal::base_table make_table(std::string name, Cs... definition) { - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); - - return {std::move(name), std::make_tuple(std::forward(definition)...)}; + internal::validate_base_table_definition(); + return {std::move(name), std::tuple{std::forward(definition)...}}; } /** @@ -141,10 +156,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::base_table make_table(std::string name, Cs... definition) { - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); - - return {std::move(name), std::make_tuple(std::forward(definition)...)}; + internal::validate_base_table_definition(); + return {std::move(name), std::tuple{std::forward(definition)...}}; } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES diff --git a/dev/schema/table_base.h b/dev/schema/table_base.h index 73197b5f1..fb2721797 100644 --- a/dev/schema/table_base.h +++ b/dev/schema/table_base.h @@ -1,11 +1,12 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::is_member_pointer +#include // std::is_member_pointer, std::remove_cvref #include // std::string -#include // std::tuple +#include // std::tuple, std::tuple_size #endif +#include "../functional/cxx_type_traits_polyfill.h" #include "../functional/cxx_functional_polyfill.h" #include "../functional/mpl.h" #include "../tuple_helper/tuple_filter.h" @@ -54,7 +55,7 @@ namespace sqlite_orm::internal { } /* - * Returns the number of columns having the specified constraint trait. + * Returns the number of columns not having the specified constraint trait. */ template class Trait> static constexpr int count_of_columns_excluding() { @@ -111,7 +112,7 @@ namespace sqlite_orm::internal { }; /** - * Encapsulates table elements, i.e. columns and constraints for a type of table that can have primary keys - base tables and usually virtual tables -, + * Encapsulates table elements, i.e. columns and constraints for a type of table that can have a primary key - base tables and usually virtual tables -, * and provides additional methods to those of a generic table definition in order to deal with primary key columns. */ template @@ -120,18 +121,21 @@ namespace sqlite_orm::internal { using elements_type = elements_type_t; /** - * Call passed lambda with all defined primary keys. + * Call passed lambda with the defined table primary key. */ template - void for_each_primary_key(L&& lambda) const { + void visit_table_primary_key(L&& lambda) const { using pk_index_sequence = filter_tuple_sequence_t; + // note: already checked in `validate_base_table_definition()` + static_assert(pk_index_sequence::size() <= 1); + // note: we use the tuple iteration function for simplicity, even if we know there is at most one primary key iterate_tuple(this->elements, pk_index_sequence{}, lambda); } - std::vector composite_key_columns_names() const { + std::vector table_key_columns_names() const { std::vector res; - this->for_each_primary_key([this, &res](auto& primaryKey) { - res = this->composite_key_columns_names(primaryKey); + this->visit_table_primary_key([this, &res](auto& primaryKey) { + res = this->table_key_columns_names(primaryKey); }); return res; } @@ -144,7 +148,7 @@ namespace sqlite_orm::internal { pkcol_index_sequence{}, &column_identifier::name); } else { - return this->composite_key_columns_names(); + return this->table_key_columns_names(); } } @@ -155,13 +159,13 @@ namespace sqlite_orm::internal { call_as_template_base([&lambda](const auto& column) { lambda(column.member_pointer); })); - this->for_each_primary_key([&lambda](auto& primaryKey) { + this->visit_table_primary_key([&lambda](auto& primaryKey) { iterate_tuple(primaryKey.columns, lambda); }); } template - std::vector composite_key_columns_names(const primary_key_t& primaryKey) const { + std::vector table_key_columns_names(const primary_key_t& primaryKey) const { return create_from_tuple>(primaryKey.columns, [this, empty = std::string{}](auto& memberPointer) { if (const std::string* columnName = @@ -175,22 +179,48 @@ namespace sqlite_orm::internal { }; template - bool exists_in_composite_primary_key(const insertable_table_definition& definition, - const column_field& column) { + bool table_primary_key_contains([[maybe_unused]] const insertable_table_definition& definition, + [[maybe_unused]] const column_field& column) { bool res = false; - definition.for_each_primary_key([&column, &res](auto& primaryKey) { - using colrefs_tuple = decltype(primaryKey.columns); - using same_type_index_sequence = - filter_tuple_sequence_t>::template fn, - member_field_type_t>; - iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { - if (compare_fields(memberPointer, column.member_pointer) || - compare_fields(memberPointer, column.setter)) { - res = true; + // note: though `visit_table_primary_key()` does no work if a column primary key exists, we try to save the compiler some work with this check up front + if constexpr (/*bool hasNoColumnPK =*/!insertable_table_definition::template count_of_columns_with< + is_primary_key>()) { + definition.visit_table_primary_key([&column, &res](auto& primaryKey) { + using colrefs_tuple = decltype(primaryKey.columns); + using same_type_index_sequence = + filter_tuple_sequence_t>::template fn, + member_field_type_t>; + iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { + if (compare_fields(memberPointer, column.member_pointer) || + compare_fields(memberPointer, column.setter)) { + res = true; + } + }); + }); + } + return res; + } + + template + bool is_single_table_primary_key([[maybe_unused]] const insertable_table_definition& definition, + [[maybe_unused]] const column_field& column) { + bool res = false; + // note: though `visit_table_primary_key()` does no work if a column primary key exists, we try to save the compiler some work with this check up front + if constexpr (/*bool hasNoColumnPK =*/!insertable_table_definition::template count_of_columns_with< + is_primary_key>()) { + definition.visit_table_primary_key([&column, &res](auto& primaryKey) { + // note: use `decltype(primaryKey)` instead of `decltype(primaryKey.columns)` otherwise msvc 141 chokes on the `if constexpr` below + using colrefs_tuple = columns_tuple_t>; + if constexpr (std::tuple_size::value == 1) { + auto& memberPointer = std::get<0>(primaryKey.columns); + if (compare_fields(memberPointer, column.member_pointer) || + compare_fields(memberPointer, column.setter)) { + res = true; + } } }); - }); + } return res; } diff --git a/dev/select_constraints.h b/dev/select_constraints.h index 1aa3a5226..2de2ac45a 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -606,7 +606,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using namespace ::sqlite_orm::internal; static_assert(is_cte_moniker_v, "Moniker must be a CTE moniker"); static_assert((!is_builtin_numeric_column_alias_v && ...), - "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + "Numeric column aliases are reserved for referencing columns locally within a single CTE"); using builder_type = cte_builder, decay_explicit_column_t>>; @@ -623,7 +623,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { constexpr auto cte(ExplicitCols... explicitColumns) { using namespace ::sqlite_orm::internal; static_assert((!is_builtin_numeric_column_alias_v && ...), - "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + "Numeric column aliases are reserved for referencing columns locally within a single CTE"); using builder_type = cte_builder, transform_tuple_t, decay_explicit_column_t>>; diff --git a/dev/serializing_util.h b/dev/serializing_util.h index 52c9459c4..a42db82f8 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -386,7 +386,7 @@ namespace sqlite_orm { using object_type = polyfill::remove_cvref_t; auto& table = pick_table(context.db_objects); - table.template for_each_column_excluding(call_as_template_base( + table.template for_each_column_excluding( [&ss, &excluded, &context, &object, first = true](auto& column) mutable { if (excluded(column)) { return; @@ -395,7 +395,7 @@ namespace sqlite_orm { static constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] << serialize(polyfill::invoke(column.member_pointer, object), context); - })); + }); return ss; } diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index f93491242..c7e2132da 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -1204,7 +1204,7 @@ namespace sqlite_orm { } }; -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) template<> struct statement_serializer { using statement_type = unindexed_t; @@ -1472,7 +1472,7 @@ namespace sqlite_orm { ss << "UPDATE " << streaming_identifier(table.name) << " SET "; table.template for_each_column_excluding>( [&table, &ss, &context, &object = get_ref(statement.object), first = true](auto& column) mutable { - if (exists_in_composite_primary_key(table, column)) { + if (table_primary_key_contains(table, column)) { return; } @@ -1483,7 +1483,7 @@ namespace sqlite_orm { ss << " WHERE "; table.for_each_column( [&table, &context, &ss, &object = get_ref(statement.object), first = true](auto& column) mutable { - if (!column.template is() && !exists_in_composite_primary_key(table, column)) { + if (!column.template is() && !table_primary_key_contains(table, column)) { return; } @@ -1592,14 +1592,21 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); - using is_without_rowid = typename std::remove_reference_t::is_without_rowid; + using table_type = polyfill::remove_cvref_t; + using without_rowid = typename table_type::is_without_rowid; std::vector> columnNames; - table.template for_each_column_excluding< - mpl::conjunction>, - mpl::disjunction_fn>>( + table.template for_each_column_excluding< /// + mpl::disjunction< + mpl::conjunction>, mpl::quote_fn>, + mpl::quote_fn>>( /// [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!without_rowid::value && + (is_single_table_primary_key(table, column) || + (column.template is_template() && table_primary_key_contains(table, column)))) { + return; + } else if (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))) { return; } @@ -1618,10 +1625,15 @@ namespace sqlite_orm { if (columnNamesCount) { ss << " (" << streaming_field_values_excluding( - mpl::conjunction>, - mpl::disjunction_fn>{}, + mpl::disjunction>, + mpl::quote_fn>, + mpl::quote_fn>{}, [&table](auto& column) { - return exists_in_composite_primary_key(table, column); + return (!without_rowid::value && (is_single_table_primary_key(table, column) || + (column.template is_template() && + table_primary_key_contains(table, column)))) || + (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))); }, context, get_ref(statement.object)) @@ -1764,14 +1776,21 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); - using is_without_rowid = typename std::remove_reference_t::is_without_rowid; + using table_type = polyfill::remove_cvref_t; + using without_rowid = typename table_type::is_without_rowid; std::vector> columnNames; - table.template for_each_column_excluding< - mpl::conjunction>, - mpl::disjunction_fn>>( + table.template for_each_column_excluding< /// + mpl::disjunction< + mpl::conjunction>, mpl::quote_fn>, + mpl::quote_fn>>( /// [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!without_rowid::value && + (is_single_table_primary_key(table, column) || + (column.template is_template() && table_primary_key_contains(table, column)))) { + return; + } else if (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))) { return; } @@ -1857,11 +1876,7 @@ namespace sqlite_orm { ss << "SELECT " << streaming_table_column_names(table, std::string{}) << " FROM " << streaming_identifier(table.name) << " WHERE "; - auto primaryKeyColumnNames = table.primary_key_column_names(); - if (primaryKeyColumnNames.empty()) { - throw std::system_error{orm_error_code::table_has_no_primary_key_column}; - } - + const auto primaryKeyColumnNames = table.primary_key_column_names(); #ifdef SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED static constexpr std::array sep = {" AND ", ""}; for (bool first = true; const std::string& pkName: primaryKeyColumnNames) { diff --git a/dev/storage.h b/dev/storage.h index ee207b24e..f07bde724 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -214,6 +214,17 @@ namespace sqlite_orm { "type is not mapped to storage"); } + template + void assert_primary_key_type() const { + using table_type = storage_pick_table_t; + using elements_type = elements_type_t; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + + static_assert(pk_index_sequence::size() + pkcol_index_sequence::size() == 1, + "The table must have primary key"); + } + template void assert_updatable_type() const { using table_type = storage_pick_table_t; @@ -221,15 +232,14 @@ namespace sqlite_orm { using column_index_sequence = col_index_sequence_of; using pk_index_sequence = filter_tuple_sequence_t; using pkcol_index_sequence = col_index_sequence_with; - constexpr size_t dedicatedPrimaryKeyColumnsCount = + constexpr size_t nTablePrimaryKeyColumns = nested_tuple_size_for_t::value; - constexpr size_t primaryKeyColumnsCount = - dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); - constexpr ptrdiff_t nonPrimaryKeysColumnsCount = column_index_sequence::size() - primaryKeyColumnsCount; - static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); + constexpr size_t nPrimaryKeyColumns = nTablePrimaryKeyColumns + pkcol_index_sequence::size(); + constexpr ptrdiff_t nNonPrimaryKeysColumns = column_index_sequence::size() - nPrimaryKeyColumns; + static_assert(nPrimaryKeyColumns > 0, "A table without primary keys cannot be updated"); static_assert( - nonPrimaryKeysColumnsCount > 0, + nNonPrimaryKeysColumns > 0, "A table with only primary keys cannot be updated. You need at least 1 non-primary key column"); } @@ -244,17 +254,15 @@ namespace sqlite_orm { void assert_insertable_type() const { using elements_type = elements_type_t; using pkcol_index_sequence = col_index_sequence_with; - static_assert( - count_filtered_tuple::value <= 1, - "Attempting to execute 'insert' request into an noninsertable table was detected. " - "Insertable table cannot contain > 1 primary keys. Please use 'replace' instead of " - "'insert', or you can use 'insert' with explicit column listing."); - static_assert(count_filtered_tuple::template fn, - pkcol_index_sequence>::value == 0, - "Attempting to execute 'insert' request into an noninsertable table was detected. " - "Insertable table cannot contain non-standard primary keys. Please use 'replace' instead " - "of 'insert', or you can use 'insert' with explicit column listing."); + if constexpr (pkcol_index_sequence::size()) { + using pkcol_type = + std::tuple_element_t(pkcol_index_sequence{}), elements_type>; + static_assert( + mpl::invoke_t, pkcol_type>::value, + "While SQLite allows primary keys of any type, sqlite_orm restricts an ordinary 'insert' into " + "tables with single-column primary keys to those with columns that are aliases for the 'rowid'." + "Instead, please use 'replace' or 'insert' with explicitly specified columns."); + } } template @@ -533,6 +541,7 @@ namespace sqlite_orm { template O get(Ids... ids) { this->assert_mapped_type(); + this->assert_primary_key_type(); auto statement = this->prepare(sqlite_orm::get(std::forward(ids)...)); return this->execute(statement); } @@ -551,6 +560,7 @@ namespace sqlite_orm { template std::unique_ptr get_pointer(Ids... ids) { this->assert_mapped_type(); + this->assert_primary_key_type(); auto statement = this->prepare(sqlite_orm::get_pointer(std::forward(ids)...)); return this->execute(statement); } @@ -588,6 +598,7 @@ namespace sqlite_orm { template std::optional get_optional(Ids... ids) { this->assert_mapped_type(); + this->assert_primary_key_type(); auto statement = this->prepare(sqlite_orm::get_optional(std::forward(ids)...)); return this->execute(statement); } @@ -928,24 +939,33 @@ namespace sqlite_orm { } template - int insert(const O& o, columns_t cols) { + int64 insert(const O& o, columns_t cols) { static_assert(cols.count > 0, "Use insert or replace with 1 argument instead"); this->assert_mapped_type(); auto statement = this->prepare(sqlite_orm::insert(std::ref(o), std::move(cols))); - return int(this->execute(statement)); + return this->execute(statement); } /** - * Insert routine. Inserts object with all non primary key fields in passed object. Id of passed - * object doesn't matter. - * @return id of just created object. + * Ordinary insert routine. + * + * - For objects mapped to a table with rowid and a single primary key: + * Inserts a record with all fields of a mapped object that are not primary key columns. + * The 'ID' of the specified object is irrelevant. + * - For objects mapped to a table with rowid and a composite primary key or no primary key: + * Inserts a record with all fields of a mapped object. + * - For objects mapped to a table without rowid: + * Inserts a record with all fields of a mapped object. + * + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. */ template - int insert(const O& o) { + int64 insert(const O& o) { this->assert_mapped_type(); this->assert_insertable_type(); auto statement = this->prepare(sqlite_orm::insert(std::ref(o))); - return int(this->execute(statement)); + return this->execute(statement); } /** @@ -1461,8 +1481,8 @@ namespace sqlite_orm { } /** - * @return The rowid of the last inserted row. - * @note The returned rowid is only meaningful in single-thread contexts. + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. */ template int64 execute(const prepared_statement_t>& statement) { @@ -1488,7 +1508,7 @@ namespace sqlite_orm { sqlite3_stmt* stmt = reset_stmt(statement.stmt); auto processObject = [&table = this->get_table(), - bindValue = field_value_binder{stmt}](auto& object) mutable { + bindValue = field_value_binder{stmt}](const object_type& object) mutable { table.template for_each_column_excluding( call_as_template_base([&bindValue, &object](auto& column) { bindValue(polyfill::invoke(column.member_pointer, object)); @@ -1505,8 +1525,10 @@ namespace sqlite_orm { auto& transformer = statement.expression.transformer; std::for_each(statement.expression.range.first, statement.expression.range.second, - [&processObject, &transformer](auto& item) { - const object_type& object = polyfill::invoke(transformer, item); + [&processObject, &transformer](auto&& item) { + using item_type = decltype(item); + const object_type& object = + polyfill::invoke(transformer, std::forward(item)); processObject(object); }); #endif @@ -1519,8 +1541,8 @@ namespace sqlite_orm { } /** - * @return The rowid of the last inserted row. - * @note The returned rowid is only meaningful in single-thread contexts. + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. */ template, is_insert_range>::value, bool> = true> @@ -1530,17 +1552,25 @@ namespace sqlite_orm { sqlite3_stmt* stmt = reset_stmt(statement.stmt); auto processObject = [&table = this->get_table(), - bindValue = field_value_binder{stmt}](auto& object) mutable { - using is_without_rowid = typename std::remove_reference_t::is_without_rowid; - - table.template for_each_column_excluding< - mpl::conjunction>, - mpl::disjunction_fn>>( - call_as_template_base([&table, &bindValue, &object](auto& column) { - if (!exists_in_composite_primary_key(table, column)) { - bindValue(polyfill::invoke(column.member_pointer, object)); + bindValue = field_value_binder{stmt}](const object_type& object) mutable { + using table_type = polyfill::remove_cvref_t; + using without_rowid = typename table_type::is_without_rowid; + + table.template for_each_column_excluding< /// + mpl::disjunction< + mpl::conjunction>, mpl::quote_fn>, + mpl::quote_fn>>( /// + [&table, &bindValue, &object](auto& column) { + if (!without_rowid::value && (is_single_table_primary_key(table, column) || + (column.template is_template() && + table_primary_key_contains(table, column)))) { + return; + } else if (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))) { + return; } - })); + bindValue(polyfill::invoke(column.member_pointer, object)); + }); }; if constexpr (is_insert_range::value) { @@ -1553,8 +1583,10 @@ namespace sqlite_orm { auto& transformer = statement.expression.transformer; std::for_each(statement.expression.range.first, statement.expression.range.second, - [&processObject, &transformer](auto& item) { - const object_type& object = polyfill::invoke(transformer, item); + [&processObject, &transformer](auto&& item) { + using item_type = decltype(item); + const object_type& object = + polyfill::invoke(transformer, std::forward(item)); processObject(object); }); #endif @@ -1585,14 +1617,16 @@ namespace sqlite_orm { auto& object = get_object(statement.expression); table.template for_each_column_excluding>( call_as_template_base([&table, &bindValue, &object](auto& column) { - if (!exists_in_composite_primary_key(table, column)) { - bindValue(polyfill::invoke(column.member_pointer, object)); + if (table_primary_key_contains(table, column)) { + return; } + bindValue(polyfill::invoke(column.member_pointer, object)); })); table.for_each_column([&table, &bindValue, &object](auto& column) { - if (column.template is() || exists_in_composite_primary_key(table, column)) { - bindValue(polyfill::invoke(column.member_pointer, object)); + if (!column.template is() && !table_primary_key_contains(table, column)) { + return; } + bindValue(polyfill::invoke(column.member_pointer, object)); }); this->executor.perform_single_step(stmt); diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 0ad475f3b..de0a71a81 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -76,7 +76,7 @@ namespace sqlite_orm { // lookup ColAlias in the final column references using colalias_index = find_tuple_type>; static_assert(colalias_index::value < std::tuple_size_v, - "No such column mapped into the CTE."); + "No such column mapped into the CTE"); return &aliased_field>::field; } @@ -109,7 +109,7 @@ namespace sqlite_orm { // lookup ColAlias in the final column references using colalias_index = find_tuple_type>; static_assert(colalias_index::value < std::tuple_size_v, - "No such column mapped into the CTE."); + "No such column mapped into the CTE"); // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `cte_table<>::find_column_name()` mechanism; // however we have the column index already. diff --git a/dev/vtabs/fts5.h b/dev/vtabs/fts5.h index 190ac96c9..915c729cc 100644 --- a/dev/vtabs/fts5.h +++ b/dev/vtabs/fts5.h @@ -1,7 +1,7 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) #include // std::tuple_size, std::make_tuple, std::get #include // std::forward, std::move #include @@ -16,7 +16,7 @@ #include "../schema/column.h" #include "../constraints.h" -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) namespace sqlite_orm::internal { template inline constexpr bool is_fts5_table_element_or_constraint_v = diff --git a/dev/xdestroy_handling.h b/dev/xdestroy_handling.h index a4e14b105..b931fea50 100644 --- a/dev/xdestroy_handling.h +++ b/dev/xdestroy_handling.h @@ -188,7 +188,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { requires (internal::is_unusable_for_xdestroy) { static_assert(polyfill::always_false_v, - "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); + "A function pointer, which is not of type xdestroy_fn_t, is prohibited"); return nullptr; } @@ -235,7 +235,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template, bool> = true> constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) { static_assert(polyfill::always_false_v, - "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); + "A function pointer, which is not of type xdestroy_fn_t, is prohibited"); return nullptr; } diff --git a/examples/chrono_binding.cpp b/examples/chrono_binding.cpp index a3a2020b1..907b80f7f 100644 --- a/examples/chrono_binding.cpp +++ b/examples/chrono_binding.cpp @@ -138,7 +138,7 @@ using std::cout; using std::endl; struct Person { - int id; + int64 id; std::string name; std::chrono::sys_days birthdate; }; diff --git a/examples/common_table_expressions.cpp b/examples/common_table_expressions.cpp index 27f1dd4c0..958b0f30c 100644 --- a/examples/common_table_expressions.cpp +++ b/examples/common_table_expressions.cpp @@ -526,7 +526,7 @@ void depth_or_breadth_first() { void select_from_subselect() { struct Employee { - int m_empno; + int64 m_empno; std::string m_ename; double m_salary; std::optional m_commission; diff --git a/examples/except_intersection.cpp b/examples/except_intersection.cpp index 928649d0f..2a2799da9 100644 --- a/examples/except_intersection.cpp +++ b/examples/except_intersection.cpp @@ -9,12 +9,12 @@ using std::cout; using std::endl; struct DeptMaster { - int deptId = 0; + sqlite_orm::int64 deptId = 0; std::string deptName; }; struct EmpMaster { - int empId = 0; + sqlite_orm::int64 empId = 0; std::string firstName; std::string lastName; long salary; diff --git a/examples/select.cpp b/examples/select.cpp index d3bca681d..1874a93d2 100644 --- a/examples/select.cpp +++ b/examples/select.cpp @@ -127,13 +127,13 @@ void all_employees() { void all_artists() { struct Artist { - int id; + int64 id; std::string name; }; struct Album { - int id; - int artist_id; + int64 id; + int64 artist_id; }; auto storage = make_storage("", @@ -165,13 +165,13 @@ void all_artists() { void named_adhoc_structs() { struct Artist { - int id; + int64 id; std::string name; }; struct Album { - int id; - int artist_id; + int64 id; + int64 artist_id; std::string name; }; diff --git a/examples/self_join.cpp b/examples/self_join.cpp index 3ea98c1ec..dc3e9a74b 100644 --- a/examples/self_join.cpp +++ b/examples/self_join.cpp @@ -15,7 +15,7 @@ using std::cout; using std::endl; struct Employee { - int employeeId; + sqlite_orm::int64 employeeId; std::string lastName; std::string firstName; std::string title; diff --git a/examples/unique.cpp b/examples/unique.cpp index 59f4aefaa..d7c6aa69f 100644 --- a/examples/unique.cpp +++ b/examples/unique.cpp @@ -9,7 +9,7 @@ using std::cout; using std::endl; struct Entry { - int id; + sqlite_orm::int64 id; std::string uniqueColumn; std::unique_ptr nullableColumn; }; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index b2551b63e..743689c11 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1233,7 +1233,7 @@ namespace sqlite_orm { template struct invoke_this_fn { static_assert(polyfill::always_false_v, - "`finds` must be invoked with a type list as first argument."); + "`finds` must be invoked with a type list as first argument"); }; template class Pack, class... T, class ProjectQ> @@ -1260,7 +1260,7 @@ namespace sqlite_orm { template struct invoke_this_fn { static_assert(polyfill::always_false_v, - "`counts` must be invoked with a type list as first argument."); + "`counts` must be invoked with a type list as first argument"); }; template class Pack, class... T, class ProjectQ> @@ -1287,7 +1287,7 @@ namespace sqlite_orm { template struct invoke_this_fn { static_assert(polyfill::always_false_v, - "`contains` must be invoked with a type list as first argument."); + "`contains` must be invoked with a type list as first argument"); }; template class Pack, class... T, class ProjectQ> @@ -3129,7 +3129,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { too_many_tables_specified, incorrect_set_fields_specified, column_not_found, - table_has_no_primary_key_column, cannot_start_a_transaction_within_a_transaction, no_active_transaction, incorrect_journal_mode_string, @@ -3178,8 +3177,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return "Incorrect set fields specified"; case orm_error_code::column_not_found: return "Column not found"; - case orm_error_code::table_has_no_primary_key_column: - return "Table has no primary key column"; case orm_error_code::cannot_start_a_transaction_within_a_transaction: return "Cannot start a transaction within a transaction"; case orm_error_code::no_active_transaction: @@ -3978,20 +3975,52 @@ namespace sqlite_orm { template struct is_generated_always : polyfill::bool_constant> {}; - /** - * PRIMARY KEY INSERTABLE traits. + // Custom type: programmer's responsibility to garantee data integrity in the value range of a 64-bit signed integer + template + inline constexpr bool is_rowid_alias_capable_v = std::is_base_of>::value; + + // For integer types: further checks + template + inline constexpr bool is_rowid_alias_capable_v< + F, + std::enable_if_t::value && + (sizeof(F) == sizeof(sqlite_int64) && + std::is_signed::value == std::is_signed::value)>> = true; + + // [Deprecation notice] For integral types other than 64-bit signed integer, AUTOINCREMENT is deprecated on PRIMARY KEY columns + // and will be turned into a static_assert failure in v1.11 + template + [[deprecated(R"(Use a 64-bit signed integer for the "rowid" key alias)")]] + inline constexpr bool is_rowid_alias_capable_v< + F, + std::enable_if_t::value && + (sizeof(F) != sizeof(sqlite_int64) || + std::is_signed::value != std::is_signed::value)>> = true; + + // For non-integer types: static_assert failure + template + inline constexpr bool + is_rowid_alias_capable_v>::value>> = + false; + + /** + * COLUMN PRIMARY KEY INSERTABLE traits. + * + * A column primary key is considered implicitly insertable if: + * - it is an INTEGER PRIMARY KEY (and thus an alias for the "rowid" key), + * - or has a default value. + * + * In terms of C++ types, this means that the field type must be capable of representing a 64-bit signed integer, + * or the column is declared with a DEFAULT constraint. + * + * Implementation note: using a struct template in favor of a template alias so that the stack leading to a deprecation message is shorter. */ template - struct is_primary_key_insertable - : polyfill::disjunction< - mpl::invoke_t, - check_if_has_template>, - constraints_type_t>, - std::is_base_of>>> { - - static_assert(tuple_has, is_primary_key>::value, - "an unexpected type was passed"); - }; + struct is_pkcol_implicitly_insertable + : mpl::invoke_t< + mpl::disjunction>>>, + check_if_has_template>, + constraints_type_t> {}; template using is_column_constraint = mpl::invoke_t>, @@ -4071,7 +4100,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {{}}; } -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) /** * UNINDEXED column constraint builder function. Used in FTS virtual tables. * @@ -8746,6 +8775,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } } +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) struct fts5; /** @@ -8810,6 +8840,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {std::move(x), std::move(y), std::move(z)}; } #endif +#endif } // #include "alias_traits.h" @@ -8913,11 +8944,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "schema/column.h" +#include // sqlite_int64 #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::tuple #include // std::string #include // std::unique_ptr -#include // std::is_same, std::is_member_object_pointer +#include // std::enable_if, std::is_same, std::is_member_object_pointer, std::is_signed #include // std::move #endif @@ -8927,10 +8959,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "../tuple_helper/tuple_filter.h" -// #include "../type_traits.h" - // #include "../member_traits/member_traits.h" +// #include "../type_traits.h" + // #include "../type_is_nullable.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE @@ -9044,6 +9076,14 @@ namespace sqlite_orm { return tuple_has::value; } + /** + * Checks whether contraints contain specified class template. + */ + template class Primary> + constexpr static bool is_template() { + return tuple_has_template::value; + } + /** * Simplified interface for `DEFAULT` constraint * @return string representation of default value if it exists otherwise nullptr @@ -9125,19 +9165,65 @@ namespace sqlite_orm { field_type_t, filter_tuple_sequence_t::template fn>>; -#if SQLITE_VERSION_NUMBER >= 3031000 + // Custom type: programmer's responsibility to garantee data integrity in the value range of a 64-bit signed integer + template + struct check_pkcol { + static constexpr void validate_column_primary_key_with_autoincrement() {} + }; + + // For integer types: further checks + template + struct check_pkcol::value>> { + // For 64-bit signed integer type: valid + template::value == std::is_signed::value, + bool> = true> + static constexpr void validate_column_primary_key_with_autoincrement() {} + + // [Deprecation notice] For integral types other than 64-bit signed integer, AUTOINCREMENT is deprecated on PRIMARY KEY columns + // and will be turned into a static_assert failure in v1.11 + template::value != std::is_signed::value, + bool> = true> + [[deprecated( + R"(Use a 64-bit signed integer for AUTOINCREMENT on an INTEGER PRIMARY KEY as an alias for the "rowid" key)")]] static constexpr void + validate_column_primary_key_with_autoincrement() {} + }; + + // For non-integer types: static_assert failure + template + struct check_pkcol>::value>> { + static constexpr void validate_column_primary_key_with_autoincrement() { + static_assert( + polyfill::always_false_v, + R"(AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY as an alias for the "rowid" key)"); + } + }; + + template + constexpr void validate_column_definition() { + using constraints_type = std::tuple; + + static_assert(polyfill::conjunction_v...>, "Incorrect column constraints"); + + if constexpr (tuple_has_template::value) { + check_pkcol>::validate_column_primary_key_with_autoincrement(); + } + } + /** * Factory function for a column definition from a member object pointer for hidden virtual table columns. */ template = true> hidden_column make_hidden_column(std::string name, M memberPointer, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + static_assert(polyfill::conjunction_v...>, "Incorrect column constraints"); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}; } -#endif } } @@ -9148,7 +9234,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template = true> internal::column_t make_column(std::string name, M memberPointer, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::validate_column_definition(); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! @@ -9166,7 +9252,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::column_t make_column(std::string name, S setter, G getter, Op... constraints) { static_assert(std::is_same, internal::getter_field_type_t>::value, "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::validate_column_definition(); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! @@ -9184,7 +9270,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::column_t make_column(std::string name, G getter, S setter, Op... constraints) { static_assert(std::is_same, internal::getter_field_type_t>::value, "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::validate_column_definition(); // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! @@ -9773,7 +9859,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using namespace ::sqlite_orm::internal; static_assert(is_cte_moniker_v, "Moniker must be a CTE moniker"); static_assert((!is_builtin_numeric_column_alias_v && ...), - "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + "Numeric column aliases are reserved for referencing columns locally within a single CTE"); using builder_type = cte_builder, decay_explicit_column_t>>; @@ -9790,7 +9876,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { constexpr auto cte(ExplicitCols... explicitColumns) { using namespace ::sqlite_orm::internal; static_assert((!is_builtin_numeric_column_alias_v && ...), - "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + "Numeric column aliases are reserved for referencing columns locally within a single CTE"); using builder_type = cte_builder, transform_tuple_t, decay_explicit_column_t>>; @@ -10249,7 +10335,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { requires (internal::is_unusable_for_xdestroy) { static_assert(polyfill::always_false_v, - "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); + "A function pointer, which is not of type xdestroy_fn_t, is prohibited"); return nullptr; } @@ -10296,7 +10382,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template, bool> = true> constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) { static_assert(polyfill::always_false_v, - "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); + "A function pointer, which is not of type xdestroy_fn_t, is prohibited"); return nullptr; } @@ -12368,7 +12454,7 @@ namespace sqlite_orm { using colalias_index = find_tuple_type>; static_assert(colalias_index::value < std::tuple_size_v, - "No such column mapped into the CTE."); + "No such column mapped into the CTE"); using type = std::tuple_element_t; }; #endif @@ -12620,13 +12706,9 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "../functional/mpl.h" -// #include "../tuple_helper/tuple_iteration.h" - // #include "../tuple_helper/tuple_filter.h" -// #include "../member_traits/member_traits.h" - -// #include "../field_of.h" +// #include "../tuple_helper/tuple_transformer.h" // #include "../type_traits.h" @@ -12637,11 +12719,13 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "table_base.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::is_member_pointer +#include // std::is_member_pointer, std::remove_cvref #include // std::string -#include // std::tuple +#include // std::tuple, std::tuple_size #endif +// #include "../functional/cxx_type_traits_polyfill.h" + // #include "../functional/cxx_functional_polyfill.h" // #include "../functional/mpl.h" @@ -12698,7 +12782,7 @@ namespace sqlite_orm::internal { } /* - * Returns the number of columns having the specified constraint trait. + * Returns the number of columns not having the specified constraint trait. */ template class Trait> static constexpr int count_of_columns_excluding() { @@ -12755,7 +12839,7 @@ namespace sqlite_orm::internal { }; /** - * Encapsulates table elements, i.e. columns and constraints for a type of table that can have primary keys - base tables and usually virtual tables -, + * Encapsulates table elements, i.e. columns and constraints for a type of table that can have a primary key - base tables and usually virtual tables -, * and provides additional methods to those of a generic table definition in order to deal with primary key columns. */ template @@ -12764,18 +12848,21 @@ namespace sqlite_orm::internal { using elements_type = elements_type_t; /** - * Call passed lambda with all defined primary keys. + * Call passed lambda with the defined table primary key. */ template - void for_each_primary_key(L&& lambda) const { + void visit_table_primary_key(L&& lambda) const { using pk_index_sequence = filter_tuple_sequence_t; + // note: already checked in `validate_base_table_definition()` + static_assert(pk_index_sequence::size() <= 1); + // note: we use the tuple iteration function for simplicity, even if we know there is at most one primary key iterate_tuple(this->elements, pk_index_sequence{}, lambda); } - std::vector composite_key_columns_names() const { + std::vector table_key_columns_names() const { std::vector res; - this->for_each_primary_key([this, &res](auto& primaryKey) { - res = this->composite_key_columns_names(primaryKey); + this->visit_table_primary_key([this, &res](auto& primaryKey) { + res = this->table_key_columns_names(primaryKey); }); return res; } @@ -12788,7 +12875,7 @@ namespace sqlite_orm::internal { pkcol_index_sequence{}, &column_identifier::name); } else { - return this->composite_key_columns_names(); + return this->table_key_columns_names(); } } @@ -12799,13 +12886,13 @@ namespace sqlite_orm::internal { call_as_template_base([&lambda](const auto& column) { lambda(column.member_pointer); })); - this->for_each_primary_key([&lambda](auto& primaryKey) { + this->visit_table_primary_key([&lambda](auto& primaryKey) { iterate_tuple(primaryKey.columns, lambda); }); } template - std::vector composite_key_columns_names(const primary_key_t& primaryKey) const { + std::vector table_key_columns_names(const primary_key_t& primaryKey) const { return create_from_tuple>(primaryKey.columns, [this, empty = std::string{}](auto& memberPointer) { if (const std::string* columnName = @@ -12819,22 +12906,48 @@ namespace sqlite_orm::internal { }; template - bool exists_in_composite_primary_key(const insertable_table_definition& definition, - const column_field& column) { + bool table_primary_key_contains([[maybe_unused]] const insertable_table_definition& definition, + [[maybe_unused]] const column_field& column) { + bool res = false; + // note: though `visit_table_primary_key()` does no work if a column primary key exists, we try to save the compiler some work with this check up front + if constexpr (/*bool hasNoColumnPK =*/!insertable_table_definition::template count_of_columns_with< + is_primary_key>()) { + definition.visit_table_primary_key([&column, &res](auto& primaryKey) { + using colrefs_tuple = decltype(primaryKey.columns); + using same_type_index_sequence = + filter_tuple_sequence_t>::template fn, + member_field_type_t>; + iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { + if (compare_fields(memberPointer, column.member_pointer) || + compare_fields(memberPointer, column.setter)) { + res = true; + } + }); + }); + } + return res; + } + + template + bool is_single_table_primary_key([[maybe_unused]] const insertable_table_definition& definition, + [[maybe_unused]] const column_field& column) { bool res = false; - definition.for_each_primary_key([&column, &res](auto& primaryKey) { - using colrefs_tuple = decltype(primaryKey.columns); - using same_type_index_sequence = - filter_tuple_sequence_t>::template fn, - member_field_type_t>; - iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { - if (compare_fields(memberPointer, column.member_pointer) || - compare_fields(memberPointer, column.setter)) { - res = true; + // note: though `visit_table_primary_key()` does no work if a column primary key exists, we try to save the compiler some work with this check up front + if constexpr (/*bool hasNoColumnPK =*/!insertable_table_definition::template count_of_columns_with< + is_primary_key>()) { + definition.visit_table_primary_key([&column, &res](auto& primaryKey) { + // note: use `decltype(primaryKey)` instead of `decltype(primaryKey.columns)` otherwise msvc 141 chokes on the `if constexpr` below + using colrefs_tuple = columns_tuple_t>; + if constexpr (std::tuple_size::value == 1) { + auto& memberPointer = std::get<0>(primaryKey.columns); + if (compare_fields(memberPointer, column.member_pointer) || + compare_fields(memberPointer, column.setter)) { + res = true; + } } }); - }); + } return res; } @@ -13118,6 +13231,25 @@ namespace sqlite_orm { template using is_base_table = polyfill::bool_constant>; + + template + constexpr void validate_base_table_definition() { + static_assert(polyfill::conjunction_v...>, + "Incorrect base table elements or constraints"); + + using elements_type = std::tuple; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + + static_assert(pk_index_sequence::size() + pkcol_index_sequence::size() <= 1, + "A base table can only have 1 primary key definition"); + if constexpr (pk_index_sequence::size() > 0) { + constexpr size_t nTablePrimaryKeyColumns = + nested_tuple_size_for_t::value; + + static_assert(nTablePrimaryKeyColumns > 0, "Tabel primary key definition must contain one column"); + } + } } } @@ -13129,10 +13261,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template>::object_type> internal::base_table make_table(std::string name, Cs... definition) { - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); - - return {std::move(name), std::make_tuple(std::forward(definition)...)}; + internal::validate_base_table_definition(); + return {std::move(name), std::tuple{std::forward(definition)...}}; } /** @@ -13142,10 +13272,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::base_table make_table(std::string name, Cs... definition) { - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); - - return {std::move(name), std::make_tuple(std::forward(definition)...)}; + internal::validate_base_table_definition(); + return {std::move(name), std::tuple{std::forward(definition)...}}; } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -13224,7 +13352,7 @@ namespace sqlite_orm { // lookup ColAlias in the final column references using colalias_index = find_tuple_type>; static_assert(colalias_index::value < std::tuple_size_v, - "No such column mapped into the CTE."); + "No such column mapped into the CTE"); return &aliased_field>::field; } @@ -13257,7 +13385,7 @@ namespace sqlite_orm { // lookup ColAlias in the final column references using colalias_index = find_tuple_type>; static_assert(colalias_index::value < std::tuple_size_v, - "No such column mapped into the CTE."); + "No such column mapped into the CTE"); // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `cte_table<>::find_column_name()` mechanism; // however we have the column index already. @@ -14471,7 +14599,7 @@ namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::unique_ptr #include // std::string -#include // std::integral_constant, std::declval +#include // std::integral_constant, std::declval, std::is_convertible #include // std::move, std::forward, std::exchange, std::pair #include // std::tuple #endif @@ -15869,6 +15997,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::replace_range_t replace_range(It from, It to, Projection project = {}) { + // validate up front that projected type is convertible to mapped object type, avoiding hard to read error messages later; + // note: we use `is_convertible` instead of `is_invocable_r` because we do not create dangling references in `storage_t<>::execute()` + using projected_type = decltype(polyfill::invoke(std::declval(), *std::declval())); + static_assert(std::is_convertible::value, + "Projected type must be convertible to mapped object type"); + return {{std::move(from), std::move(to)}, std::move(project)}; } @@ -15903,6 +16037,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::insert_range_t insert_range(It from, It to, Projection project = {}) { + // validate up front that projected type is convertible to mapped object type, avoiding hard to read error messages later; + // note: we use `is_convertible` instead of `is_invocable_r` because we do not create dangling references in `storage_t<>::execute()` + using projected_type = decltype(polyfill::invoke(std::declval(), *std::declval())); + static_assert(std::is_convertible::value, + "Projected type must be convertible to mapped object type"); + return {{std::move(from), std::move(to)}, std::move(project)}; } @@ -17785,7 +17925,7 @@ namespace sqlite_orm { using object_type = polyfill::remove_cvref_t; auto& table = pick_table(context.db_objects); - table.template for_each_column_excluding(call_as_template_base( + table.template for_each_column_excluding( [&ss, &excluded, &context, &object, first = true](auto& column) mutable { if (excluded(column)) { return; @@ -17794,7 +17934,7 @@ namespace sqlite_orm { static constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] << serialize(polyfill::invoke(column.member_pointer, object), context); - })); + }); return ss; } @@ -20492,13 +20632,13 @@ namespace sqlite_orm { // No CTE for object expressions. template struct cte_column_names_collector> { - static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed"); }; // No CTE for object expressions. template struct cte_column_names_collector> { - static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed."); + static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed"); }; template @@ -22286,7 +22426,7 @@ namespace sqlite_orm { } }; -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) template<> struct statement_serializer { using statement_type = unindexed_t; @@ -22554,7 +22694,7 @@ namespace sqlite_orm { ss << "UPDATE " << streaming_identifier(table.name) << " SET "; table.template for_each_column_excluding>( [&table, &ss, &context, &object = get_ref(statement.object), first = true](auto& column) mutable { - if (exists_in_composite_primary_key(table, column)) { + if (table_primary_key_contains(table, column)) { return; } @@ -22565,7 +22705,7 @@ namespace sqlite_orm { ss << " WHERE "; table.for_each_column( [&table, &context, &ss, &object = get_ref(statement.object), first = true](auto& column) mutable { - if (!column.template is() && !exists_in_composite_primary_key(table, column)) { + if (!column.template is() && !table_primary_key_contains(table, column)) { return; } @@ -22674,14 +22814,21 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); - using is_without_rowid = typename std::remove_reference_t::is_without_rowid; + using table_type = polyfill::remove_cvref_t; + using without_rowid = typename table_type::is_without_rowid; std::vector> columnNames; - table.template for_each_column_excluding< - mpl::conjunction>, - mpl::disjunction_fn>>( + table.template for_each_column_excluding< /// + mpl::disjunction< + mpl::conjunction>, mpl::quote_fn>, + mpl::quote_fn>>( /// [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!without_rowid::value && + (is_single_table_primary_key(table, column) || + (column.template is_template() && table_primary_key_contains(table, column)))) { + return; + } else if (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))) { return; } @@ -22700,10 +22847,15 @@ namespace sqlite_orm { if (columnNamesCount) { ss << " (" << streaming_field_values_excluding( - mpl::conjunction>, - mpl::disjunction_fn>{}, + mpl::disjunction>, + mpl::quote_fn>, + mpl::quote_fn>{}, [&table](auto& column) { - return exists_in_composite_primary_key(table, column); + return (!without_rowid::value && (is_single_table_primary_key(table, column) || + (column.template is_template() && + table_primary_key_contains(table, column)))) || + (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))); }, context, get_ref(statement.object)) @@ -22846,14 +22998,21 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); - using is_without_rowid = typename std::remove_reference_t::is_without_rowid; + using table_type = polyfill::remove_cvref_t; + using without_rowid = typename table_type::is_without_rowid; std::vector> columnNames; - table.template for_each_column_excluding< - mpl::conjunction>, - mpl::disjunction_fn>>( + table.template for_each_column_excluding< /// + mpl::disjunction< + mpl::conjunction>, mpl::quote_fn>, + mpl::quote_fn>>( /// [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!without_rowid::value && + (is_single_table_primary_key(table, column) || + (column.template is_template() && table_primary_key_contains(table, column)))) { + return; + } else if (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))) { return; } @@ -22939,11 +23098,7 @@ namespace sqlite_orm { ss << "SELECT " << streaming_table_column_names(table, std::string{}) << " FROM " << streaming_identifier(table.name) << " WHERE "; - auto primaryKeyColumnNames = table.primary_key_column_names(); - if (primaryKeyColumnNames.empty()) { - throw std::system_error{orm_error_code::table_has_no_primary_key_column}; - } - + const auto primaryKeyColumnNames = table.primary_key_column_names(); #ifdef SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED static constexpr std::array sep = {" AND ", ""}; for (bool first = true; const std::string& pkName: primaryKeyColumnNames) { @@ -23749,7 +23904,7 @@ namespace sqlite_orm { // No CTE for object expression. template struct column_expression_type, void> { - static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed"); }; /** @@ -24055,7 +24210,7 @@ namespace sqlite_orm { (!is_builtin_numeric_column_alias_v< alias_holder_type_or_none_t>> && ...), - "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + "Numeric column aliases are reserved for referencing columns locally within a single CTE"); return std::tuple{ determine_cte_colref(dbObjects, get(subselectColRefs), get(explicitColRefs))...}; @@ -24361,6 +24516,17 @@ namespace sqlite_orm { "type is not mapped to storage"); } + template + void assert_primary_key_type() const { + using table_type = storage_pick_table_t; + using elements_type = elements_type_t; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + + static_assert(pk_index_sequence::size() + pkcol_index_sequence::size() == 1, + "The table must have primary key"); + } + template void assert_updatable_type() const { using table_type = storage_pick_table_t; @@ -24368,15 +24534,14 @@ namespace sqlite_orm { using column_index_sequence = col_index_sequence_of; using pk_index_sequence = filter_tuple_sequence_t; using pkcol_index_sequence = col_index_sequence_with; - constexpr size_t dedicatedPrimaryKeyColumnsCount = + constexpr size_t nTablePrimaryKeyColumns = nested_tuple_size_for_t::value; - constexpr size_t primaryKeyColumnsCount = - dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); - constexpr ptrdiff_t nonPrimaryKeysColumnsCount = column_index_sequence::size() - primaryKeyColumnsCount; - static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); + constexpr size_t nPrimaryKeyColumns = nTablePrimaryKeyColumns + pkcol_index_sequence::size(); + constexpr ptrdiff_t nNonPrimaryKeysColumns = column_index_sequence::size() - nPrimaryKeyColumns; + static_assert(nPrimaryKeyColumns > 0, "A table without primary keys cannot be updated"); static_assert( - nonPrimaryKeysColumnsCount > 0, + nNonPrimaryKeysColumns > 0, "A table with only primary keys cannot be updated. You need at least 1 non-primary key column"); } @@ -24391,17 +24556,15 @@ namespace sqlite_orm { void assert_insertable_type() const { using elements_type = elements_type_t
; using pkcol_index_sequence = col_index_sequence_with; - static_assert( - count_filtered_tuple::value <= 1, - "Attempting to execute 'insert' request into an noninsertable table was detected. " - "Insertable table cannot contain > 1 primary keys. Please use 'replace' instead of " - "'insert', or you can use 'insert' with explicit column listing."); - static_assert(count_filtered_tuple::template fn, - pkcol_index_sequence>::value == 0, - "Attempting to execute 'insert' request into an noninsertable table was detected. " - "Insertable table cannot contain non-standard primary keys. Please use 'replace' instead " - "of 'insert', or you can use 'insert' with explicit column listing."); + if constexpr (pkcol_index_sequence::size()) { + using pkcol_type = + std::tuple_element_t(pkcol_index_sequence{}), elements_type>; + static_assert( + mpl::invoke_t, pkcol_type>::value, + "While SQLite allows primary keys of any type, sqlite_orm restricts an ordinary 'insert' into " + "tables with single-column primary keys to those with columns that are aliases for the 'rowid'." + "Instead, please use 'replace' or 'insert' with explicitly specified columns."); + } } template @@ -24680,6 +24843,7 @@ namespace sqlite_orm { template O get(Ids... ids) { this->assert_mapped_type(); + this->assert_primary_key_type(); auto statement = this->prepare(sqlite_orm::get(std::forward(ids)...)); return this->execute(statement); } @@ -24698,6 +24862,7 @@ namespace sqlite_orm { template std::unique_ptr get_pointer(Ids... ids) { this->assert_mapped_type(); + this->assert_primary_key_type(); auto statement = this->prepare(sqlite_orm::get_pointer(std::forward(ids)...)); return this->execute(statement); } @@ -24735,6 +24900,7 @@ namespace sqlite_orm { template std::optional get_optional(Ids... ids) { this->assert_mapped_type(); + this->assert_primary_key_type(); auto statement = this->prepare(sqlite_orm::get_optional(std::forward(ids)...)); return this->execute(statement); } @@ -25075,24 +25241,33 @@ namespace sqlite_orm { } template - int insert(const O& o, columns_t cols) { + int64 insert(const O& o, columns_t cols) { static_assert(cols.count > 0, "Use insert or replace with 1 argument instead"); this->assert_mapped_type(); auto statement = this->prepare(sqlite_orm::insert(std::ref(o), std::move(cols))); - return int(this->execute(statement)); + return this->execute(statement); } /** - * Insert routine. Inserts object with all non primary key fields in passed object. Id of passed - * object doesn't matter. - * @return id of just created object. + * Ordinary insert routine. + * + * - For objects mapped to a table with rowid and a single primary key: + * Inserts a record with all fields of a mapped object that are not primary key columns. + * The 'ID' of the specified object is irrelevant. + * - For objects mapped to a table with rowid and a composite primary key or no primary key: + * Inserts a record with all fields of a mapped object. + * - For objects mapped to a table without rowid: + * Inserts a record with all fields of a mapped object. + * + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. */ template - int insert(const O& o) { + int64 insert(const O& o) { this->assert_mapped_type(); this->assert_insertable_type(); auto statement = this->prepare(sqlite_orm::insert(std::ref(o))); - return int(this->execute(statement)); + return this->execute(statement); } /** @@ -25608,8 +25783,8 @@ namespace sqlite_orm { } /** - * @return The rowid of the last inserted row. - * @note The returned rowid is only meaningful in single-thread contexts. + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. */ template int64 execute(const prepared_statement_t>& statement) { @@ -25635,7 +25810,7 @@ namespace sqlite_orm { sqlite3_stmt* stmt = reset_stmt(statement.stmt); auto processObject = [&table = this->get_table(), - bindValue = field_value_binder{stmt}](auto& object) mutable { + bindValue = field_value_binder{stmt}](const object_type& object) mutable { table.template for_each_column_excluding( call_as_template_base([&bindValue, &object](auto& column) { bindValue(polyfill::invoke(column.member_pointer, object)); @@ -25652,8 +25827,10 @@ namespace sqlite_orm { auto& transformer = statement.expression.transformer; std::for_each(statement.expression.range.first, statement.expression.range.second, - [&processObject, &transformer](auto& item) { - const object_type& object = polyfill::invoke(transformer, item); + [&processObject, &transformer](auto&& item) { + using item_type = decltype(item); + const object_type& object = + polyfill::invoke(transformer, std::forward(item)); processObject(object); }); #endif @@ -25666,8 +25843,8 @@ namespace sqlite_orm { } /** - * @return The rowid of the last inserted row. - * @note The returned rowid is only meaningful in single-thread contexts. + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. */ template, is_insert_range>::value, bool> = true> @@ -25677,17 +25854,25 @@ namespace sqlite_orm { sqlite3_stmt* stmt = reset_stmt(statement.stmt); auto processObject = [&table = this->get_table(), - bindValue = field_value_binder{stmt}](auto& object) mutable { - using is_without_rowid = typename std::remove_reference_t::is_without_rowid; - - table.template for_each_column_excluding< - mpl::conjunction>, - mpl::disjunction_fn>>( - call_as_template_base([&table, &bindValue, &object](auto& column) { - if (!exists_in_composite_primary_key(table, column)) { - bindValue(polyfill::invoke(column.member_pointer, object)); + bindValue = field_value_binder{stmt}](const object_type& object) mutable { + using table_type = polyfill::remove_cvref_t; + using without_rowid = typename table_type::is_without_rowid; + + table.template for_each_column_excluding< /// + mpl::disjunction< + mpl::conjunction>, mpl::quote_fn>, + mpl::quote_fn>>( /// + [&table, &bindValue, &object](auto& column) { + if (!without_rowid::value && (is_single_table_primary_key(table, column) || + (column.template is_template() && + table_primary_key_contains(table, column)))) { + return; + } else if (without_rowid::value && (column.template is_template() && + table_primary_key_contains(table, column))) { + return; } - })); + bindValue(polyfill::invoke(column.member_pointer, object)); + }); }; if constexpr (is_insert_range::value) { @@ -25700,8 +25885,10 @@ namespace sqlite_orm { auto& transformer = statement.expression.transformer; std::for_each(statement.expression.range.first, statement.expression.range.second, - [&processObject, &transformer](auto& item) { - const object_type& object = polyfill::invoke(transformer, item); + [&processObject, &transformer](auto&& item) { + using item_type = decltype(item); + const object_type& object = + polyfill::invoke(transformer, std::forward(item)); processObject(object); }); #endif @@ -25732,14 +25919,16 @@ namespace sqlite_orm { auto& object = get_object(statement.expression); table.template for_each_column_excluding>( call_as_template_base([&table, &bindValue, &object](auto& column) { - if (!exists_in_composite_primary_key(table, column)) { - bindValue(polyfill::invoke(column.member_pointer, object)); + if (table_primary_key_contains(table, column)) { + return; } + bindValue(polyfill::invoke(column.member_pointer, object)); })); table.for_each_column([&table, &bindValue, &object](auto& column) { - if (column.template is() || exists_in_composite_primary_key(table, column)) { - bindValue(polyfill::invoke(column.member_pointer, object)); + if (!column.template is() && !table_primary_key_contains(table, column)) { + return; } + bindValue(polyfill::invoke(column.member_pointer, object)); }); this->executor.perform_single_step(stmt); @@ -26062,17 +26251,17 @@ namespace sqlite_orm { column.template is(), column.template is()); }); - auto compositeKeyColumnNames = this->composite_key_columns_names(); + const auto tableKeyColumnNames = this->table_key_columns_names(); #if defined(SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_CPP20_RANGES_SUPPORTED) - for (int n = 1; const std::string& columnName: compositeKeyColumnNames) { + for (int n = 1; const std::string& columnName: tableKeyColumnNames) { if (auto it = std::ranges::find(res, columnName, &table_xinfo::name); it != res.end()) { it->pk = n; } ++n; } #else - for (size_t i = 0; i < compositeKeyColumnNames.size(); ++i) { - const std::string& columnName = compositeKeyColumnNames[i]; + for (size_t i = 0; i < tableKeyColumnNames.size(); ++i) { + const std::string& columnName = tableKeyColumnNames[i]; auto it = std::find_if(res.begin(), res.end(), [&columnName](const table_xinfo& ti) { return ti.name == columnName; }); @@ -27021,7 +27210,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "fts5.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) #include // std::tuple_size, std::make_tuple, std::get #include // std::forward, std::move #include @@ -27042,7 +27231,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "../constraints.h" -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) namespace sqlite_orm::internal { template inline constexpr bool is_fts5_table_element_or_constraint_v = diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3a6c311e1..82d0e5367 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,10 +79,8 @@ if(MSVC) # otherwise for some reason the linker will not pick up `wmain`, which is provided by the static Catch2 library target_link_options(unit_tests PRIVATE "/ENTRY:wmainCRTStartup") endif() -endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") target_compile_options(unit_tests PUBLIC - -Wno-c++23-extensions # disabled due to unit tests testing deprecated features -Wno-deprecated-declarations # perfectly valid to have missing field initializers in library's test code diff --git a/tests/ast_iterator_tests.cpp b/tests/ast_iterator_tests.cpp index 66000df9d..eb9598086 100644 --- a/tests/ast_iterator_tests.cpp +++ b/tests/ast_iterator_tests.cpp @@ -26,8 +26,10 @@ TEST_CASE("ast_iterator") { int id = 0; std::string name; }; +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) constexpr auto user_table = c(); using user_hidden = fts5::hidden_fields_of; +#endif std::vector typeIndexes; decltype(typeIndexes) expected; @@ -270,6 +272,7 @@ TEST_CASE("ast_iterator") { expected.push_back(typeid(const char*)); iterate_ast(node, lambda); } +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) SECTION("match any column") { constexpr auto node = match(user_table->*&fts5::hidden::any, "Plazma"); expected.push_back(typeid(const char*)); @@ -280,6 +283,7 @@ TEST_CASE("ast_iterator") { expected.push_back(typeid(const char*)); iterate_ast(node, lambda); } +#endif SECTION("match specific column") { constexpr auto node = match(&User::name, "Claude"); expected.push_back(typeid(const char*)); @@ -454,6 +458,7 @@ TEST_CASE("ast_iterator") { } #endif #endif +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) SECTION("highlight using explicit template parameter") { auto expression = highlight(0, std::string(""), std::string("")); expected.push_back(typeid(int)); @@ -490,6 +495,7 @@ TEST_CASE("ast_iterator") { iterate_ast(expression3, nodeLambda); } #endif +#endif #ifdef SQLITE_ENABLE_DBSTAT_VTAB SECTION("table-valued") { auto expression = from(dbstat_table("main")); diff --git a/tests/backup_tests.cpp b/tests/backup_tests.cpp index c90680728..524e52c38 100644 --- a/tests/backup_tests.cpp +++ b/tests/backup_tests.cpp @@ -6,7 +6,7 @@ using namespace sqlite_orm; namespace { struct User { - int id = 0; + int64 id = 0; std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED @@ -19,7 +19,7 @@ namespace { }; struct MarvelHero { - int id = 0; + int64 id = 0; std::string name; std::string abilities; }; diff --git a/tests/built_in_functions_tests/core_functions_tests.cpp b/tests/built_in_functions_tests/core_functions_tests.cpp index a86b363a3..81a5cbd38 100644 --- a/tests/built_in_functions_tests/core_functions_tests.cpp +++ b/tests/built_in_functions_tests/core_functions_tests.cpp @@ -310,16 +310,20 @@ TEST_CASE("instr") { namespace replace_func_local { struct Contact { - int id = 0; + int64 id = 0; std::string firstName; std::string lastName; std::string phone; - }; - bool operator==(const Contact& lhs, const Contact& rhs) { - return lhs.id == rhs.id && lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && - lhs.phone == rhs.phone; - } +#ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED + friend bool operator==(const Contact&, const Contact&) = default; +#else + friend bool operator==(const Contact& lhs, const Contact& rhs) { + return lhs.id == rhs.id && lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && + lhs.phone == rhs.phone; + } +#endif + }; } TEST_CASE("replace func") { diff --git a/tests/constraints/default.cpp b/tests/constraints/default.cpp index 14979883d..6f05a5228 100644 --- a/tests/constraints/default.cpp +++ b/tests/constraints/default.cpp @@ -7,7 +7,7 @@ using namespace sqlite_orm; // appeared after #55 TEST_CASE("Default value") { struct User { - int userId; + int64 userId; std::string name; int age; std::string email; diff --git a/tests/constraints/foreign_key.cpp b/tests/constraints/foreign_key.cpp index 39e14e795..ff64b64a9 100644 --- a/tests/constraints/foreign_key.cpp +++ b/tests/constraints/foreign_key.cpp @@ -77,7 +77,7 @@ TEST_CASE("Foreign key 2") { test1() {}; // Variables - int id; + int64 id; std::string val1; std::string val2; }; @@ -88,8 +88,8 @@ TEST_CASE("Foreign key 2") { test2() {}; // Variables - int id; - int fk_id; + int64 id; + int64 fk_id; std::string val1; std::string val2; }; @@ -133,7 +133,7 @@ TEST_CASE("Foreign key 2") { TEST_CASE("Foreign key with inheritance") { struct Person { - int id; + int64 id; std::string name; int age; }; @@ -157,9 +157,9 @@ TEST_CASE("Foreign key with inheritance") { // Define Classroom class referencing Teacher and Student struct Classroom { - int id; - int teacher_id; // Foreign key referencing Teacher - int student_id; // Foreign key referencing Student + int64 id; + int64 teacher_id; // Foreign key referencing Teacher + int64 student_id; // Foreign key referencing Student std::string room_name; }; diff --git a/tests/constraints/unique.cpp b/tests/constraints/unique.cpp index dd78c499e..cb34e9e16 100644 --- a/tests/constraints/unique.cpp +++ b/tests/constraints/unique.cpp @@ -12,18 +12,18 @@ TEST_CASE("Unique") { #endif struct Contact { - int id = 0; + int64 id = 0; std::string firstName; std::string lastName; std::string email; }; struct Shape { - int id = 0; + int64 id = 0; std::string backgroundColor; std::string foregroundColor; }; struct List { - int id = 0; + int64 id = 0; std::unique_ptr email; }; diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index a6c1724e7..490f64afa 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -39,16 +39,16 @@ TEST_CASE("logger") { auto didRunQuery = GENERATE(Callback(DidLogsCollector()), Callback()); struct User { - int id = 0; + int64 id = 0; std::string name; }; struct Visit { - int id = 0; - int userId = 0; + int64 id = 0; + int64 userId = 0; std::string date; }; struct VisitLog { - int id = 0; + int64 id = 0; std::string message; }; diff --git a/tests/operators/between.cpp b/tests/operators/between.cpp index c1e52f1fa..3cae5223b 100644 --- a/tests/operators/between.cpp +++ b/tests/operators/between.cpp @@ -5,7 +5,7 @@ using namespace sqlite_orm; TEST_CASE("Between") { struct Object { - int id = 0; + int64 id = 0; }; auto storage = diff --git a/tests/operators/glob.cpp b/tests/operators/glob.cpp index eac12beec..0cf22a53a 100644 --- a/tests/operators/glob.cpp +++ b/tests/operators/glob.cpp @@ -10,7 +10,7 @@ using namespace sqlite_orm; namespace { struct Employee { - int id = 0; + int64 id = 0; std::string firstName; std::string lastName; float salary = 0; diff --git a/tests/operators/like.cpp b/tests/operators/like.cpp index e921245ec..4c45b77fa 100644 --- a/tests/operators/like.cpp +++ b/tests/operators/like.cpp @@ -5,7 +5,7 @@ using namespace sqlite_orm; TEST_CASE("Like operator") { struct User { - int id = 0; + int64 id = 0; std::string name; }; struct Pattern { diff --git a/tests/prepared_statement_tests/get_pointer.cpp b/tests/prepared_statement_tests/get_pointer.cpp index 426a29879..8eba5f276 100644 --- a/tests/prepared_statement_tests/get_pointer.cpp +++ b/tests/prepared_statement_tests/get_pointer.cpp @@ -35,8 +35,8 @@ TEST_CASE("Prepared get pointer") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); { auto statement = storage.prepare(get_pointer(1)); diff --git a/tests/prepared_statement_tests/insert.cpp b/tests/prepared_statement_tests/insert.cpp index c1b3808d6..e6b659a43 100644 --- a/tests/prepared_statement_tests/insert.cpp +++ b/tests/prepared_statement_tests/insert.cpp @@ -151,8 +151,8 @@ TEST_CASE("Prepared insert") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); User user{0, "Stromae"}; SECTION("by ref") { diff --git a/tests/prepared_statement_tests/insert_explicit.cpp b/tests/prepared_statement_tests/insert_explicit.cpp index 17af43db9..ed60013c2 100644 --- a/tests/prepared_statement_tests/insert_explicit.cpp +++ b/tests/prepared_statement_tests/insert_explicit.cpp @@ -41,8 +41,8 @@ TEST_CASE("Prepared insert explicit") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); SECTION("user two columns") { User user{5, "Eminem"}; diff --git a/tests/prepared_statement_tests/insert_range.cpp b/tests/prepared_statement_tests/insert_range.cpp index 42a1b9ea8..10eec267a 100644 --- a/tests/prepared_statement_tests/insert_range.cpp +++ b/tests/prepared_statement_tests/insert_range.cpp @@ -38,8 +38,8 @@ TEST_CASE("Prepared insert range") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); std::vector users; std::vector expected{{1, "Team BS"}, {2, "Shy'm"}, {3, "Maître Gims"}}; @@ -80,6 +80,13 @@ TEST_CASE("Prepared insert range") { REQUIRE(get<1>(statement) == usersPointers.end()); storage.execute(statement); } + SECTION("container with references") { + std::vector> usersRefs{users.begin(), users.end()}; + auto statement = storage.prepare(insert_range(usersRefs.begin(), usersRefs.end())); + REQUIRE(get<0>(statement) == usersRefs.begin()); + REQUIRE(get<1>(statement) == usersRefs.end()); + storage.execute(statement); + } expected.push_back(user); } SECTION("two") { diff --git a/tests/prepared_statement_tests/prepared_common.h b/tests/prepared_statement_tests/prepared_common.h index 8803d5ec8..2fefae28e 100644 --- a/tests/prepared_statement_tests/prepared_common.h +++ b/tests/prepared_statement_tests/prepared_common.h @@ -7,7 +7,7 @@ namespace PreparedStatementTests { struct User { - int id = 0; + sqlite_orm::int64 id = 0; std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED @@ -24,7 +24,7 @@ namespace PreparedStatementTests { }; struct Visit { - int id = 0; + sqlite_orm::int64 id = 0; decltype(User::id) userId = 0; long time = 0; }; diff --git a/tests/prepared_statement_tests/remove.cpp b/tests/prepared_statement_tests/remove.cpp index 2e5063208..0d62d777f 100644 --- a/tests/prepared_statement_tests/remove.cpp +++ b/tests/prepared_statement_tests/remove.cpp @@ -35,8 +35,8 @@ TEST_CASE("Prepared remove") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); SECTION("by val") { { diff --git a/tests/prepared_statement_tests/remove_all.cpp b/tests/prepared_statement_tests/remove_all.cpp index 647c5d2c1..ab8c0d074 100644 --- a/tests/prepared_statement_tests/remove_all.cpp +++ b/tests/prepared_statement_tests/remove_all.cpp @@ -35,8 +35,8 @@ TEST_CASE("Prepared remove all") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); SECTION("Without conditions") { auto statement = storage.prepare(remove_all()); diff --git a/tests/prepared_statement_tests/replace.cpp b/tests/prepared_statement_tests/replace.cpp index de9658fda..2a1e68966 100644 --- a/tests/prepared_statement_tests/replace.cpp +++ b/tests/prepared_statement_tests/replace.cpp @@ -96,8 +96,8 @@ TEST_CASE("Prepared replace") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); SECTION("by ref existing") { user = {1, "Stromae"}; expected.push_back(User{1, "Stromae"}); diff --git a/tests/prepared_statement_tests/replace_range.cpp b/tests/prepared_statement_tests/replace_range.cpp index 0505bb813..969cafa1e 100644 --- a/tests/prepared_statement_tests/replace_range.cpp +++ b/tests/prepared_statement_tests/replace_range.cpp @@ -37,8 +37,8 @@ TEST_CASE("Prepared replace range") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); std::vector users; std::vector> userPointers; @@ -75,11 +75,19 @@ TEST_CASE("Prepared replace range") { SECTION("pointers") { userPointers.push_back(std::make_unique(user)); auto statement = storage.prepare( - replace_range(userPointers.begin(), userPointers.end(), &std::unique_ptr::operator*)); + replace_range(userPointers.begin(), userPointers.end(), &std::unique_ptr::operator*)); REQUIRE(get<0>(statement) == userPointers.begin()); REQUIRE(get<1>(statement) == userPointers.end()); storage.execute(statement); } + SECTION("references") { + users.push_back(user); + std::vector> usersRefs{users.begin(), users.end()}; + auto statement = storage.prepare(replace_range(usersRefs.begin(), usersRefs.end())); + REQUIRE(get<0>(statement) == usersRefs.begin()); + REQUIRE(get<1>(statement) == usersRefs.end()); + storage.execute(statement); + } } SECTION("one existing and one new") { User user{2, "Raye"}; diff --git a/tests/prepared_statement_tests/select.cpp b/tests/prepared_statement_tests/select.cpp index ad19fea1b..b37eac992 100644 --- a/tests/prepared_statement_tests/select.cpp +++ b/tests/prepared_statement_tests/select.cpp @@ -35,8 +35,8 @@ TEST_CASE("Prepared select") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); SECTION("const access to bindable") { auto statement = storage.prepare(select(10)); @@ -165,7 +165,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - REQUIRE_THAT(rows, UnorderedEquals({1, 2, 3})); + REQUIRE_THAT(rows, UnorderedEquals({1, 2, 3})); } } } @@ -194,7 +194,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - REQUIRE_THAT(rows, UnorderedEquals({1, 3})); + REQUIRE_THAT(rows, UnorderedEquals({1, 3})); } } SECTION("by ref") { @@ -209,7 +209,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - REQUIRE_THAT(rows, UnorderedEquals({1, 3})); + REQUIRE_THAT(rows, UnorderedEquals({1, 3})); } } } @@ -226,7 +226,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - REQUIRE_THAT(rows, UnorderedEquals({1})); + REQUIRE_THAT(rows, UnorderedEquals({1})); } } SECTION("by ref") { @@ -245,7 +245,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - REQUIRE_THAT(rows, UnorderedEquals({1})); + REQUIRE_THAT(rows, UnorderedEquals({1})); } } } @@ -258,7 +258,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - std::vector> expected; + std::vector> expected; expected.push_back(std::make_tuple(1, "Team BS")); expected.push_back(std::make_tuple(2, "Shy'm")); expected.push_back(std::make_tuple(3, "Maître Gims")); @@ -276,7 +276,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - std::vector> expected; + std::vector> expected; expected.push_back(std::make_tuple("Shy'm", 2)); REQUIRE_THAT(rows, UnorderedEquals(expected)); } @@ -298,7 +298,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - std::vector> expected; + std::vector> expected; expected.push_back(std::make_tuple("Shy'm", 2)); REQUIRE_THAT(rows, UnorderedEquals(expected)); } @@ -342,7 +342,7 @@ TEST_CASE("Prepared select") { } SECTION("execute") { auto rows = storage.execute(statement); - std::vector> expected; + std::vector> expected; expected.push_back({User{1, "Team BS"}, 1, "Team BS", User{1, "Team BS"}}); expected.push_back({User{2, "Shy'm"}, 2, "Shy'm", User{2, "Shy'm"}}); expected.push_back({User{3, "Maître Gims"}, 3, "Maître Gims", User{3, "Maître Gims"}}); diff --git a/tests/prepared_statement_tests/update.cpp b/tests/prepared_statement_tests/update.cpp index bb7b61936..acbea120a 100644 --- a/tests/prepared_statement_tests/update.cpp +++ b/tests/prepared_statement_tests/update.cpp @@ -35,8 +35,8 @@ TEST_CASE("Prepared update") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); User user{2, "Stromae"}; SECTION("by ref") { diff --git a/tests/prepared_statement_tests/update_all.cpp b/tests/prepared_statement_tests/update_all.cpp index cfbbdc099..6aa1b1a13 100644 --- a/tests/prepared_statement_tests/update_all.cpp +++ b/tests/prepared_statement_tests/update_all.cpp @@ -35,8 +35,8 @@ TEST_CASE("Prepared update all") { storage.replace(User{2, "Shy'm"}); storage.replace(User{3, "Maître Gims"}); - storage.replace(UserAndVisit{2, 1, "Glad you came"}); - storage.replace(UserAndVisit{3, 1, "Shine on"}); + storage.insert(UserAndVisit{2, 1, "Glad you came"}); + storage.insert(UserAndVisit{3, 1, "Shine on"}); { // by val auto statement = storage.prepare(update_all(set(assign(&User::name, conc(&User::name, "_"))))); diff --git a/tests/private_getters_tests.cpp b/tests/private_getters_tests.cpp index 94e591bc6..e91d69e2a 100644 --- a/tests/private_getters_tests.cpp +++ b/tests/private_getters_tests.cpp @@ -8,20 +8,20 @@ TEST_CASE("Issue 343") { public: A() = default; - A(int id_, std::string name_) : name(std::move(name_)), id(id_) {} + A(int64 id_, std::string name_) : name(std::move(name_)), id(id_) {} - int getId() const { + int64 getId() const { return this->id; } - void setId(int id_) { + void setId(int64 id_) { this->id = id_; } std::string name; private: - int id = 0; + int64 id = 0; }; auto storage = make_storage( diff --git a/tests/row_extractor.cpp b/tests/row_extractor.cpp index 6e53001fe..64f29d03e 100644 --- a/tests/row_extractor.cpp +++ b/tests/row_extractor.cpp @@ -1,6 +1,7 @@ #include #include #include +#include // std::remove #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED using namespace sqlite_orm; @@ -12,7 +13,7 @@ enum class Gender { }; struct SuperHero { - int id = 0; + int64 id = 0; std::string name; Gender gender = Gender::Invalid; }; @@ -113,10 +114,10 @@ struct CustomConcatFunction { // Also it is representative for all stock row extractor specializations in regard to testing // storage's machinery during select and function calls, and whether they return proper values or build objects with proper values. TEST_CASE("Custom row extractors") { - remove("cre.sqlite"); + std::remove("cre.sqlite"); struct fguard { ~fguard() { - remove("cre.sqlite"); + std::remove("cre.sqlite"); } } g; auto storage = make_storage("cre.sqlite", diff --git a/tests/schema/explicit_columns.cpp b/tests/schema/explicit_columns.cpp index ab37c364b..bcb9e94a9 100644 --- a/tests/schema/explicit_columns.cpp +++ b/tests/schema/explicit_columns.cpp @@ -6,7 +6,7 @@ using namespace sqlite_orm; TEST_CASE("Explicit columns") { struct Object { - int id; + int64 id; }; struct User : Object { diff --git a/tests/schema/table_tests.cpp b/tests/schema/table_tests.cpp index 053f25383..4304f9449 100644 --- a/tests/schema/table_tests.cpp +++ b/tests/schema/table_tests.cpp @@ -6,7 +6,7 @@ using namespace sqlite_orm; TEST_CASE("table::find_column_name") { SECTION("fields") { struct Contact { - int id = 0; + int64 id /*= 0*/; std::string firstName; std::string lastName; int countryCode = 0; @@ -36,7 +36,7 @@ TEST_CASE("table::find_column_name") { SECTION("getters and setters") { struct Contact { private: - int _id = 0; + int64 _id = 0; std::string _firstName; std::string _lastName; int _countryCode = 0; @@ -44,11 +44,11 @@ TEST_CASE("table::find_column_name") { int _visitsCount = 0; public: - int id() const { + int64 id() const { return this->_id; } - void setId(int value) { + void setId(int64 value) { this->_id = value; } @@ -134,7 +134,7 @@ TEST_CASE("Composite key column names") { make_column("name", &User::name), make_column("info", &User::info), primary_key(&User::id, &User::name)); - auto compositeKeyColumnsNames = table.composite_key_columns_names(); + auto compositeKeyColumnsNames = table.table_key_columns_names(); std::vector expected = {"id", "name"}; REQUIRE(std::equal(compositeKeyColumnsNames.begin(), compositeKeyColumnsNames.end(), expected.begin())); } @@ -144,7 +144,7 @@ TEST_CASE("Composite key column names") { make_column("name", &User::name), make_column("info", &User::info), primary_key(&User::name, &User::id)); - auto compositeKeyColumnsNames = table.composite_key_columns_names(); + auto compositeKeyColumnsNames = table.table_key_columns_names(); std::vector expected = {"name", "id"}; REQUIRE(std::equal(compositeKeyColumnsNames.begin(), compositeKeyColumnsNames.end(), expected.begin())); } @@ -154,7 +154,7 @@ TEST_CASE("Composite key column names") { make_column("name", &User::name), make_column("info", &User::info), primary_key(&User::name, &User::id, &User::info)); - auto compositeKeyColumnsNames = table.composite_key_columns_names(); + auto compositeKeyColumnsNames = table.table_key_columns_names(); std::vector expected = {"name", "id", "info"}; REQUIRE(std::equal(compositeKeyColumnsNames.begin(), compositeKeyColumnsNames.end(), expected.begin())); } @@ -163,7 +163,7 @@ TEST_CASE("Composite key column names") { make_column("id", &User::id), make_column("name", &User::name), make_column("info", &User::info)); - auto compositeKeyColumnsNames = table.composite_key_columns_names(); + auto compositeKeyColumnsNames = table.table_key_columns_names(); REQUIRE(compositeKeyColumnsNames.empty()); } } diff --git a/tests/schema/virtual_table.cpp b/tests/schema/virtual_table.cpp index dfa1d3af7..a3336bd50 100644 --- a/tests/schema/virtual_table.cpp +++ b/tests/schema/virtual_table.cpp @@ -13,9 +13,9 @@ namespace { }; } -#if SQLITE_VERSION_NUMBER >= 3009000 using namespace sqlite_orm; +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) TEST_CASE("fts5 virtual table schema") { using Catch::Matchers::UnorderedEquals; struct Post { @@ -131,13 +131,13 @@ TEST_CASE("fts5 virtual table schema") { TEST_CASE("issue1410") { struct NormalTable { - int id; + int64 id; std::string text; int otherValue; }; struct SearchTable { - int normal_table_id; + int64 normal_table_id; std::string text; }; diff --git a/tests/select_constraints_tests.cpp b/tests/select_constraints_tests.cpp index 8871e8e10..158b6c4bd 100644 --- a/tests/select_constraints_tests.cpp +++ b/tests/select_constraints_tests.cpp @@ -7,7 +7,7 @@ using namespace sqlite_orm; namespace { struct Employee { - int id = 0; + int64 id = 0; std::string name; int age = 0; std::string address; // optional @@ -57,7 +57,7 @@ TEST_CASE("select constraints") { SECTION("asterisk") { auto allEmployeesTuples = storage.select(asterisk()); - std::vector> expected; + std::vector> expected; expected.push_back(std::make_tuple(paul.id, "Paul", 32, "California", 20000.0)); expected.push_back(std::make_tuple(allen.id, "Allen", 25, "Texas", 15000.0)); @@ -262,14 +262,14 @@ TEST_CASE("Exists") { namespace { struct User2 { - int id = 0; + int64 id = 0; std::string firstName; std::string lastName; std::string country; }; struct Track2 { - int id = 0; + int64 id = 0; std::string name; long milliseconds = 0; }; @@ -306,7 +306,7 @@ TEST_CASE("Case") { columns(case_(&User2::country).when("USA", then("Dosmetic")).else_("Foreign").end()), multi_order_by(order_by(&User2::lastName), order_by(&User2::firstName))); auto verifyRows = [&storage](auto& rows) { - REQUIRE(rows.size() == storage.count()); + REQUIRE(rows.size() == static_cast(storage.count())); REQUIRE(std::get<0>(rows[0]) == "Foreign"); REQUIRE(std::get<0>(rows[1]) == "Foreign"); REQUIRE(std::get<0>(rows[2]) == "Dosmetic"); diff --git a/tests/statement_serializer_tests/ast/match.cpp b/tests/statement_serializer_tests/ast/match.cpp index 7e288969e..cd9ed3bbe 100644 --- a/tests/statement_serializer_tests/ast/match.cpp +++ b/tests/statement_serializer_tests/ast/match.cpp @@ -1,7 +1,7 @@ #include #include -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) using namespace sqlite_orm; TEST_CASE("statement_serializer match") { diff --git a/tests/statement_serializer_tests/column_constraints/unindexed.cpp b/tests/statement_serializer_tests/column_constraints/unindexed.cpp index 899ca355a..2fb871152 100644 --- a/tests/statement_serializer_tests/column_constraints/unindexed.cpp +++ b/tests/statement_serializer_tests/column_constraints/unindexed.cpp @@ -1,7 +1,7 @@ #include #include -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) using namespace sqlite_orm; TEST_CASE("statement_serializer unindexed") { diff --git a/tests/statement_serializer_tests/schema/column.cpp b/tests/statement_serializer_tests/schema/column.cpp index 7c60b9745..b303616c4 100644 --- a/tests/statement_serializer_tests/schema/column.cpp +++ b/tests/statement_serializer_tests/schema/column.cpp @@ -98,11 +98,13 @@ TEST_CASE("statement_serializer column") { value = serialize(column, context); expected = R"("name")"; } +#if SQLITE_VERSION_NUMBER >= 3024000 SECTION("auxiliary") { auto column = make_column("name", &User::name, auxiliary()); value = serialize(column, context); expected = R"(+"name")"; } +#endif } REQUIRE(value == expected); } diff --git a/tests/statement_serializer_tests/schema/using_fts5.cpp b/tests/statement_serializer_tests/schema/using_fts5.cpp index 6d6a72ce9..bf1ee52f2 100644 --- a/tests/statement_serializer_tests/schema/using_fts5.cpp +++ b/tests/statement_serializer_tests/schema/using_fts5.cpp @@ -1,7 +1,7 @@ #include #include -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) using namespace sqlite_orm; TEST_CASE("statement_serializer fts5") { diff --git a/tests/statement_serializer_tests/statements/insert_replace.cpp b/tests/statement_serializer_tests/statements/insert_replace.cpp index 5ab7735f6..c88b22756 100644 --- a/tests/statement_serializer_tests/statements/insert_replace.cpp +++ b/tests/statement_serializer_tests/statements/insert_replace.cpp @@ -18,15 +18,79 @@ TEST_CASE("statement_serializer insert/replace") { int id = 0; std::string name; }; - auto table = make_table("users", make_column("id", &User::id), make_column("name", &User::name)); + struct User2 { + int id = 0; + std::string name; + }; + struct User3 { + int id = 0; + std::string name; + }; + struct UserData1 { + int userId = 0; + int discriminatingId = 0; + }; + struct UserData2 { + int userId = 0; + int discriminatingId = 0; + }; + struct UserData3 { + int userId = 0; + }; + struct UserData4 { + int userId = 0; + }; + struct UserData5 { + int userId = 0; + int discriminatingId = 0; + }; + struct UserData6 { + int userId = 0; + int discriminatingId = 0; + }; + + // with rowid, no pk + auto table1 = make_table("users", make_column("id", &User::id), make_column("name", &User::name)); auto table2 = make_table("users_backup", make_column("id", &UserBackup::id), make_column("name", &UserBackup::name)); - using db_objects_t = internal::db_objects_tuple; - auto dbObjects = db_objects_t{table, table2}; + // with rowid, column pk + auto table3 = make_table("users2", make_column("id", &User2::id, primary_key()), make_column("name", &User2::name)); + // with rowid, single table pk + auto table4 = + make_table("users3", make_column("id", &User3::id), make_column("name", &User3::name), primary_key(&User3::id)); + // with rowid, composite table pk + auto table5 = make_table("user_data1", + make_column("user_id", &UserData1::userId), + make_column("discriminating_id", &UserData1::discriminatingId), + primary_key(&UserData1::userId, &UserData1::discriminatingId)); + // with rowid, composite table pk involving a default value + auto table6 = make_table("user_data2", + make_column("user_id", &UserData2::userId), + make_column("discriminating_id", &UserData2::discriminatingId, default_value(1)), + primary_key(&UserData2::userId, &UserData2::discriminatingId)); + // without rowid, column pk + auto table7 = make_table("user_data3", make_column("user_id", &UserData3::userId, primary_key())).without_rowid(); + // without rowid, single table pk + auto table8 = make_table("user_data4", make_column("user_id", &UserData4::userId), primary_key(&UserData4::userId)) + .without_rowid(); + // without rowid, composite table pk + auto table9 = make_table("user_data5", + make_column("user_id", &UserData5::userId), + make_column("discriminating_id", &UserData5::discriminatingId), + primary_key(&UserData5::userId, &UserData5::discriminatingId)) + .without_rowid(); + // without rowid, composite table pk involving a default value + auto table10 = make_table("user_data6", + make_column("user_id", &UserData6::userId), + make_column("discriminating_id", &UserData6::discriminatingId, default_value(1)), + primary_key(&UserData6::userId, &UserData6::discriminatingId)); + const std::tuple dbObjects = {table1, table2, table3, table4, table5, table6, table7, table8, table9, table10}; + using db_objects_t = decltype(dbObjects); using context_t = internal::serializer_context; context_t context{dbObjects}; std::string value; decltype(value) expected; + SECTION("replace") { SECTION("object") { User user{5, "Gambit"}; @@ -122,22 +186,30 @@ TEST_CASE("statement_serializer insert/replace") { value = serialize(expression, context); expected = R"(REPLACE INTO "users" ("id", "name") VALUES (?, ?))"; } +#ifdef _MSC_VER /* `&std::reference_wrapper::get` is only invocable with Microsoft STL and libstdc++ 15 */ SECTION("projected") { auto expression = replace_range(userRefs.begin(), userRefs.end(), &std::reference_wrapper::get); -#ifdef _MSC_VER /* `&std::reference_wrapper::get` is only invocable with Microsoft STL */ // deduced object type assert_same(replace_range(userRefs.begin(), userRefs.end(), &std::reference_wrapper::get), expression); -#endif value = serialize(expression, context); expected = R"(REPLACE INTO "users" ("id", "name") VALUES (?, ?))"; } +#endif } } } SECTION("insert") { User user{5, "Gambit"}; + User2 user2{5, "Gambit"}; + User3 user3{5, "Gambit"}; + UserData1 userData1{5, 5}; + UserData2 userData2{5, 5}; + UserData3 userData3{5}; + UserData4 userData4{5}; + UserData5 userData5{5, 5}; + UserData6 userData6{5, 5}; SECTION("crud") { auto statement = insert(user); SECTION("question marks") { @@ -150,6 +222,54 @@ TEST_CASE("statement_serializer insert/replace") { } value = serialize(statement, context); } + SECTION("crud with rowid, column pk") { + context.replace_bindable_with_question = false; + auto statement = insert(user2); + expected = R"(INSERT INTO "users2" ("name") VALUES ('Gambit'))"; + value = serialize(statement, context); + } + SECTION("crud with rowid, single table pk") { + context.replace_bindable_with_question = false; + auto statement = insert(user3); + expected = R"(INSERT INTO "users3" ("name") VALUES ('Gambit'))"; + value = serialize(statement, context); + } + SECTION("crud with rowid, composite table pk") { + context.replace_bindable_with_question = false; + auto statement = insert(userData1); + expected = R"(INSERT INTO "user_data1" ("user_id", "discriminating_id") VALUES (5, 5))"; + value = serialize(statement, context); + } + SECTION("crud with rowid, composite table pk involving a default value") { + context.replace_bindable_with_question = false; + auto statement = insert(userData2); + expected = R"(INSERT INTO "user_data2" ("user_id") VALUES (5))"; + value = serialize(statement, context); + } + SECTION("crud without rowid, column pk") { + context.replace_bindable_with_question = false; + auto statement = insert(userData3); + expected = R"(INSERT INTO "user_data3" ("user_id") VALUES (5))"; + value = serialize(statement, context); + } + SECTION("crud without rowid, single table pk") { + context.replace_bindable_with_question = false; + auto statement = insert(userData4); + expected = R"(INSERT INTO "user_data4" ("user_id") VALUES (5))"; + value = serialize(statement, context); + } + SECTION("crud without rowid, composite table pk") { + context.replace_bindable_with_question = false; + auto statement = insert(userData5); + expected = R"(INSERT INTO "user_data5" ("user_id", "discriminating_id") VALUES (5, 5))"; + value = serialize(statement, context); + } + SECTION("crud without rowid, composite table pk involving a default value") { + context.replace_bindable_with_question = false; + auto statement = insert(userData6); + expected = R"(INSERT INTO "user_data6" ("user_id") VALUES (5))"; + value = serialize(statement, context); + } SECTION("explicit") { SECTION("one column") { auto statement = insert(user, columns(&User::id)); @@ -385,6 +505,14 @@ TEST_CASE("statement_serializer insert/replace") { context.replace_bindable_with_question = false; std::vector users(1); + std::vector users2(1); + std::vector users3(1); + std::vector userData1(1); + std::vector userData2(1); + std::vector userData3(1); + std::vector userData4(1); + std::vector userData5(1); + std::vector userData6(1); SECTION("objects") { auto expression = insert_range(users.begin(), users.end()); // deduced object type @@ -415,17 +543,57 @@ TEST_CASE("statement_serializer insert/replace") { value = serialize(expression, context); expected = R"(INSERT INTO "users" ("id", "name") VALUES (?, ?))"; } +#ifdef _MSC_VER /* `&std::reference_wrapper::get` is only invocable with Microsoft STL and libstdc++ 15 */ SECTION("projected") { auto expression = insert_range(userRefs.begin(), userRefs.end(), &std::reference_wrapper::get); -#ifdef _MSC_VER /* `&std::reference_wrapper::get` is only invocable with Microsoft STL */ // deduced object type assert_same(insert_range(userRefs.begin(), userRefs.end(), &std::reference_wrapper::get), expression); -#endif value = serialize(expression, context); expected = R"(INSERT INTO "users" ("id", "name") VALUES (?, ?))"; } +#endif + } + SECTION("wit rowid, column pk") { + auto expression = insert_range(users2.begin(), users2.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "users2" ("name") VALUES (?))"; + } + SECTION("with rowid, single table pk") { + auto expression = insert_range(users3.begin(), users3.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "users3" ("name") VALUES (?))"; + } + SECTION("with rowid, composite table pk") { + auto expression = insert_range(userData1.begin(), userData1.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "user_data1" ("user_id", "discriminating_id") VALUES (?, ?))"; + } + SECTION("with rowid, composite table pk involving a default value") { + auto expression = insert_range(userData2.begin(), userData2.end()); + expected = R"(INSERT INTO "user_data2" ("user_id") VALUES (?))"; + value = serialize(expression, context); + } + SECTION("without rowid, column pk") { + auto expression = insert_range(userData3.begin(), userData3.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "user_data3" ("user_id") VALUES (?))"; + } + SECTION("without rowid, single table pk") { + auto expression = insert_range(userData4.begin(), userData4.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "user_data4" ("user_id") VALUES (?))"; + } + SECTION("without rowid, composite table pk") { + auto expression = insert_range(userData5.begin(), userData5.end()); + expected = R"(INSERT INTO "user_data5" ("user_id", "discriminating_id") VALUES (?, ?))"; + value = serialize(expression, context); + } + SECTION("without rowid, composite table pk involving a default value") { + auto expression = insert_range(userData6.begin(), userData6.end()); + expected = R"(INSERT INTO "user_data6" ("user_id") VALUES (?))"; + value = serialize(expression, context); } } } diff --git a/tests/statement_serializer_tests/statements/select.cpp b/tests/statement_serializer_tests/statements/select.cpp index fd138cb17..f55a8a538 100644 --- a/tests/statement_serializer_tests/statements/select.cpp +++ b/tests/statement_serializer_tests/statements/select.cpp @@ -202,12 +202,12 @@ TEST_CASE("statement_serializer select_t") { #if SQLITE_VERSION_NUMBER >= 3006019 SECTION("issue #945") { struct Employee { - int m_empno; - int m_deptno; + int64 m_empno; + int64 m_deptno; }; struct Department { - int m_deptno; + int64 m_deptno; std::string m_deptname; }; diff --git a/tests/statement_serializer_tests/table_constraints/foreign_key.cpp b/tests/statement_serializer_tests/table_constraints/foreign_key.cpp index 7db1273c3..4d1c8e770 100644 --- a/tests/statement_serializer_tests/table_constraints/foreign_key.cpp +++ b/tests/statement_serializer_tests/table_constraints/foreign_key.cpp @@ -13,12 +13,12 @@ using namespace sqlite_orm; TEST_CASE("statement_serializer foreign key") { SECTION("one to one") { struct User { - int id = 0; + int64 id = 0; std::string name; }; struct Visit { - int id = 0; + int64 id = 0; decltype(User::id) userId; long time = 0; }; diff --git a/tests/statement_serializer_tests/table_constraints/prefix.cpp b/tests/statement_serializer_tests/table_constraints/prefix.cpp index fcb6d1c96..72c2ccd9a 100644 --- a/tests/statement_serializer_tests/table_constraints/prefix.cpp +++ b/tests/statement_serializer_tests/table_constraints/prefix.cpp @@ -1,7 +1,7 @@ #include #include -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) using namespace sqlite_orm; TEST_CASE("statement_serializer prefix") { diff --git a/tests/static_tests/column_pointer.cpp b/tests/static_tests/column_pointer.cpp index ae9452225..52008bc68 100644 --- a/tests/static_tests/column_pointer.cpp +++ b/tests/static_tests/column_pointer.cpp @@ -74,6 +74,12 @@ concept refers_to_table_callable = orm_refers_to_table && requires { { count() } -> same_as>>; }; +template> +concept table_reference_callable_no_pk = orm_table_reference && requires { + { get_all
() } -> same_as>>; + { count
() } -> same_as>; +}; + template> concept table_reference_callable = orm_table_reference && requires { { get
(42) } -> same_as>; @@ -94,6 +100,13 @@ concept storage_refers_to_table_callable = orm_refers_to_table && requires(S& { storage.template iterate() } -> same_as>; }; +template> +concept storage_table_reference_callable_no_pk = orm_table_reference && requires(S& storage) { + { storage.template get_all
() } -> same_as>; + { storage.template count
() } -> same_as; + { storage.template iterate
() } -> same_as>; +}; + template> concept storage_table_reference_callable = orm_table_reference && requires(S& storage) { { storage.template get
(42) } -> same_as; @@ -177,7 +190,7 @@ TEST_CASE("column pointers") { STATIC_REQUIRE(table_reference_callable); STATIC_REQUIRE(refers_to_recordset_callable); STATIC_REQUIRE(refers_to_table_callable); - STATIC_REQUIRE(table_reference_callable); + STATIC_REQUIRE(table_reference_callable_no_pk); STATIC_REQUIRE(refers_to_recordset_callable); STATIC_REQUIRE(refers_to_table_callable); @@ -189,7 +202,7 @@ TEST_CASE("column pointers") { STATIC_REQUIRE(storage_refers_to_table_callable); STATIC_REQUIRE(storage_table_reference_callable); STATIC_REQUIRE(storage_refers_to_table_callable); - STATIC_REQUIRE(storage_table_reference_callable); + STATIC_REQUIRE(storage_table_reference_callable_no_pk); STATIC_REQUIRE(storage_refers_to_table_callable); #endif } diff --git a/tests/static_tests/foreign_key.cpp b/tests/static_tests/foreign_key.cpp index 50ca5f8fa..c51f28cff 100644 --- a/tests/static_tests/foreign_key.cpp +++ b/tests/static_tests/foreign_key.cpp @@ -36,7 +36,7 @@ TEST_CASE("foreign key static") { std::string includee_path; }; struct VarDecl { - int id; + int64 id; std::string name; std::string type; bool is_global; @@ -44,11 +44,11 @@ TEST_CASE("foreign key static") { }; struct Base { // Note: `long` was chosen as a different type than `int` for the primary key columns to ensure that the following static asserts work as expected. - long id; - long id2; + long long id; + long long id2; - int parentId; - int parentId2; + int64 parentId; + int64 parentId2; }; struct Derived : Base {}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES diff --git a/tests/static_tests/is_column_with_insertable_primary_key.cpp b/tests/static_tests/is_column_with_insertable_primary_key.cpp deleted file mode 100644 index 4be302d29..000000000 --- a/tests/static_tests/is_column_with_insertable_primary_key.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include - -using namespace sqlite_orm; -using namespace internal; - -template -using insertable_index_sequence = filter_tuple_sequence_t>; -template -using noninsertable_index_sequence = filter_tuple_sequence_t::template fn, - polyfill::type_identity_t, - col_index_sequence_with>; - -TEST_CASE("is_column_with_insertable_primary_key") { - struct User { - int id; - std::string username; - std::string password; - bool isActive; - }; - - auto insertable = std::make_tuple( /// - make_column("", &User::id, primary_key()), - make_column("", &User::username, primary_key(), default_value("Clint Eastwood")), - make_column("", &User::username, primary_key(), default_value(std::vector{})), - make_column("", &User::username, primary_key().autoincrement())); - - auto noninsertable = std::make_tuple( /// - make_column("", &User::username, primary_key()), - make_column("", &User::password, primary_key())); - - auto outside = std::make_tuple( /// - make_column("", &User::id), ///< not a primary key - std::make_shared() ///< not a column - ); - - STATIC_REQUIRE(insertable_index_sequence::size() == 4); - STATIC_REQUIRE(noninsertable_index_sequence::size() == 2); - STATIC_REQUIRE(noninsertable_index_sequence::size() == 0); -} \ No newline at end of file diff --git a/tests/static_tests/is_primary_key_insertable.cpp b/tests/static_tests/is_primary_key_insertable.cpp index e5f4428e5..42619fa52 100644 --- a/tests/static_tests/is_primary_key_insertable.cpp +++ b/tests/static_tests/is_primary_key_insertable.cpp @@ -1,32 +1,53 @@ #include #include -#include // std::decay +#include using namespace sqlite_orm; +using internal::count_tuple, internal::is_pkcol_implicitly_insertable; -TEST_CASE("is_primary_key_insertable") { +namespace { + struct CustomRowIdKeyType { + std::uint32_t low; + std::int32_t high; + }; +} +template<> +struct sqlite_orm::type_printer : public integer_printer {}; + +TEST_CASE("is_pkcol_implicitly_insertable") { struct User { - int id; + int intId; + int64 int64Id; + long long longlongId; // note: distinct from `int64` depending on platform + bool boolId; + CustomRowIdKeyType customId; std::string username; - std::string password; - bool isActive; }; - auto insertable = std::make_tuple( /// - make_column("", &User::id, primary_key()), + std::tuple columns( + /// + /// implicitly insertable + /// + // works but deprecated + make_column("", &User::intId, primary_key()), + // works but deprecated + make_column("", &User::intId, primary_key().autoincrement()), + make_column("", &User::int64Id, primary_key()), + make_column("", &User::int64Id, primary_key().autoincrement()), + make_column("", &User::longlongId, primary_key()), + make_column("", &User::longlongId, primary_key().autoincrement()), + // works but deprecated + make_column("", &User::boolId, primary_key()), + // works but deprecated + make_column("", &User::boolId, primary_key().autoincrement()), + make_column("", &User::customId, primary_key()), + make_column("", &User::customId, primary_key().autoincrement()), make_column("", &User::username, primary_key(), default_value("Clint Eastwood")), make_column("", &User::username, primary_key(), default_value(std::vector{})), - make_column("", &User::username, primary_key().autoincrement())); - - auto noninsertable = std::make_tuple( /// - make_column("", &User::username, primary_key()), - make_column("", &User::password, primary_key())); - - iterate_tuple(insertable, [](auto& v) { - STATIC_REQUIRE(internal::is_primary_key_insertable>::value); - }); + /// + /// not implicitly insertable + /// + make_column("", &User::username, primary_key())); - iterate_tuple(noninsertable, [](auto& v) { - STATIC_REQUIRE_FALSE(internal::is_primary_key_insertable>::value); - }); + STATIC_REQUIRE(count_tuple::value == 12); } \ No newline at end of file diff --git a/tests/static_tests/table_static_tests.cpp b/tests/static_tests/table_static_tests.cpp index d35d9318f..e9a885209 100644 --- a/tests/static_tests/table_static_tests.cpp +++ b/tests/static_tests/table_static_tests.cpp @@ -15,7 +15,7 @@ using dedicated_pk_columns_count_t = TEST_CASE("table static count_of()") { struct User { - int id = 0; + int64 id = 0; std::string name; }; { // 1 column no pk @@ -27,10 +27,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 1); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } - { // 1 column with 1 inline pk + { // 1 column with column pk auto table = make_table("users", make_column("id", &User::id, primary_key())); using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); @@ -39,10 +39,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 1); STATIC_REQUIRE(col_index_sequence_with::size() == 1); STATIC_REQUIRE(col_index_sequence_excluding::size() == 0); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } - { // 1 column with 1 inline pk autoincrement + { // 1 column with column pk autoincrement auto table = make_table("users", make_column("id", &User::id, primary_key().autoincrement())); using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); @@ -51,10 +51,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 1); STATIC_REQUIRE(col_index_sequence_with::size() == 1); STATIC_REQUIRE(col_index_sequence_excluding::size() == 0); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } - { // 1 column with 1 dedicated pk + { // 1 column with a single table pk auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id)); using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); @@ -64,9 +64,9 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 1); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } - { // 1 column with 1 dedicated pk autoincrement + { // 1 column with a single table pk autoincrement auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id).autoincrement()); using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); @@ -75,7 +75,7 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 1); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); } { // 2 columns no pk @@ -87,10 +87,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } - { // 2 columns with 1 inline id pk + { // 2 columns with a column pk (id) auto table = make_table("users", make_column("id", &User::id, primary_key()), make_column("id", &User::name)); using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); @@ -99,10 +99,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with::size() == 1); STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } - { // 2 columns with 1 inline name pk + { // 2 columns with a column pk (name) auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name, primary_key())); using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); @@ -111,10 +111,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with::size() == 1); STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } - { // 2 columns with 1 dedicated id pk + { // 2 columns with a single table pk (id) auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::id)); using elements_type = decltype(table.elements); @@ -124,10 +124,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); } - { // 2 columns with 1 dedicated name pk + { // 2 columns with a single table pk (name) auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::name)); using elements_type = decltype(table.elements); @@ -137,10 +137,10 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); } - { // 2 columns with 2 dedicated pks + { // 2 columns with a composite pk auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name), @@ -152,7 +152,7 @@ TEST_CASE("table static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with::size() == 0); STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 2); } } diff --git a/tests/static_tests/virtual_tables.cpp b/tests/static_tests/virtual_tables.cpp index 7ae721ecd..c6b7eb7c2 100644 --- a/tests/static_tests/virtual_tables.cpp +++ b/tests/static_tests/virtual_tables.cpp @@ -83,7 +83,7 @@ TEST_CASE("generate_series layout tests") { } #endif -#if SQLITE_VERSION_NUMBER >= 3009000 +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) TEST_CASE("fts5 layout tests") { using internal::fts5_module_tag; struct Post { diff --git a/tests/storage_non_crud_tests.cpp b/tests/storage_non_crud_tests.cpp index ac5ba13b9..fc1e8db7e 100644 --- a/tests/storage_non_crud_tests.cpp +++ b/tests/storage_non_crud_tests.cpp @@ -113,9 +113,18 @@ TEST_CASE("update set null") { } } +namespace { + struct CustomRowIdKeyType { + std::uint32_t low; + std::int32_t high; + }; +} +template<> +struct sqlite_orm::type_printer : public integer_printer {}; + TEST_CASE("InsertRange") { struct Object { - int id = 0; + int64 id = 0; std::string name; #ifndef SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED @@ -123,7 +132,23 @@ TEST_CASE("InsertRange") { Object(int id, std::string name) : id{id}, name{std::move(name)} {} #endif }; - + struct Object2 { + int64 id = 0; + std::string name; + }; + struct Object3 { + int objectId = 0; + int discriminatingId = 0; + }; + struct Object4 { + int objectId = 0; + int discriminatingId = 0; + }; + struct Object5 { + CustomRowIdKeyType id = {}; + std::string name; + }; +#if SQLITE_VERSION_NUMBER >= 3008002 struct ObjectWithoutRowid { int id = 0; std::string name; @@ -133,18 +158,79 @@ TEST_CASE("InsertRange") { ObjectWithoutRowid(int id, std::string name) : id{id}, name{std::move(name)} {} #endif }; + struct ObjectWithoutRowid2 { + int id = 0; + }; + struct ObjectWithoutRowid3 { + int objectId = 0; + int discriminatingId = 0; + }; + struct ObjectWithoutRowid4 { + int objectId = 0; + int discriminatingId = 0; + }; +#endif auto storage = make_storage( "test_insert_range.sqlite", + // with rowid, column pk make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name)), + // with rowid, single table pk + make_table("objects2", + make_column("id", &Object2::id), + make_column("name", &Object2::name), + primary_key(&Object2::id)), + // with rowid, composite table pk + make_table("objects3", + make_column("object_id", &Object3::objectId), + make_column("discriminating_id", &Object3::discriminatingId), + primary_key(&Object3::objectId, &Object3::discriminatingId)), + // with rowid, composite table pk involving a default value + make_table("objects4", + make_column("object_id", &Object4::objectId), + make_column("discriminating_id", &Object4::discriminatingId, default_value(1)), + primary_key(&Object4::objectId, &Object4::discriminatingId)), + // with rowid, column pk of custom type + make_table("objects5", make_column("id", &Object5::id, primary_key()), make_column("name", &Object5::name)) +#if SQLITE_VERSION_NUMBER >= 3008002 + , + // without rowid, column pk make_table("objects_without_rowid", make_column("id", &ObjectWithoutRowid::id, primary_key()), make_column("name", &ObjectWithoutRowid::name)) - .without_rowid()); + .without_rowid(), + // without rowid, single table pk + make_table("objects_without_rowid2", + make_column("id", &ObjectWithoutRowid2::id), + primary_key(&ObjectWithoutRowid2::id)) + .without_rowid(), + // with rowid, composite table pk + make_table("objects_without_rowid3", + make_column("object_id", &ObjectWithoutRowid3::objectId), + make_column("discriminating_id", &ObjectWithoutRowid3::discriminatingId), + primary_key(&ObjectWithoutRowid3::objectId, &ObjectWithoutRowid3::discriminatingId)) + .without_rowid(), + // with rowid, composite table pk involving a default value + make_table("objects_without_rowid4", + make_column("object_id", &ObjectWithoutRowid4::objectId), + make_column("discriminating_id", &ObjectWithoutRowid4::discriminatingId, default_value(1)), + primary_key(&ObjectWithoutRowid4::objectId, &ObjectWithoutRowid4::discriminatingId)) + .without_rowid() +#endif + ); storage.sync_schema(); storage.remove_all(); + storage.remove_all(); + storage.remove_all(); + storage.remove_all(); + storage.remove_all(); +#if SQLITE_VERSION_NUMBER >= 3008002 storage.remove_all(); + storage.remove_all(); + storage.remove_all(); + storage.remove_all(); +#endif SECTION("straight") { std::vector objects = {100, @@ -155,16 +241,52 @@ TEST_CASE("InsertRange") { storage.insert_range(objects.begin(), objects.end()); REQUIRE(storage.count() == 100); - // test empty container + // empty container std::vector emptyVector; - storage.insert_range(emptyVector.begin(), emptyVector.end()); - - // test insert_range without rowid - std::vector objectsWR = {ObjectWithoutRowid{10, "Life"}, ObjectWithoutRowid{20, "Death"}}; - REQUIRE(objectsWR.size() == 2); - storage.insert_range(objectsWR.begin(), objectsWR.end()); + REQUIRE_NOTHROW(storage.insert_range(emptyVector.begin(), emptyVector.end())); + + // with rowid, single table pk + std::vector objects2 = {{2}, {4}}; + storage.insert_range(objects2.begin(), objects2.end()); + REQUIRE_NOTHROW(storage.get(1)); + + // with rowid, composite table pk + std::vector objects3 = {{2, 2}, {4, 4}}; + storage.insert_range(objects3.begin(), objects3.end()); + REQUIRE_NOTHROW(storage.get(2, 2)); + + // with rowid, composite table pk involving a default value + std::vector objects4 = {{2, 2}, {4, 4}}; + storage.insert_range(objects4.begin(), objects4.end()); + REQUIRE_NOTHROW(storage.get(2, 1)); + + // with rowid, single table pk + std::vector objects5 = {{}, {}}; + storage.insert_range(objects5.begin(), objects5.end()); + REQUIRE(storage.count(where(c(&Object5::id) == 1)) == 1); + +#if SQLITE_VERSION_NUMBER >= 3008002 + // without rowid, column pk + std::vector objectsWR1 = {{10, "Life"}, {20, "Death"}}; + storage.insert_range(objectsWR1.begin(), objectsWR1.end()); REQUIRE(storage.get(10).name == "Life"); REQUIRE(storage.get(20).name == "Death"); + + // without rowid, single table pk + std::vector objectsWR2 = {{2}}; + storage.insert_range(objectsWR2.begin(), objectsWR2.end()); + REQUIRE_NOTHROW(storage.get(2)); + + // without rowid, composite table pk + std::vector objectsWR3 = {{2, 2}, {4, 4}}; + storage.insert_range(objectsWR3.begin(), objectsWR3.end()); + REQUIRE_NOTHROW(storage.get(2, 2)); + + // without rowid, composite table pk involving a default value + std::vector objectsWR4 = {{2, 2}, {4, 4}}; + storage.insert_range(objectsWR4.begin(), objectsWR4.end()); + REQUIRE_NOTHROW(storage.get(2, 1)); +#endif } SECTION("pointers") { std::vector> objects; @@ -177,8 +299,10 @@ TEST_CASE("InsertRange") { // test empty container std::vector> emptyVector; - storage.insert_range(emptyVector.begin(), emptyVector.end(), &std::unique_ptr::operator*); + REQUIRE_NOTHROW( + storage.insert_range(emptyVector.begin(), emptyVector.end(), &std::unique_ptr::operator*)); +#if SQLITE_VERSION_NUMBER >= 3008002 // test insert_range without rowid std::vector> objectsWR; objectsWR.push_back(std::make_unique(10, "Life")); @@ -188,6 +312,7 @@ TEST_CASE("InsertRange") { storage.insert_range(objectsWR.begin(), objectsWR.end(), &std::unique_ptr::operator*); REQUIRE(storage.get(10).name == "Life"); REQUIRE(storage.get(20).name == "Death"); +#endif } } @@ -280,7 +405,7 @@ TEST_CASE("Select") { sqlite3_close(db); struct Word { - int id; + int64 id; std::string currentWord; std::string beforeWord; std::string afterWord; diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index 94cbcd58e..50ff865ae 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -476,47 +476,136 @@ TEST_CASE("non-unique DBOs") { db.sync_schema(); } +namespace { + struct CustomRowIdKeyType { + std::uint32_t low; + std::int32_t high; + }; +} +template<> +struct sqlite_orm::type_printer : public integer_printer {}; + TEST_CASE("insert") { struct Object { - int id; + int64 id = 0; std::string name; }; - + struct Object2 { + int64 id = 0; + std::string name; + }; + struct Object3 { + int objectId = 0; + int discriminatingId = 0; + }; + struct Object4 { + int objectId = 0; + int discriminatingId = 0; + }; + struct Object5 { + CustomRowIdKeyType id = {}; + std::string name; + }; +#if SQLITE_VERSION_NUMBER >= 3008002 struct ObjectWithoutRowid { - int id; + int id = 0; std::string name; }; + struct ObjectWithoutRowid2 { + int id = 0; + }; + struct ObjectWithoutRowid3 { + int objectId = 0; + int discriminatingId = 0; + }; + struct ObjectWithoutRowid4 { + int objectId = 0; + int discriminatingId = 0; + }; +#endif auto storage = make_storage( "test_insert.sqlite", + // with rowid, column pk make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name)), + // with rowid, single table pk + make_table("objects2", + make_column("id", &Object2::id), + make_column("name", &Object2::name), + primary_key(&Object2::id)), + // with rowid, composite table pk + make_table("objects3", + make_column("object_id", &Object3::objectId), + make_column("discriminating_id", &Object3::discriminatingId), + primary_key(&Object3::objectId, &Object3::discriminatingId)), + // with rowid, composite table pk involving a default value + make_table("objects4", + make_column("object_id", &Object4::objectId), + make_column("discriminating_id", &Object4::discriminatingId, default_value(1)), + primary_key(&Object4::objectId, &Object4::discriminatingId)), + // with rowid, column pk of custom type + make_table("objects5", make_column("id", &Object5::id, primary_key()), make_column("name", &Object5::name)) +#if SQLITE_VERSION_NUMBER >= 3008002 + , + // without rowid, column pk make_table("objects_without_rowid", make_column("id", &ObjectWithoutRowid::id, primary_key()), make_column("name", &ObjectWithoutRowid::name)) - .without_rowid()); + .without_rowid(), + // without rowid, single table pk + make_table("objects_without_rowid2", + make_column("id", &ObjectWithoutRowid2::id), + primary_key(&ObjectWithoutRowid2::id)) + .without_rowid(), + // with rowid, composite table pk + make_table("objects_without_rowid3", + make_column("object_id", &ObjectWithoutRowid3::objectId), + make_column("discriminating_id", &ObjectWithoutRowid3::discriminatingId), + primary_key(&ObjectWithoutRowid3::objectId, &ObjectWithoutRowid3::discriminatingId)) + .without_rowid(), + // with rowid, composite table pk involving a default value + make_table("objects_without_rowid4", + make_column("object_id", &ObjectWithoutRowid4::objectId), + make_column("discriminating_id", &ObjectWithoutRowid4::discriminatingId, default_value(1)), + primary_key(&ObjectWithoutRowid4::objectId, &ObjectWithoutRowid4::discriminatingId)) + .without_rowid() +#endif + ); storage.sync_schema(); storage.remove_all(); + storage.remove_all(); + storage.remove_all(); + storage.remove_all(); + storage.remove_all(); +#if SQLITE_VERSION_NUMBER >= 3008002 storage.remove_all(); + storage.remove_all(); + storage.remove_all(); + storage.remove_all(); +#endif - for (auto i = 0; i < 100; ++i) { - storage.insert(Object{ - 0, - "Skillet", - }); - REQUIRE(storage.count() == i + 1); - } + storage.transaction([&storage]() { + for (auto i = 0; i < 100; ++i) { + REQUIRE(storage.insert(Object{ + 0, + "Skillet", + }) == i + 1); + } + return true; + }); + REQUIRE(storage.count() == 100); - auto initList = { - Object{ + const std::initializer_list initList = { + { 0, "Insane", }, - Object{ + { 0, "Super", }, - Object{ + { 0, "Sun", }, @@ -544,12 +633,50 @@ TEST_CASE("insert") { REQUIRE_NOTHROW( storage.insert_range(emptyVector.begin(), emptyVector.end(), &std::unique_ptr::operator*)); } - - // test insert without rowid - storage.insert(ObjectWithoutRowid{10, "Life"}); - REQUIRE(storage.get(10).name == "Life"); - storage.insert(ObjectWithoutRowid{20, "Death"}); - REQUIRE(storage.get(20).name == "Death"); + SECTION("with rowid, single table pk") { + REQUIRE(storage.insert(Object2{2}) == 1); + REQUIRE(storage.insert(Object2{4}) == 2); + REQUIRE_NOTHROW(storage.get(1)); + } + SECTION("with rowid, composite table pk") { + REQUIRE(storage.insert(Object3{2, 2}) == 1); + REQUIRE(storage.insert(Object3{4, 4}) == 2); + REQUIRE_NOTHROW(storage.get(2, 2)); + } + SECTION("with rowid, composite table pk involving a default value") { + REQUIRE(storage.insert(Object4{2, 2}) == 1); + REQUIRE(storage.insert(Object4{4, 4}) == 2); + REQUIRE_NOTHROW(storage.get(2, 1)); + REQUIRE_NOTHROW(storage.get(4, 1)); + } + SECTION("with rowid, column pk of custom type") { + REQUIRE(storage.insert(Object5{}) == 1); + REQUIRE(storage.insert(Object5{}) == 2); + REQUIRE(storage.count(where(c(&Object5::id) == 1)) == 1); + } +#if SQLITE_VERSION_NUMBER >= 3008002 + SECTION("without rowid, column pk") { + REQUIRE(storage.insert(ObjectWithoutRowid{10, "Life"}) == 0); + REQUIRE(storage.get(10).name == "Life"); + REQUIRE(storage.insert(ObjectWithoutRowid{20, "Death"}) == 0); + REQUIRE(storage.get(20).name == "Death"); + } + SECTION("without rowid, single table pk") { + REQUIRE(storage.insert(ObjectWithoutRowid2{2}) == 0); + REQUIRE_NOTHROW(storage.get(2)); + } + SECTION("without rowid, composite table pk") { + REQUIRE(storage.insert(ObjectWithoutRowid3{2, 2}) == 0); + REQUIRE(storage.insert(ObjectWithoutRowid3{4, 4}) == 0); + REQUIRE_NOTHROW(storage.get(2, 2)); + } + SECTION("without rowid, composite table pk involving a default value") { + REQUIRE(storage.insert(ObjectWithoutRowid4{2, 2}) == 0); + REQUIRE(storage.insert(ObjectWithoutRowid4{4, 4}) == 0); + REQUIRE_NOTHROW(storage.get(2, 1)); + REQUIRE_NOTHROW(storage.get(4, 1)); + } +#endif } TEST_CASE("Empty storage") { @@ -559,7 +686,7 @@ TEST_CASE("Empty storage") { TEST_CASE("Remove") { struct Object { - int id; + int64 id; std::string name; }; @@ -597,13 +724,13 @@ TEST_CASE("Remove") { make_column("name", &Object::name), primary_key(&Object::id, &Object::name))); storage.sync_schema(); - storage.replace(Object{1, "Skillet"}); + storage.insert(Object{1, "Skillet"}); REQUIRE(storage.count() == 1); storage.remove(1, "Skillet"); REQUIRE(storage.count() == 0); - storage.replace(Object{1, "Skillet"}); - storage.replace(Object{2, "Paul Cless"}); + storage.insert(Object{1, "Skillet"}); + storage.insert(Object{2, "Paul Cless"}); REQUIRE(storage.count() == 2); storage.remove(1, "Skillet"); REQUIRE(storage.count() == 1); @@ -657,7 +784,7 @@ TEST_CASE("insert with generated column") { TEST_CASE("last insert rowid") { struct Object { - int id; + int64 id; }; auto storage = make_storage("", make_table("objects", make_column("id", &Object::id, primary_key()))); @@ -665,12 +792,12 @@ TEST_CASE("last insert rowid") { storage.sync_schema(); SECTION("ordinary insert") { - int id = storage.insert({0}); + int64 id = storage.insert({0}); REQUIRE(id == storage.last_insert_rowid()); REQUIRE_NOTHROW(storage.get(id)); } SECTION("explicit insert") { - int id = storage.insert({2}, columns(&Object::id)); + int64 id = storage.insert({2}, columns(&Object::id)); REQUIRE(id == 2); REQUIRE(id == storage.last_insert_rowid()); } diff --git a/tests/sync_schema_tests.cpp b/tests/sync_schema_tests.cpp index b7e0ed0fa..8597c8a43 100644 --- a/tests/sync_schema_tests.cpp +++ b/tests/sync_schema_tests.cpp @@ -17,7 +17,7 @@ TEST_CASE("Sync schema") { // this is an old version of user.. struct UserBefore { - int id = 0; + int64 id = 0; std::string name; std::unique_ptr categoryId; std::unique_ptr surname; @@ -25,7 +25,7 @@ TEST_CASE("Sync schema") { // this is a new version of user struct UserAfter { - int id = 0; + int64 id = 0; std::string name; bool operator==(const UserBefore& before) const { @@ -138,7 +138,7 @@ TEST_CASE("issue521") { auto storagePath = "issue521.sqlite"; struct MockDatabasePoco { - int id = 0; + int64 id = 0; std::string name; std::uint32_t alpha{0}; float beta{0.0}; diff --git a/tests/table_name_collector.cpp b/tests/table_name_collector.cpp index 4b009ebe9..c229a0b12 100644 --- a/tests/table_name_collector.cpp +++ b/tests/table_name_collector.cpp @@ -121,6 +121,7 @@ TEST_CASE("table name collector") { REQUIRE(collector.table_names == expected); } #endif +#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5) #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED SECTION("highlight") { using user_hidden = fts5::hidden_fields_of; @@ -140,4 +141,5 @@ TEST_CASE("table name collector") { REQUIRE(collector.table_names == expected); } #endif +#endif } diff --git a/tests/tests.cpp b/tests/tests.cpp index ad2a00087..8940998c1 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -110,7 +110,7 @@ TEST_CASE("Custom collate") { #endif struct Item { - int id; + int64 id; std::string name; }; @@ -226,7 +226,7 @@ TEST_CASE("Custom collate") { TEST_CASE("Vacuum") { struct Item { - int id; + int64 id; std::string name; }; diff --git a/tests/tests3.cpp b/tests/tests3.cpp index 66ca9b22f..1d1aa8e56 100644 --- a/tests/tests3.cpp +++ b/tests/tests3.cpp @@ -5,7 +5,7 @@ using namespace sqlite_orm; TEST_CASE("Multi order by") { struct Singer { - int id; + int64 id; std::string name; std::string gender; }; @@ -50,7 +50,7 @@ TEST_CASE("Multi order by") { TEST_CASE("Issue 105") { struct Data { - int str; + int64 str; }; auto storage = make_storage("", make_table("data", make_column("str", &Data::str, primary_key()))); @@ -97,7 +97,7 @@ TEST_CASE("Issue 87") { TEST_CASE("Wide string") { struct Alphabet { - int id; + int64 id; std::wstring letters; }; @@ -250,7 +250,7 @@ TEST_CASE("Aggregate functions") { TEST_CASE("Open forever") { struct User { - int id; + int64 id; std::string name; }; @@ -349,7 +349,7 @@ TEST_CASE("Blob") { */ TEST_CASE("Escape chars") { struct Employee { - int id; + int64 id; std::string name; int age; std::string address; diff --git a/tests/tests4.cpp b/tests/tests4.cpp index 50e855b96..e70f742da 100644 --- a/tests/tests4.cpp +++ b/tests/tests4.cpp @@ -10,7 +10,7 @@ using namespace sqlite_orm; TEST_CASE("Unique ptr in update") { struct User { - int id = 0; + int64 id = 0; std::unique_ptr name; }; @@ -38,7 +38,7 @@ TEST_CASE("Unique ptr in update") { TEST_CASE("optional in update") { struct User { - int id = 0; + int64 id = 0; std::optional carYear; // will be empty if user takes the bus. }; @@ -137,59 +137,59 @@ TEST_CASE("join") { #if SQLITE_VERSION_NUMBER >= 3006019 TEST_CASE("two joins") { struct Statement { - int id_statement; + int64 id_statement; long date; }; struct Concepto { - int id_concepto; + int64 id_concepto; std::string name; // TFT-SINPE A: 15103-02**-****-8467 - int fkey_account; // { 15103-02**-****-8467, ...} + int64 fkey_account; // { 15103-02**-****-8467, ...} }; struct Account { - int id_account; + int64 id_account; std::string number; // 15103-02**-****-8467 - int fkey_bank; // { BAC San Jose, "Barrio Dent", { Costa Rica} } - int fkey_account_owner; // { Juan Dent Herrera, ... } + int64 fkey_bank; // { BAC San Jose, "Barrio Dent", { Costa Rica} } + int64 fkey_account_owner; // { Juan Dent Herrera, ... } std::string description; // AMEX Cashback Premium bool is_tarjeta; // true }; struct Pais { - int id_pais; + int64 id_pais; std::string name; }; struct Banco { - int id_bank; + int64 id_bank; std::string nombre; std::string ubicacion; int fkey_pais; }; struct AccountOwner { - int id_owner; + int64 id_owner; std::string name; }; struct Transaccion { - int id_transaccion; + int64 id_transaccion; double amount_colones; double amount_dolares; - int fkey_account_own; // Account - int fkey_account_other = 0; // Account optional + int64 fkey_account_own; // Account + int64 fkey_account_other = 0; // Account optional long line_date; std::string descripcion; - int fkey_category; - int fkey_concepto; - int fkey_statement; + int64 fkey_category; + int64 fkey_concepto; + int64 fkey_statement; int row; // fkey_statement + row is unique }; struct Categoria { - int id_categoria; + int64 id_categoria; std::string name; bool is_expense_or_income; }; diff --git a/tests/tests5.cpp b/tests/tests5.cpp index 4fed2b70f..a2d0b5846 100644 --- a/tests/tests5.cpp +++ b/tests/tests5.cpp @@ -208,7 +208,7 @@ TEST_CASE("Different getters and setters") { TEST_CASE("Dump") { struct User { - int id = 0; + int64 id = 0; std::optional carYear; // will be empty if user takes the bus. }; @@ -315,9 +315,9 @@ TEST_CASE("issue822") { make_column("value", &A::getValue, &A::setValue), primary_key(&A::getAddress, &A::getType, &A::getIndex))); storage.sync_schema(); - storage.replace(A(1, 1, 0, std::make_shared(55.5))); + storage.insert(A(1, 1, 0, std::make_shared(55.5))); auto records = storage.get_all(where(c(&A::getAddress) == 1 and c(&A::getType) == 1 and c(&A::getIndex) == 0)); - if (records.size() != 0) { + if (!records.empty()) { A a = records[0]; a.setValue(std::make_shared(10)); storage.update(a); diff --git a/tests/transaction_tests.cpp b/tests/transaction_tests.cpp index 5654a5c8e..206d35ca5 100644 --- a/tests/transaction_tests.cpp +++ b/tests/transaction_tests.cpp @@ -7,7 +7,7 @@ using namespace sqlite_orm; namespace { struct Object { - int id = 0; + int64 id = 0; std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED diff --git a/tests/unique_cases/issue1357.cpp b/tests/unique_cases/issue1357.cpp index 8934065b2..a905b3c62 100644 --- a/tests/unique_cases/issue1357.cpp +++ b/tests/unique_cases/issue1357.cpp @@ -8,18 +8,18 @@ using namespace sqlite_orm; TEST_CASE("issue1357") { struct Employee { - int m_empno; + int64 m_empno; std::string m_ename; std::string m_job; std::optional m_mgr; std::string m_hiredate; double m_salary; std::optional m_commission; - int m_depno; + int64 m_depno; }; struct Department { - int m_deptno; + int64 m_deptno; std::string m_deptname; std::string m_loc; }; diff --git a/tests/unique_cases/issue525.cpp b/tests/unique_cases/issue525.cpp index 1b71e02b7..8e2ac5e64 100644 --- a/tests/unique_cases/issue525.cpp +++ b/tests/unique_cases/issue525.cpp @@ -5,16 +5,16 @@ using namespace sqlite_orm; TEST_CASE("issue525") { struct User { - int id; + int64 id; std::string firstName; std::string lastName; int birthDate; std::unique_ptr imageUrl; - int typeId; + int64 typeId; }; struct UserType { - int id; + int64 id; std::string name; }; diff --git a/tests/unique_cases/issue663.cpp b/tests/unique_cases/issue663.cpp index 19160a17d..11edb1917 100644 --- a/tests/unique_cases/issue663.cpp +++ b/tests/unique_cases/issue663.cpp @@ -65,7 +65,7 @@ namespace { namespace { struct User1 { - int id = 0; + int64 id = 0; std::string name; int age = 0; std::string email; @@ -80,7 +80,7 @@ namespace { }; } -TEST_CASE("Issue 663 - pk inside") { +TEST_CASE("Issue 663 - column pk") { auto storage = make_storage("", make_table("users", @@ -99,7 +99,7 @@ TEST_CASE("Issue 663 - pk inside") { } } -TEST_CASE("Issue 663 - pk outside") { +TEST_CASE("Issue 663 - single table pk") { auto storage = make_storage("", make_table("users", @@ -125,7 +125,7 @@ namespace { std::string name; }; } -TEST_CASE("Issue 663 - pk outside, with default") { +TEST_CASE("Issue 663 - composite table pk with default") { auto storage = make_storage("", make_table("users", @@ -148,7 +148,7 @@ namespace { std::string id; }; } -TEST_CASE("Issue 663 - pk inside, with default") { +TEST_CASE("Issue 663 - column pk with default") { auto storage = make_storage("", make_table("users", make_column("id", &User3::id, primary_key(), default_value("200")))); storage.sync_schema(); diff --git a/tests/unique_cases/issue937.cpp b/tests/unique_cases/issue937.cpp index 19f7370a0..c96f02381 100644 --- a/tests/unique_cases/issue937.cpp +++ b/tests/unique_cases/issue937.cpp @@ -8,25 +8,25 @@ using namespace sqlite_orm; #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED TEST_CASE("issue937") { struct Employee { - int m_empno; + int64 m_empno; std::string m_ename; std::string m_job; std::optional m_mgr; std::string m_hiredate; double m_salary; std::optional m_commission; - int m_depno; + int64 m_depno; }; struct Department { - int m_deptno; + int64 m_deptno; std::string m_deptname; std::string m_loc; }; struct EmpBonus { - int m_id; - int m_empno; + int64 m_id; + int64 m_empno; std::string m_received; // date int m_type; }; @@ -66,7 +66,7 @@ TEST_CASE("issue937") { auto statement = storage.prepare(select(union_all( select(columns(as(&Department::m_deptname), as_optional(&Department::m_deptno))), - select(union_all(select(columns(quote("--------------------"), std::optional())), + select(union_all(select(columns(quote("--------------------"), std::optional())), select(columns(as(&Employee::m_ename), as_optional(&Employee::m_depno)))))))); #if SQLITE_VERSION_NUMBER >= 3014000 auto sql = statement.expanded_sql(); diff --git a/tests/unique_cases/prepare_get_all_with_case.cpp b/tests/unique_cases/prepare_get_all_with_case.cpp index e0657fcfb..f8a915448 100644 --- a/tests/unique_cases/prepare_get_all_with_case.cpp +++ b/tests/unique_cases/prepare_get_all_with_case.cpp @@ -5,7 +5,7 @@ using namespace sqlite_orm; TEST_CASE("Prepare with case") { struct UserProfile { - int id = 0; + int64 id = 0; std::string firstName; };