From 73abd61eba4c01c202aa158bf48022696a4bd9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Tue, 3 Feb 2026 07:26:37 +0100 Subject: [PATCH] has many to many collection resolver progress --- include/matador/query/schema.hpp | 18 +++++-- test/backends/QueryTest.cpp | 82 +++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/include/matador/query/schema.hpp b/include/matador/query/schema.hpp index 283dc88..f33235b 100644 --- a/include/matador/query/schema.hpp +++ b/include/matador/query/schema.hpp @@ -69,10 +69,6 @@ public: static void on_belongs_to(const char * /*id*/, Pointer & /*x*/, const utils::foreign_attributes &/*attr*/) {} template static void on_has_one(const char * /*id*/, Pointer & /*x*/, const utils::foreign_attributes &/*attr*/) {} - template - static void on_has_many_to_many(const char *, ContainerType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &/*attr*/) {} - template - static void on_has_many_to_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {} template void on_has_many(const char * /*id*/, CollectionType &/*cont*/, const char *join_column, const utils::foreign_attributes &/*attr*/, std::enable_if_t::value> * = nullptr) { @@ -99,6 +95,20 @@ public: } + template + void on_has_many_to_many(const char *, CollectionType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &/*attr*/) { + const auto it = schema_.find(typeid(typename CollectionType::value_type::value_type)); + if (it == schema_.end()) { + throw query_builder_exception{query_build_error::UnknownType}; + } + if (!it->second.node().info().has_primary_key()) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + + } + template + static void on_has_many_to_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {} + private: basic_schema& schema_; const std::type_index root_type_; diff --git a/test/backends/QueryTest.cpp b/test/backends/QueryTest.cpp index 69104bb..3b9b77e 100644 --- a/test/backends/QueryTest.cpp +++ b/test/backends/QueryTest.cpp @@ -740,8 +740,6 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with lazy belongs to relation", } TEST_CASE_METHOD(QueryFixture, "Test load entity with eager belongs to relation", "[query][belongs_to][eager]") { - // auto result = repo.attach("authors") - // .and_then( [this] { return repo.attach("books"); } ) auto result = repo.attach("books") .and_then( [this] { return repo.attach("authors"); }) .and_then([this] { return repo.create(db); }); @@ -816,4 +814,84 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager belongs to relation" REQUIRE(b->published_in == books.at(index)->published_in); ++index; } +} + +TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many relation", "[query][has_many_to_many][eager]") { + auto result = repo.attach("ingredients") + .and_then( [this] { return repo.attach("recipes"); } ) + .and_then([this] {return repo.create(db); }); + + REQUIRE(db.exists(RECIPE.table_name())); + REQUIRE(db.exists(INGREDIENT.table_name())); + REQUIRE(db.exists(RECIPE_INGREDIENT.table_name())); + + std::vector ingredients { + {1, "Apple"}, + {2, "Strawberry"}, + {3, "Pineapple"}, + {4, "Sugar"}, + {5, "Flour"}, + {6, "Butter"}, + {7, "Beans"} + }; + + for (const auto &i: ingredients) { + auto res = query::insert() + .into(INGREDIENT, {INGREDIENT.id, INGREDIENT.name}) + .values(i) + .execute(db); + REQUIRE(res.is_ok()); + REQUIRE(*res == 1); + } + + std::vector recipes{ + {7, "Apple Crumble"}, + {8, "Beans Chili"}, + {9, "Fruit Salad"} + }; + + for (const auto &r: recipes) { + auto res = query::insert() + .into(RECIPE, {RECIPE.id, RECIPE.name}) + .values(r) + .execute(db); + REQUIRE(res.is_ok()); + REQUIRE(*res == 1); + } + + std::vector> recipe_ingredients { + { 7, 1 }, + { 7, 4 }, + { 7, 5 }, + { 8, 6 }, + { 8, 7 }, + { 9, 1 }, + { 9, 2 }, + { 9, 3 } + }; + + for (const auto & [recipe_id, ingredient_id]: recipe_ingredients) { + auto res = query::insert() + .into(RECIPE_INGREDIENT, {RECIPE_INGREDIENT.recipe_id, RECIPE_INGREDIENT.ingredient_id}) + .values({recipe_id, ingredient_id}) + .execute(db); + REQUIRE(res.is_ok()); + REQUIRE(*res == 1); + } + + const auto r = RECIPE.as("r"); + const auto ri= RECIPE_INGREDIENT.as("ri"); + const auto i = INGREDIENT.as("i"); + + auto ingredients_result = query::select({r.id, r.name, ri.ingredient_id, i.name}) + .from(r) + .join_left(ri).on(r.id == ri.recipe_id) + .join_left(i).on(ri.ingredient_id == i.id) + .order_by({i.id, r.id}).asc() + .fetch_all(db); + REQUIRE(ingredients_result.is_ok()); + + for (const auto &ing : *ingredients_result) { + REQUIRE(!ing->recipes.empty()); + } } \ No newline at end of file