diff --git a/backends/postgres/test/CMakeLists.txt b/backends/postgres/test/CMakeLists.txt index cd53054..1bc0772 100644 --- a/backends/postgres/test/CMakeLists.txt +++ b/backends/postgres/test/CMakeLists.txt @@ -46,6 +46,7 @@ set(TEST_SOURCES ../../../test/models/user.hpp ../../../test/utils/RecordPrinter.cpp ../../../test/utils/RecordPrinter.hpp + ../../../test/backends/SessionDeleteHasMany.cpp ) set(LIBRARY_TEST_TARGET PostgresTests) diff --git a/include/matador/query/delete_query_builder.hpp b/include/matador/query/delete_query_builder.hpp new file mode 100644 index 0000000..7439625 --- /dev/null +++ b/include/matador/query/delete_query_builder.hpp @@ -0,0 +1,267 @@ +#ifndef MATADOR_DELETE_QUERY_BUILDER_HPP +#define MATADOR_DELETE_QUERY_BUILDER_HPP + +#include "matador/object/collection.hpp" +#include "matador/object/object_cache.hpp" +#include "matador/object/object_ptr.hpp" + +#include "matador/query/basic_schema.hpp" +#include "matador/query/error_code.hpp" +#include "matador/query/execute_step.hpp" +#include "matador/query/query_contexts.hpp" +#include "matador/query/query_builder_exception.hpp" +#include "matador/query/query_builder_utils.hpp" + +#include "matador/sql/execute_result.hpp" +#include "matador/sql/statement.hpp" + +#include "matador/utils/error.hpp" +#include "matador/utils/identifier.hpp" +#include "matador/utils/primary_key_accessor.hpp" +#include "matador/utils/result.hpp" + +namespace matador::query { +template +class delete_step_object final : public execute_step { +public: + delete_step_object(sql::query_context ctx, const object::object_ptr &ptr) + : execute_step(std::move(ctx)) + , ptr_(ptr) {} + + utils::result prepare(sql::executor &/*conn*/) override { + id_ = ptr_.primary_key(); + return utils::ok(); + } + + utils::result execute(sql::statement &stmt) override { + if (!ptr_) { + return utils::failure(utils::error{error_code::InvalidObject, "Object is null"}); + } + + if (const auto result = stmt.bind(0, id_).execute(); !result.is_ok()) { + return utils::failure(result.err()); + } + + return utils::ok(); + } + + utils::result finalize(object::object_cache &cache, const resolver_service_ptr& /*resolver_service*/) override { + if (!ptr_) { + return utils::failure(utils::error{error_code::InvalidObject, "Object is null"}); + } + + cache.erase(id_); + ptr_.change_state(object::object_state::Transient); + + return utils::ok(); + } + +private: + object::object_ptr ptr_; +}; + +struct delete_context { + const basic_schema &schema_; + const std::unordered_map &contexts_by_type_; + + std::vector> steps_{}; + std::vector> relation_steps_{}; + std::unordered_set, entity_visit_key_hash> visited_{}; +}; + +template +class delete_step_processor { +public: + explicit delete_step_processor(delete_context &ctx) + : ctx_(ctx) {} + + utils::result build(object::object_ptr ptr, const bool as_relation_step = false) { + if (!ptr) { + return utils::failure(utils::error{error_code::InvalidObject, "Object is null"}); + } + + ptr_ = ptr; + + const auto key = make_entity_visit_key(*ptr_); + if (ctx_.visited_.find(key) != ctx_.visited_.end()) { + return utils::ok(); + } + ctx_.visited_.insert(key); + + const auto it = ctx_.schema_.find(typeid(ObjectType)); + if (it == ctx_.schema_.end()) { + return utils::failure(utils::error{error_code::UnknownType, "Unknown type"}); + } + + const auto &info = it->second.node().info(); + if (!info.has_primary_key()) { + return utils::failure(utils::error{error_code::MissingPrimaryKey, "Type " + info.name() + " has no primary key"}); + } + + try { + access::process(*this, *ptr_); + } catch (const query_builder_exception &ex) { + return utils::failure(ex.error()); + } + + const auto cit = ctx_.contexts_by_type_.find(it->second.node().info().type_index()); + if (cit == ctx_.contexts_by_type_.end()) { + return utils::failure(utils::error{error_code::UnknownType, "Unknown type"}); + } + + if (as_relation_step) { + ctx_.relation_steps_.push_back(std::make_unique>(cit->second.delete_one, ptr_)); + } else { + ctx_.steps_.push_back(std::make_unique>(cit->second.delete_one, ptr_)); + } + + ptr_.reset(); + + return utils::ok(); + } + + template + static void on_primary_key(const char * /*id*/, PrimaryKeyType &, const utils::primary_key_attribute & /*attr*/) {} + + static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {} + + template + static void on_attribute(const char * /*id*/, Type &, const utils::field_attributes & /*attr*/) {} + + template + void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) { + on_foreign_object(obj, attr); + } + + template + void on_has_one(const char * /*id*/, Pointer &obj, const char * /*join_column*/, const utils::foreign_attributes &attr) { + on_foreign_object(obj, attr); + } + + template + void on_has_many(const char * /*id*/, + object::collection> &objects, + const char *join_column, + const utils::foreign_attributes &attr) { + if (join_column == nullptr) { + return; + } + if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Remove)) { + return; + } + + delete_step_processor processor{ctx_}; + for (auto &obj : objects) { + if (!obj) { + continue; + } + + auto result = processor.build(obj, true); + if (!result) { + throw query_builder_exception(result.release_error()); + } + } + } + + template + static void on_has_many(const char * /*id*/, + object::collection & /*objects*/, + const char * /*join_column*/, + const utils::foreign_attributes & /*attr*/) { + // Value-Collections bzw. Relationstabellen werden hier nicht direkt gelöscht. + // Dafür wird das Delete-Statement der jeweiligen Entity verwendet. + } + + template + void on_has_many_to_many(const char * /*id*/, + object::collection> &objects, + const char * /*join_column*/, + const char * /*inverse_join_column*/, + const utils::foreign_attributes &attr) { + on_many_to_many_objects(objects, attr); + } + + template + void on_has_many_to_many(const char * /*id*/, + object::collection> &objects, + const utils::foreign_attributes &attr) { + on_many_to_many_objects(objects, attr); + } + +private: + template + void on_foreign_object(object::object_ptr &obj, const utils::foreign_attributes &attr) { + if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Remove) || !obj) { + return; + } + + delete_step_processor processor{ctx_}; + + auto result = processor.build(obj); + if (!result) { + throw query_builder_exception(result.release_error()); + } + } + + template + void on_many_to_many_objects(object::collection> &objects, + const utils::foreign_attributes &attr) { + if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Remove)) { + return; + } + + delete_step_processor processor{ctx_}; + for (auto &obj : objects) { + if (!obj) { + continue; + } + + auto result = processor.build(obj, true); + if (!result) { + throw query_builder_exception(result.release_error()); + } + } + } + +private: + delete_context &ctx_; + object::object_ptr ptr_; +}; + +template +class delete_query_builder { +public: + explicit delete_query_builder(const basic_schema &schema, + const std::unordered_map &contexts_by_type) + : schema_(schema) + , contexts_by_type_(contexts_by_type) {} + + utils::result>, utils::error> build(const object::object_ptr &ptr) { + if (const auto it = schema_.find(typeid(ObjectType)); it == schema_.end()) { + return utils::failure(utils::error{error_code::UnknownType, "Unknown type for delete query"}); + } + + delete_context ctx{schema_, contexts_by_type_}; + delete_step_processor processor{ctx}; + + const auto result = processor.build(ptr); + if (!result) { + return utils::failure(result.err()); + } + + // relation inserts must run after all entity inserts were collected + for (auto &s : ctx.steps_) { + ctx.relation_steps_.push_back(std::move(s)); + } + ctx.steps_.clear(); + + return utils::ok(std::move(ctx.relation_steps_)); + } + +private: + const basic_schema &schema_; + const std::unordered_map &contexts_by_type_; +}; +} // namespace matador::query + +#endif //MATADOR_DELETE_QUERY_BUILDER_HPP diff --git a/include/matador/query/execute_step.hpp b/include/matador/query/execute_step.hpp new file mode 100644 index 0000000..95c9f2e --- /dev/null +++ b/include/matador/query/execute_step.hpp @@ -0,0 +1,37 @@ +#ifndef MATADOR_EXECUTE_STEP_HPP +#define MATADOR_EXECUTE_STEP_HPP + +#include "matador/utils/identifier.hpp" +#include "matador/utils/primary_key_accessor.hpp" +#include "matador/utils/error.hpp" +#include "matador/utils/result.hpp" + +#include "matador/object/object_cache.hpp" + +#include "matador/sql/query_context.hpp" +#include "matador/sql/executor.hpp" + +#include "matador/query/abstract_pk_generator.hpp" + +namespace matador::query { +using resolver_service_ptr = std::shared_ptr; + +class execute_step { +public: + explicit execute_step(sql::query_context ctx) + : ctx_(std::move(ctx)) {} + virtual ~execute_step() = default; + + virtual utils::result prepare(sql::executor &conn) = 0; + virtual utils::result execute(sql::statement &stmt) = 0; + virtual utils::result finalize(object::object_cache& cache, const resolver_service_ptr& resolver_service) = 0; + + [[nodiscard]] const sql::query_context& ctx() const { return ctx_; } + +protected: + utils::identifier id_; + utils::primary_key_accessor pk_accessor_; + sql::query_context ctx_; +}; +} +#endif //MATADOR_EXECUTE_STEP_HPP diff --git a/include/matador/query/fk_value_extractor.hpp b/include/matador/query/fk_value_extractor.hpp index 701d511..376baff 100644 --- a/include/matador/query/fk_value_extractor.hpp +++ b/include/matador/query/fk_value_extractor.hpp @@ -12,14 +12,12 @@ class foreign_attributes; namespace matador::query::detail { -class fk_value_extractor -{ +class fk_value_extractor { public: fk_value_extractor() = default; template - utils::database_type extract(Type &x) - { + utils::database_type extract(Type &x) { access::process(*this, x); return value_; } diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index f4c363d..38045eb 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -10,6 +10,7 @@ #include "matador/query/query.hpp" #include "matador/query/query_contexts.hpp" #include "matador/query/query_builder_exception.hpp" +#include "matador/query/query_builder_utils.hpp" #include "matador/sql/statement.hpp" @@ -55,19 +56,6 @@ private: std::string join_column_; }; -template -static std::pair make_visit_key(const EntityType &ptr) { - return {std::type_index(typeid(EntityType)), static_cast(&ptr)}; -} - -struct visit_key_hash { - size_t operator()(const std::pair &p) const noexcept { - const size_t h1 = p.first.hash_code(); - const size_t h2 = std::hash{}(p.second); - return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); - } -}; - struct processing_many_to_many_key { std::string id; std::type_index local_type{typeid(void)}; @@ -100,9 +88,9 @@ struct insert_context { const basic_schema &schema_; const std::unordered_map &contexts_by_type_; - std::vector> steps_{}; - std::vector> relation_steps_{}; - std::unordered_set, visit_key_hash> visited_{}; + std::vector> steps_{}; + std::vector> relation_steps_{}; + std::unordered_set, entity_visit_key_hash> visited_{}; std::unordered_set processing_many_to_many_relations_{}; }; @@ -120,7 +108,7 @@ public: } ptr_ = ptr; - const auto key = make_visit_key(*ptr_); + const auto key = make_entity_visit_key(*ptr_); if (ctx_.visited_.find(key) != ctx_.visited_.end()) { return utils::ok(); } @@ -339,7 +327,7 @@ private: } std::ignore = ctx_.processing_many_to_many_relations_.insert(key); - std::vector> insert_relation_steps; + std::vector> insert_relation_steps; insert_step_processor processor(ctx_); for (auto &obj : objects) { if (!obj) { @@ -366,7 +354,7 @@ private: ctx_.processing_many_to_many_relations_.erase(key); } - std::unique_ptr create_insert_step(const sql::query_context& query_ctx, const schema_node& node) { + std::unique_ptr create_insert_step(const sql::query_context& query_ctx, const schema_node& node) { if (node.pk_generator().type() == utils::generator_type::Manual) { return std::make_unique>(query_ctx, ptr_); } @@ -389,7 +377,7 @@ public: , contexts_by_type_(contexts_by_type) {} - utils::result>, utils::error> build(const object::object_ptr &ptr) { + utils::result>, utils::error> build(const object::object_ptr &ptr) { if (const auto it = schema_.find(typeid(ObjectType)); it == schema_.end()) { return utils::failure(utils::error{error_code::UnknownType, "Unknown type for insert query"}); } diff --git a/include/matador/query/insert_step.hpp b/include/matador/query/insert_step.hpp index 7e4c680..73b16e1 100644 --- a/include/matador/query/insert_step.hpp +++ b/include/matador/query/insert_step.hpp @@ -1,44 +1,18 @@ #ifndef MATADOR_INSERT_STEP_HPP #define MATADOR_INSERT_STEP_HPP -#include - -#include "matador/utils/identifier.hpp" -#include "matador/utils/primary_key_accessor.hpp" -#include "matador/utils/error.hpp" -#include "matador/utils/result.hpp" +#include "matador/query/abstract_pk_generator.hpp" +#include "matador/query/execute_step.hpp" +#include "matador/query/error_code.hpp" #include "matador/object/object_cache.hpp" #include "matador/object/object_ptr.hpp" -#include "matador/sql/query_context.hpp" #include "matador/sql/execute_result.hpp" -#include "matador/sql/executor.hpp" +#include "matador/sql/resolver_service.hpp" #include "matador/sql/statement.hpp" -#include "matador/query/error_code.hpp" -#include "matador/query/abstract_pk_generator.hpp" - namespace matador::query { -using resolver_service_ptr = std::shared_ptr; - -class insert_step { -public: - explicit insert_step(sql::query_context ctx) - : ctx_(std::move(ctx)) {} - virtual ~insert_step() = default; - - virtual utils::result prepare(sql::executor &conn) = 0; - virtual utils::result insert(sql::statement &stmt) = 0; - virtual utils::result finalize(object::object_cache& cache, const resolver_service_ptr& resolver_service) = 0; - - [[nodiscard]] const sql::query_context& ctx() const { return ctx_; } -protected: - utils::identifier id_; - utils::primary_key_accessor pk_accessor_; - sql::query_context ctx_; -}; - template utils::result finalize_inserted_object(object::object_ptr &ptr, const utils::identifier& pk, @@ -66,10 +40,10 @@ utils::result finalize_inserted_object(object::object_ptr -class insert_step_pk_generated : public insert_step { +class insert_step_pk_generated : public execute_step { public: insert_step_pk_generated(sql::query_context ctx, const object::object_ptr& ptr, abstract_pk_generator& pk_generator) - : insert_step(std::move(ctx)) + : execute_step(std::move(ctx)) , ptr_(ptr) , pk_generator_(pk_generator){} @@ -84,7 +58,7 @@ public: return utils::ok(); } - utils::result insert(sql::statement& stmt) override { + utils::result execute(sql::statement& stmt) override { stmt.bind(*ptr_); if (const auto exec_result = stmt.execute(); !exec_result.is_ok()) { @@ -105,10 +79,10 @@ private: }; template -class insert_step_pk_identity : public insert_step { +class insert_step_pk_identity : public execute_step { public: insert_step_pk_identity(sql::query_context ctx, const object::object_ptr& ptr, std::string pk_column_name) - : insert_step(std::move(ctx)) + : execute_step(std::move(ctx)) , ptr_(ptr) , pk_column_name_(std::move(pk_column_name)){} @@ -116,7 +90,7 @@ public: return utils::ok(); } - utils::result insert(sql::statement& stmt) override { + utils::result execute(sql::statement& stmt) override { stmt.bind(*ptr_); auto result = stmt.fetch_one(); @@ -148,16 +122,16 @@ private: }; template -class insert_step_pk_manual : public insert_step { +class insert_step_pk_manual : public execute_step { public: insert_step_pk_manual(sql::query_context ctx, const object::object_ptr& ptr) - : insert_step(std::move(ctx)) + : execute_step(std::move(ctx)) , ptr_(ptr) {} utils::result prepare(sql::executor &) override { return utils::ok(); } - utils::result insert(sql::statement &stmt) override { + utils::result execute(sql::statement &stmt) override { stmt.bind(*ptr_); if (const auto exec_result = stmt.execute(); !exec_result.is_ok()) { return utils::failure(exec_result.err()); @@ -177,16 +151,16 @@ private: }; template -class insert_step_relation : public insert_step { +class insert_step_relation : public execute_step { public: insert_step_relation(sql::query_context ctx, const object::object_ptr& ptr) - : insert_step(std::move(ctx)) + : execute_step(std::move(ctx)) , ptr_(ptr) {} utils::result prepare(sql::executor &) override { return utils::ok(); } - utils::result insert(sql::statement &stmt) override { + utils::result execute(sql::statement &stmt) override { stmt.bind(*ptr_); if (const auto exec_result = stmt.execute(); !exec_result.is_ok()) { return utils::failure(exec_result.err()); diff --git a/include/matador/query/query_builder_utils.hpp b/include/matador/query/query_builder_utils.hpp new file mode 100644 index 0000000..a8d18d5 --- /dev/null +++ b/include/matador/query/query_builder_utils.hpp @@ -0,0 +1,22 @@ +#ifndef MATADOR_QUERY_BUILDER_UTILS_HPP +#define MATADOR_QUERY_BUILDER_UTILS_HPP + +#include +#include +#include + +namespace matador::query { +template +static std::pair make_entity_visit_key(const EntityType &ptr) { + return {std::type_index(typeid(EntityType)), static_cast(&ptr)}; +} + +struct entity_visit_key_hash { + size_t operator()(const std::pair &p) const noexcept { + const size_t h1 = p.first.hash_code(); + const size_t h2 = std::hash{}(p.second); + return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); + } +}; +} +#endif //MATADOR_QUERY_BUILDER_UTILS_HPP diff --git a/include/matador/query/session.hpp b/include/matador/query/session.hpp index 32170f9..6420107 100644 --- a/include/matador/query/session.hpp +++ b/include/matador/query/session.hpp @@ -2,6 +2,7 @@ #define QUERY_SESSION_HPP #include "matador/query/error_code.hpp" +#include "matador/query/delete_query_builder.hpp" #include "matador/query/select_query_builder.hpp" #include "matador/query/criteria.hpp" #include "matador/query/insert_query_builder.hpp" @@ -52,7 +53,6 @@ public: */ template utils::result, utils::error> insert(object::object_ptr obj); - template utils::result, utils::error> update(const object::object_ptr &obj); template @@ -112,7 +112,7 @@ utils::result, utils::error> session::insert(object::ob return utils::failure(stmt.err()); } - if (const auto result = step->insert(*stmt); !result.is_ok()) { + if (const auto result = step->execute(*stmt); !result.is_ok()) { return utils::failure(result.err()); } } @@ -207,28 +207,67 @@ utils::result, utils::error> session::update(const obje template utils::result session::remove(const object::object_ptr &obj) { - const auto it = schema_.find(typeid(Type)); - if (it == schema_.end()) { + if (!obj.is_persistent()) { + return utils::ok(); + } + + if (const auto it = schema_.find(typeid(Type)); it == schema_.end()) { return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); } - using namespace matador::utils; - using namespace matador::query; - const auto col = table_column(it->second.node().info().primary_key_attribute()->name()); - const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); - if (cit == contexts_by_type_.end()) { - return failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); - } - auto stmt = cache_.acquire(cit->second.delete_one); - if (!stmt.is_ok()) { - return failure(stmt.err()); + delete_query_builder dqb(schema_, contexts_by_type_); + auto steps = dqb.build(obj); + if (!steps.is_ok()) { + return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build delete dependency queries.")); } - pk_object_binder binder(*stmt, stmt->bind_pos()); - if (const auto update_result = binder.bind(*obj).execute(); !update_result.is_ok()) { - return utils::failure(update_result.err()); + for (auto &step : *steps) { + const auto conn = pool_.acquire(); + if (!conn.valid()) { + return utils::failure(make_error(error_code::FailedToAcquirePool, "Failed to acquire connection pool for primary key generation.")); + } + + if (const auto result = step->prepare(*conn); !result.is_ok()) { + return utils::failure(result.err()); + } + conn.release(); + + auto stmt = cache_.acquire(step->ctx()); + if (!stmt.is_ok()) { + return utils::failure(stmt.err()); + } + + if (const auto result = step->execute(*stmt); !result.is_ok()) { + return utils::failure(result.err()); + } } + + // After successfully executed all deletes, add them to the object cache + for (auto &step : *steps) { + if (const auto result = step->finalize(object_cache_, resolver_service_); !result.is_ok()) { + return utils::failure(result.err()); + } + } + return utils::ok(); + // using namespace matador::utils; + // using namespace matador::query; + // + // const auto col = table_column(it->second.node().info().primary_key_attribute()->name()); + // const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); + // if (cit == contexts_by_type_.end()) { + // return failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); + // } + // auto stmt = cache_.acquire(cit->second.delete_one); + // if (!stmt.is_ok()) { + // return failure(stmt.err()); + // } + // + // pk_object_binder binder(*stmt, stmt->bind_pos()); + // if (const auto update_result = binder.bind(*obj).execute(); !update_result.is_ok()) { + // return utils::failure(update_result.err()); + // } + // return utils::ok(); } template diff --git a/source/orm/CMakeLists.txt b/source/orm/CMakeLists.txt index 78689c8..500a870 100644 --- a/source/orm/CMakeLists.txt +++ b/source/orm/CMakeLists.txt @@ -31,7 +31,7 @@ add_library(matador-orm STATIC ../../include/matador/query/identity_pk_generator.hpp ../../include/matador/query/insert_query_builder.hpp ../../include/matador/query/insert_query_builder.hpp - ../../include/matador/query/insert_step.hpp + ../../include/matador/query/execute_step.hpp ../../include/matador/query/intermediates/executable_query.hpp ../../include/matador/query/intermediates/fetchable_query.hpp ../../include/matador/query/intermediates/query_alter_intermediate.hpp @@ -221,6 +221,9 @@ add_library(matador-orm STATIC sql/resolver_service.cpp sql/statement.cpp sql/statement_cache.cpp + ../../include/matador/query/delete_query_builder.hpp + ../../include/matador/query/query_builder_utils.hpp + ../../include/matador/query/insert_step.hpp ) target_include_directories(matador-orm diff --git a/test/backends/SessionDeleteHasMany.cpp b/test/backends/SessionDeleteHasMany.cpp new file mode 100644 index 0000000..a5264c8 --- /dev/null +++ b/test/backends/SessionDeleteHasMany.cpp @@ -0,0 +1,71 @@ +#include "catch2/catch_test_macros.hpp" + +#include "SessionFixture.hpp" + +#include "connection.hpp" + +#include "matador/query/session.hpp" + +#include "models/author.hpp" +#include "models/book.hpp" + +using namespace matador::test; +using namespace matador::query; +using namespace matador::object; + +namespace matador::test { +template +void validate_author_state(const object_ptr& ptr, object_state expected_state) { + REQUIRE(ptr.is_state(expected_state)); + for (auto &b: ptr->books) { + REQUIRE(b.is_state(expected_state)); + } +} +} + +namespace matador::utils { +template < typename ValueType > +std::ostream& operator<<(std::ostream& os, const result& value) { + if (value) { + return os; + } + return os << "Error: " << value.err(); +} + +std::ostream& operator<<(std::ostream& os, const result& value) { + if (value) { + return os; + } + return os << "Error: " << value.err(); +} +} + +TEST_CASE_METHOD(SessionFixture, "Test delete object with has many relation", "[session][delete][has_many]") { + const auto result = schema.attach("books") + .and_then( [this] { return schema.attach("authors"); } ) + .and_then([this] { return schema.create(db); } ); + REQUIRE(result.is_ok()); + + session ses({bus, connection::dns, 4}, schema); + + auto s_king = make_object(1, "Steven", "King", "21.9.1947", 1956, false); + + s_king->books.push_back(make_object(2, "Carrie", nullobj, 1974)); + s_king->books.push_back(make_object(3, "The Shining", nullobj, 1977)); + s_king->books.push_back(make_object(4, "It", nullobj, 1986)); + s_king->books.push_back(make_object(5, "Misery", nullobj, 1987)); + s_king->books.push_back(make_object(6, "The Dark Tower: The Gunslinger", nullobj, 1982)); + + validate_author_state(s_king, object_state::Transient); + auto res = ses.insert(s_king); + REQUIRE(res); + validate_author_state(s_king, object_state::Persistent); + + auto author_result = ses.find(s_king->id); + REQUIRE(author_result); + REQUIRE(author_result->is_persistent()); + REQUIRE(author_result.value()->books.size() == 5); + + auto del_res = ses.remove(s_king); + REQUIRE(del_res); +}