From 59e1533cca9c40032d1e6def1cce420ef343d8af Mon Sep 17 00:00:00 2001 From: sascha Date: Mon, 20 Apr 2026 19:57:18 +0200 Subject: [PATCH] insert query builder has many to many progress --- backends/postgres/test/CMakeLists.txt | 1 + include/matador/object/collection.hpp | 6 +- include/matador/object/collection_proxy.hpp | 4 + .../matador/object/many_to_many_relation.hpp | 6 + .../matador/query/insert_query_builder.hpp | 107 +++++++++++++----- .../matador/query/query_builder_exception.hpp | 3 +- .../SessionInsertHasManyToManyTest.cpp | 40 +++++++ 7 files changed, 135 insertions(+), 32 deletions(-) create mode 100644 test/backends/SessionInsertHasManyToManyTest.cpp diff --git a/backends/postgres/test/CMakeLists.txt b/backends/postgres/test/CMakeLists.txt index 493a4d9..b8fbe1a 100644 --- a/backends/postgres/test/CMakeLists.txt +++ b/backends/postgres/test/CMakeLists.txt @@ -41,6 +41,7 @@ set(TEST_SOURCES ../../../test/backends/TableSequenceFixture.cpp ../../../test/backends/TableSequenceTest.cpp ../../../test/backends/SessionInsertHasMany.cpp + ../../../test/backends/SessionInsertHasManyToManyTest.cpp ) set(LIBRARY_TEST_TARGET PostgresTests) diff --git a/include/matador/object/collection.hpp b/include/matador/object/collection.hpp index dcd730a..bb6cbf1 100644 --- a/include/matador/object/collection.hpp +++ b/include/matador/object/collection.hpp @@ -8,9 +8,9 @@ namespace matador::object { template < class Type > class collection { public: - using value_type = Type; - using iterator = typename std::vector::iterator; - using const_iterator = typename std::vector::const_iterator; + using value_type = typename collection_proxy::value_type; + using iterator = typename collection_proxy::iterator; + using const_iterator = typename collection_proxy::const_iterator; collection() : proxy_(std::make_shared>()) {} diff --git a/include/matador/object/collection_proxy.hpp b/include/matador/object/collection_proxy.hpp index d2e7023..ce98e9e 100644 --- a/include/matador/object/collection_proxy.hpp +++ b/include/matador/object/collection_proxy.hpp @@ -16,6 +16,10 @@ namespace matador::object { template class collection_proxy final { public: + using value_type = Type; + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + collection_proxy() = default; // Lazy diff --git a/include/matador/object/many_to_many_relation.hpp b/include/matador/object/many_to_many_relation.hpp index 1b22211..d45ceee 100644 --- a/include/matador/object/many_to_many_relation.hpp +++ b/include/matador/object/many_to_many_relation.hpp @@ -15,6 +15,12 @@ public: many_to_many_relation(std::string local_name, std::string remote_name) : local_name_(std::move(local_name)) , remote_name_(std::move(remote_name)) {} + many_to_many_relation(std::string local_name, std::string remote_name, const object_ptr& local, const object_ptr& remote) + : local_name_(std::move(local_name)) + , remote_name_(std::move(remote_name)) + , local_(local) + , remote_(remote) + {} template void process(Operator &op) { diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 2dda231..b3c22ce 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -128,14 +128,14 @@ public: } template static void on_has_many(const char * /*id*/, object::collection &/*con*/, const char *, const utils::foreign_attributes &/*attr*/) {} - template - void on_has_many_to_many(const char *id, Collection &container, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes & ) { + + 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) { if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) { return; } - if (current_entity_pk_ == 0ULL) { - // Without a local PK we cannot create relation rows - // (PK may be assigned later by Identity; that case needs a different mechanism than requested here) + + if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) { return; } @@ -143,38 +143,92 @@ public: if (it == schema_.end()) { throw query_builder_exception(query_build_error::UnknownType); } - const table &relation_table = it->second.table(); - for (auto &obj : container) { + 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); + } + + for (auto &obj : objects) { if (!obj) { continue; } - // Ensure target exists as dependency (deps first) - if (!obj.is_persistent()) { - using dep_t = std::remove_reference_t; - build_for(obj, steps_); - } - - // Extract FK value from the foreign object - insert_step rel_step{}; - const auto pk = rel_step.pk_accessor.get(*obj); - if (pk.is_valid()) { + if (obj.is_persistent()) { continue; } - // Build INSERT into relation table with the 2 FK columns - // rel_step.query = executable_query{ - // insert() - // .into(relation_table, {table_column{&relation_table, join_column}, table_column{&relation_table, inverse_join_column}}) - // .values({utils::database_type{current_entity_pk_}, utils::database_type{fk.value}}) - // }; + // Ensure target exists as dependency (deps first) + if (obj.is_transient()) { + build_for(obj, relation_steps_); + } + + auto rel = object::make_object(join_column, inverse_join_column, ptr_, obj); + + // 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; + // } + relation_steps_.push_back(std::move(rel_step)); } } - template - static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} + 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; + } + object::join_columns_collector collector; + auto join_columns = collector.collect(); + + 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; + + if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) { + throw query_builder_exception(query_build_error::InvalidRelationType); + } + + for (auto &obj : objects) { + if (!obj) { + continue; + } + + if (obj.is_persistent()) { + continue; + } + + // Ensure target exists as dependency (deps first) + if (obj.is_transient()) { + build_for(obj, relation_steps_); + } + + 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; + // } + + relation_steps_.push_back(std::move(rel_step)); + } + } + + 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) { @@ -264,9 +318,6 @@ private: std::vector steps_; std::vector relation_steps_; std::unordered_set, visit_key_hash> visited_; - - - std::uint64_t current_entity_pk_{0}; }; } #endif //MATADOR_INSERT_QUERY_BUILDER_HPP \ No newline at end of file diff --git a/include/matador/query/query_builder_exception.hpp b/include/matador/query/query_builder_exception.hpp index 499d37f..f6e1e8a 100644 --- a/include/matador/query/query_builder_exception.hpp +++ b/include/matador/query/query_builder_exception.hpp @@ -13,7 +13,8 @@ enum class query_build_error : std::uint8_t { MissingPrimaryKey, MissingObject, UnexpectedError, - QueryError + QueryError, + InvalidRelationType }; class query_builder_exception final : public std::exception { diff --git a/test/backends/SessionInsertHasManyToManyTest.cpp b/test/backends/SessionInsertHasManyToManyTest.cpp new file mode 100644 index 0000000..4fc2d92 --- /dev/null +++ b/test/backends/SessionInsertHasManyToManyTest.cpp @@ -0,0 +1,40 @@ +#include "catch2/catch_test_macros.hpp" + +#include "SessionFixture.hpp" + +#include "connection.hpp" + +#include "models/recipe.hpp" + +using namespace matador; +using namespace matador::object; +using namespace matador::test; + +TEST_CASE_METHOD(SessionFixture, "Test insert object with has many to many relation", "[session][insert][has_many_to_many]") { + auto result = schema.attach("recipes") + .and_then( [this] { return schema.attach("ingredients"); } ) + .and_then([this] { return schema.create(db); } ); + + orm::session ses({bus, connection::dns, 4}, schema); + + 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]}) + }; + + for (auto &r: recipes) { + auto res = ses.insert(r); + REQUIRE(res.is_ok()); + } +} \ No newline at end of file