diff --git a/include/matador/object/object_ptr.hpp b/include/matador/object/object_ptr.hpp index b7830c6..405de04 100644 --- a/include/matador/object/object_ptr.hpp +++ b/include/matador/object/object_ptr.hpp @@ -53,7 +53,7 @@ public: [[nodiscard]] bool is_detached() const { return proxy_->is_detached(); } [[nodiscard]] bool is_removed() const { return proxy_->is_removed(); } - void change_state(object_state s) { + void change_state(object_state s) const { if (proxy_) { proxy_->change_state(s); } diff --git a/include/matador/orm/session.hpp b/include/matador/orm/session.hpp index 2c313a0..e4678a4 100644 --- a/include/matador/orm/session.hpp +++ b/include/matador/orm/session.hpp @@ -145,6 +145,7 @@ utils::result, utils::error> session::insert(object::ob } else if (const auto exec_result = result->execute(); !exec_result.is_ok()) { return utils::failure(exec_result.err()); } + step.make_object_persistent(); } obj.change_state(object::object_state::Persistent); diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index b3c22ce..0a58394 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -64,6 +64,7 @@ struct insert_step { std::function apply_returning{}; std::function apply_primary_key{}; std::function bind_object{}; + std::function make_object_persistent{}; }; template @@ -139,17 +140,28 @@ public: return; } + if (processing_many_to_many_relations_.find(id) != processing_many_to_many_relations_.end()) { + return; + } + const auto it = schema_.find(std::string{id}); if (it == schema_.end()) { throw query_builder_exception(query_build_error::UnknownType); } - using relation_value_type = object::many_to_many_relation; + using relation_value_type = object::many_to_many_relation; if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) { throw query_builder_exception(query_build_error::InvalidRelationType); } + const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); + if (cit == contexts_by_type_.end()) { + throw query_builder_exception(query_build_error::UnknownType); + } + + const auto rel_it = processing_many_to_many_relations_.insert(id); + std::vector rel_steps; for (auto &obj : objects) { if (!obj) { continue; @@ -164,24 +176,32 @@ public: build_for(obj, relation_steps_); } - auto rel = object::make_object(join_column, inverse_join_column, ptr_, obj); + auto rel = object::make_object(join_column, inverse_join_column, obj, ptr_); - // Extract FK value from the foreign object - insert_step rel_step{}; - // const auto pk = rel_step.pk_accessor.get(*obj); - // if (pk.is_valid()) { - // continue; - // } + access::process(*this, *rel); - relation_steps_.push_back(std::move(rel_step)); + insert_step step{}; + step.pk_generator = utils::generator_type::None; + step.ctx = cit->second.insert; + step.bind_object = [rel](sql::statement &stmt) { stmt.bind(*rel); }; + step.make_object_persistent = [] {}; + + rel_steps.push_back(std::move(step)); } + relation_steps_.insert(relation_steps_.end(), rel_steps.begin(), rel_steps.end()); + processing_many_to_many_relations_.erase(id); } + template void on_has_many_to_many(const char *id, object::collection> &objects, const utils::foreign_attributes &attr) { if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) { return; } + if (processing_many_to_many_relations_.find(id) != processing_many_to_many_relations_.end()) { + return; + } + object::join_columns_collector collector; auto join_columns = collector.collect(); @@ -196,6 +216,13 @@ public: throw query_builder_exception(query_build_error::InvalidRelationType); } + const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); + if (cit == contexts_by_type_.end()) { + throw query_builder_exception(query_build_error::UnknownType); + } + + const auto rel_it = processing_many_to_many_relations_.insert(id); + std::vector rel_steps; for (auto &obj : objects) { if (!obj) { continue; @@ -212,23 +239,20 @@ public: auto rel = object::make_object(join_columns.inverse_join_column, join_columns.join_column, obj, ptr_); - // Extract FK value from the foreign object - insert_step rel_step{}; - // const auto pk = rel_step.pk_accessor.get(*obj); - // if (pk.is_valid()) { - // continue; - // } + access::process(*this, *rel); - relation_steps_.push_back(std::move(rel_step)); + insert_step step{}; + step.pk_generator = utils::generator_type::None; + step.ctx = cit->second.insert; + step.bind_object = [rel](sql::statement &stmt) { stmt.bind(*rel); }; + step.make_object_persistent = [] {}; + + rel_steps.push_back(std::move(step)); } + relation_steps_.insert(relation_steps_.end(), rel_steps.begin(), rel_steps.end()); + processing_many_to_many_relations_.erase(id); } - template - void process_has_many_to_many(const char *id, CollectionType &objects, const char *join_column, const char *inverse_join_column) { - if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) { - return; - } - } private: template static std::pair make_visit_key(const object::object_ptr &ptr) { @@ -292,6 +316,7 @@ private: step.pk_accessor.set(*ptr, id); }; } + step.make_object_persistent = [ptr] { ptr.change_state(object::object_state::Persistent); }; steps.push_back(std::move(step)); } @@ -318,6 +343,7 @@ private: std::vector steps_; std::vector relation_steps_; std::unordered_set, visit_key_hash> visited_; + std::unordered_set processing_many_to_many_relations_; }; } #endif //MATADOR_INSERT_QUERY_BUILDER_HPP \ No newline at end of file diff --git a/include/matador/utils/primary_key_generator_type.hpp b/include/matador/utils/primary_key_generator_type.hpp index b815115..e4eccd1 100644 --- a/include/matador/utils/primary_key_generator_type.hpp +++ b/include/matador/utils/primary_key_generator_type.hpp @@ -3,6 +3,7 @@ namespace matador::utils { enum class generator_type { + None = 0, /**< No generator type set. */ Manual, /**< User sets the primary key value manually. */ Auto, /**< Matador chooses the best generator type depending on the underlying dbms. */ Identity, /**< DBMS automatically generates the primary key value. */ diff --git a/test/orm/query/InsertQueryBuilderTest.cpp b/test/orm/query/InsertQueryBuilderTest.cpp index 44aeafa..849250e 100644 --- a/test/orm/query/InsertQueryBuilderTest.cpp +++ b/test/orm/query/InsertQueryBuilderTest.cpp @@ -18,6 +18,7 @@ #include "../../models/book.hpp" #include "../../models/airplane.hpp" #include "../../models/flight.hpp" +#include "../../models/recipe.hpp" using namespace matador::object; using namespace matador::sql; @@ -112,4 +113,41 @@ TEST_CASE("Test insert builder has many", "[query][insert_query_builder][has_man const auto& stmts = *build_result; REQUIRE_FALSE(stmts.empty()); REQUIRE(stmts.size() == 6); +} + +TEST_CASE("Test insert builder has many to many", "[query][insert_query_builder][many_to_many]") { + using namespace matador::test; + backend_provider::instance().register_backend("noop", std::make_unique()); + connection db("noop://noop.db"); + + schema scm; + const auto result = scm.attach("recipes") + .and_then( [&scm] { return scm.attach("ingredients"); } ); + REQUIRE(result.is_ok()); + + const std::vector ingredients { + make_object(1, "Apple"), + make_object(2, "Strawberry"), + make_object(3, "Pineapple"), + make_object(4, "Sugar"), + make_object(5, "Flour"), + make_object(6, "Butter"), + make_object(7, "Beans") + }; + + std::vector recipes { + make_object(1, "Apple Pie", std::vector{ingredients[0], ingredients[3], ingredients[4]}), + make_object(2, "Strawberry Cake", std::vector{ingredients[5], ingredients[6]}), + make_object(3, "Pineapple Pie", std::vector{ingredients[0], ingredients[1], ingredients[2]}) + }; + + const auto contexts_by_type = to_contexts_by_name(scm, db.dialect()); + insert_query_builder iqb(scm, contexts_by_type); + + auto build_result = iqb.build(recipes[0]); + REQUIRE(build_result.is_ok()); + + const auto& stmts = *build_result; + REQUIRE_FALSE(stmts.empty()); + REQUIRE(stmts.size() == 7); } \ No newline at end of file