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/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/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/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/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/statement_serializer.h b/dev/statement_serializer.h index f93491242..f369433eb 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -1599,7 +1599,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!is_without_rowid::value && exists_in_composite_primary_key(table, column)) { return; } @@ -1621,7 +1621,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>{}, [&table](auto& column) { - return exists_in_composite_primary_key(table, column); + return !is_without_rowid::value && exists_in_composite_primary_key(table, column); }, context, get_ref(statement.object)) @@ -1771,7 +1771,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!is_without_rowid::value && exists_in_composite_primary_key(table, column)) { return; } diff --git a/dev/storage.h b/dev/storage.h index ee207b24e..4d6d591a7 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -936,9 +936,12 @@ namespace sqlite_orm { } /** - * 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. + * Insert routine. + * + * - For objects mapped to a table with rowid: 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 without rowid: Inserts a record with all fields of a mapped object. + * @return The ID of the newly created record. */ template int insert(const O& o) { @@ -1488,7 +1491,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 +1508,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 @@ -1530,16 +1535,18 @@ 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; + bindValue = field_value_binder{stmt}](const object_type& object) mutable { + using table_type = polyfill::remove_cvref_t; + using is_without_rowid = typename table_type::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)); + if (!is_without_rowid::value && exists_in_composite_primary_key(table, column)) { + return; } + bindValue(polyfill::invoke(column.member_pointer, object)); })); }; @@ -1553,8 +1560,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 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/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/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index b2551b63e..906f6cc92 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> @@ -9773,7 +9773,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 +9790,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 +10249,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 +10296,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 +12368,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 @@ -13224,7 +13224,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 +13257,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 +14471,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 +15869,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 +15909,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)}; } @@ -20492,13 +20504,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 @@ -22681,7 +22693,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!is_without_rowid::value && exists_in_composite_primary_key(table, column)) { return; } @@ -22703,7 +22715,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>{}, [&table](auto& column) { - return exists_in_composite_primary_key(table, column); + return !is_without_rowid::value && exists_in_composite_primary_key(table, column); }, context, get_ref(statement.object)) @@ -22853,7 +22865,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if (exists_in_composite_primary_key(table, column)) { + if (!is_without_rowid::value && exists_in_composite_primary_key(table, column)) { return; } @@ -23749,7 +23761,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 +24067,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))...}; @@ -25083,9 +25095,12 @@ namespace sqlite_orm { } /** - * 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. + * Insert routine. + * + * - For objects mapped to a table with rowid: 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 without rowid: Inserts a record with all fields of a mapped object. + * @return The ID of the newly created record. */ template int insert(const O& o) { @@ -25635,7 +25650,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 +25667,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 @@ -25677,16 +25694,18 @@ 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; + bindValue = field_value_binder{stmt}](const object_type& object) mutable { + using table_type = polyfill::remove_cvref_t; + using is_without_rowid = typename table_type::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)); + if (!is_without_rowid::value && exists_in_composite_primary_key(table, column)) { + return; } + bindValue(polyfill::invoke(column.member_pointer, object)); })); }; @@ -25700,8 +25719,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 diff --git a/tests/prepared_statement_tests/insert_range.cpp b/tests/prepared_statement_tests/insert_range.cpp index 42a1b9ea8..fa2a385e0 100644 --- a/tests/prepared_statement_tests/insert_range.cpp +++ b/tests/prepared_statement_tests/insert_range.cpp @@ -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/replace_range.cpp b/tests/prepared_statement_tests/replace_range.cpp index 0505bb813..b9d7e79f3 100644 --- a/tests/prepared_statement_tests/replace_range.cpp +++ b/tests/prepared_statement_tests/replace_range.cpp @@ -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/statement_serializer_tests/statements/insert_replace.cpp b/tests/statement_serializer_tests/statements/insert_replace.cpp index 5ab7735f6..379846b32 100644 --- a/tests/statement_serializer_tests/statements/insert_replace.cpp +++ b/tests/statement_serializer_tests/statements/insert_replace.cpp @@ -18,11 +18,31 @@ TEST_CASE("statement_serializer insert/replace") { int id = 0; std::string name; }; + struct User2 { + int id = 0; + std::string name; + }; + struct User3 { + int id = 0; + std::string name; + }; + struct UserData1 { + int userId = 0; + }; + struct UserData2 { + int userId = 0; + }; auto table = 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}; + auto table3 = make_table("users2", make_column("id", &User2::id, primary_key()), make_column("name", &User2::name)); + auto table4 = + make_table("users3", make_column("id", &User3::id), make_column("name", &User3::name), primary_key(&User3::id)); + auto table5 = make_table("user_data1", make_column("user_id", &UserData1::userId, primary_key())).without_rowid(); + auto table6 = make_table("user_data2", make_column("user_id", &UserData2::userId), primary_key(&UserData2::userId)) + .without_rowid(); + std::tuple dbObjects = {table, table2, table3, table4, table5, table6}; + using db_objects_t = decltype(dbObjects); using context_t = internal::serializer_context; context_t context{dbObjects}; std::string value; @@ -138,6 +158,10 @@ TEST_CASE("statement_serializer insert/replace") { } SECTION("insert") { User user{5, "Gambit"}; + User2 user2{5, "Gambit"}; + User3 user3{5, "Gambit"}; + UserData1 userData1{5}; + UserData2 userData2{5}; SECTION("crud") { auto statement = insert(user); SECTION("question marks") { @@ -150,6 +174,30 @@ 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, 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 without rowid, column pk") { + context.replace_bindable_with_question = false; + auto statement = insert(userData1); + expected = R"(INSERT INTO "user_data1" ("user_id") VALUES (5))"; + value = serialize(statement, context); + } + SECTION("crud without rowid, table pk") { + 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("explicit") { SECTION("one column") { auto statement = insert(user, columns(&User::id)); @@ -385,6 +433,10 @@ 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); SECTION("objects") { auto expression = insert_range(users.begin(), users.end()); // deduced object type @@ -427,6 +479,26 @@ TEST_CASE("statement_serializer insert/replace") { expected = R"(INSERT INTO "users" ("id", "name") VALUES (?, ?))"; } } + 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, table pk") { + auto expression = insert_range(users3.begin(), users3.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "users3" ("name") VALUES (?))"; + } + SECTION("without rowid, column pk") { + auto expression = insert_range(userData1.begin(), userData1.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "user_data1" ("user_id") VALUES (?))"; + } + SECTION("without rowid, table pk") { + auto expression = insert_range(userData2.begin(), userData2.end()); + value = serialize(expression, context); + expected = R"(INSERT INTO "user_data2" ("user_id") VALUES (?))"; + } } } REQUIRE(value == expected); diff --git a/tests/storage_non_crud_tests.cpp b/tests/storage_non_crud_tests.cpp index ac5ba13b9..0591244d8 100644 --- a/tests/storage_non_crud_tests.cpp +++ b/tests/storage_non_crud_tests.cpp @@ -123,7 +123,6 @@ TEST_CASE("InsertRange") { Object(int id, std::string name) : id{id}, name{std::move(name)} {} #endif }; - struct ObjectWithoutRowid { int id = 0; std::string name; @@ -133,6 +132,9 @@ TEST_CASE("InsertRange") { ObjectWithoutRowid(int id, std::string name) : id{id}, name{std::move(name)} {} #endif }; + struct ObjectWithoutRowid2 { + int id = 0; + }; auto storage = make_storage( "test_insert_range.sqlite", @@ -140,11 +142,16 @@ TEST_CASE("InsertRange") { make_table("objects_without_rowid", make_column("id", &ObjectWithoutRowid::id, primary_key()), make_column("name", &ObjectWithoutRowid::name)) + .without_rowid(), + make_table("objects_without_rowid2", + make_column("id", &ObjectWithoutRowid2::id), + primary_key(&ObjectWithoutRowid2::id)) .without_rowid()); storage.sync_schema(); storage.remove_all(); storage.remove_all(); + storage.remove_all(); SECTION("straight") { std::vector objects = {100, @@ -159,12 +166,16 @@ TEST_CASE("InsertRange") { 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()); + // test insert_range without rowid, column pk + std::vector objectsWR1 = {ObjectWithoutRowid{10, "Life"}, ObjectWithoutRowid{20, "Death"}}; + storage.insert_range(objectsWR1.begin(), objectsWR1.end()); REQUIRE(storage.get(10).name == "Life"); REQUIRE(storage.get(20).name == "Death"); + + // test insert_range without rowid, table pk + std::vector objectsWR2 = {ObjectWithoutRowid2{2}}; + storage.insert_range(objectsWR2.begin(), objectsWR2.end()); + REQUIRE_NOTHROW(storage.get(2)); } SECTION("pointers") { std::vector> objects; diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index 94cbcd58e..56920f6b5 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -486,6 +486,9 @@ TEST_CASE("insert") { int id; std::string name; }; + struct ObjectWithoutRowid2 { + int id = 0; + }; auto storage = make_storage( "test_insert.sqlite", @@ -493,30 +496,38 @@ TEST_CASE("insert") { make_table("objects_without_rowid", make_column("id", &ObjectWithoutRowid::id, primary_key()), make_column("name", &ObjectWithoutRowid::name)) + .without_rowid(), + make_table("objects_without_rowid2", + make_column("id", &ObjectWithoutRowid2::id), + primary_key(&ObjectWithoutRowid2::id)) .without_rowid()); storage.sync_schema(); storage.remove_all(); storage.remove_all(); + storage.remove_all(); - 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 +555,16 @@ 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("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, table pk") { + REQUIRE(storage.insert(ObjectWithoutRowid2{2}) == 0); + REQUIRE_NOTHROW(storage.get(2)); + } } TEST_CASE("Empty storage") {