diff --git a/demo/work.cpp b/demo/work.cpp index 2819571..db12419 100644 --- a/demo/work.cpp +++ b/demo/work.cpp @@ -121,8 +121,10 @@ int main() { using namespace matador::query::meta; using namespace matador::utils; + const auto payloads = PAYLOAD.as( "payloads" ); const auto tasks = TASK.as( "tasks" ); + const auto stmt1 = query:: query::select( { tasks.payload, payloads.id } ) .from( tasks, payloads ) .where ( payloads.id == tasks.payload ) diff --git a/include/matador/query/schema.hpp b/include/matador/query/schema.hpp index f33235b..54287cf 100644 --- a/include/matador/query/schema.hpp +++ b/include/matador/query/schema.hpp @@ -96,18 +96,42 @@ 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)); + void on_has_many_to_many(const char *id, CollectionType &, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) { + const auto it = schema_.find(id); 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}; + + auto producer = std::make_unique>( + schema_, + it->second.table(), + inverse_join_column, + root_type_, + join_column); + const object::collection_composite_key key{root_type_, typeid(typename CollectionType::value_type), inverse_join_column}; + schema_.collection_resolver_producers_[key] = std::move(producer); + } + + template + void on_has_many_to_many(const char *id, CollectionType &, const utils::foreign_attributes &/*attr*/) { + const auto it = schema_.find(id); + if (it == schema_.end()) { + throw query_builder_exception{query_build_error::UnknownType}; } + object::join_columns_collector collector; + const auto jc = collector.collect(); + + auto producer = std::make_unique>( + schema_, + it->second.table(), + jc.join_column, + root_type_, + jc.inverse_join_column); + const object::collection_composite_key key{root_type_, typeid(typename CollectionType::value_type), jc.join_column}; + schema_.collection_resolver_producers_[key] = std::move(producer); + } - template - static void on_has_many_to_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {} private: basic_schema& schema_; diff --git a/include/matador/sql/internal/query_result_impl.hpp b/include/matador/sql/internal/query_result_impl.hpp index d9f7a17..7e38182 100644 --- a/include/matador/sql/internal/query_result_impl.hpp +++ b/include/matador/sql/internal/query_result_impl.hpp @@ -18,6 +18,7 @@ #include "matador/object/object_proxy.hpp" #include "matador/object/object_ptr.hpp" #include "matador/object/collection_proxy.hpp" +#include "matador/object/join_columns_collector.hpp" #include #include @@ -112,26 +113,15 @@ public: on_foreign_key(x, attr); } - template - void on_has_many_to_many(const char *, ContainerType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &attr) { - if (attr.fetch() == utils::fetch_type::Lazy) { - - } else {} - } - - template - void on_has_many_to_many(const char *, ContainerType &, const utils::foreign_attributes &attr) { - if (attr.fetch() == utils::fetch_type::Lazy) { - - } else {} - } - 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); - template void on_has_many(const char * /*id*/, CollectionType &, const char * /*join_column*/, const utils::foreign_attributes &/*attr*/, std::enable_if_t::value> * = nullptr) { } + template + void on_has_many_to_many(const char *id, CollectionType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &attr); + template + void on_has_many_to_many(const char *, CollectionType &, const utils::foreign_attributes &attr); template void bind(const Type &obj) { @@ -186,7 +176,7 @@ protected: std::unordered_set initialized_collections_; }; -template +template void query_result_impl::on_has_many(const char *, CollectionType &cont, const char *join_column, const utils::foreign_attributes &attr, std::enable_if_t::value> *) { using value_type = typename CollectionType::value_type::value_type; auto object_resolver = resolver_->object_resolver(); @@ -212,6 +202,59 @@ void query_result_impl::on_has_many(const char *, CollectionType &cont, const ch } } +template +void query_result_impl::on_has_many_to_many(const char *id, CollectionType &cont, const char *join_column, const char *, const utils::foreign_attributes &attr) { + using value_type = typename CollectionType::value_type::value_type; + auto object_resolver = resolver_->object_resolver(); + auto resolver = resolver_->collection_resolver(result_type_, join_column); + if (attr.fetch() == utils::fetch_type::Lazy) { + cont.reset(std::make_shared>(resolver, current_pk_)); + } else { + if (initialized_collections_.insert({result_type_, typeid(typename CollectionType::value_type), std::string{id}}).second) { + cont.reset(std::make_shared>(resolver, std::vector())); + } + + const auto ti = std::type_index(typeid(value_type)); + type_stack_.push(ti); + auto obj = std::make_shared(); + access::process(*this, *obj); + type_stack_.pop(); + auto ptr = typename CollectionType::value_type(std::make_shared>(object_resolver, obj)); + const auto pk = ptr.primary_key(); + if (ptr.primary_key().is_valid()) { + cont.push_back(ptr); + } + } +} + +template +void query_result_impl::on_has_many_to_many(const char *id, CollectionType &cont, const utils::foreign_attributes &attr) { + using value_type = typename CollectionType::value_type::value_type; + object::join_columns_collector collector; + const auto jc = collector.collect(); + + auto object_resolver = resolver_->object_resolver(); + auto resolver = resolver_->collection_resolver(result_type_, jc.inverse_join_column); + if (attr.fetch() == utils::fetch_type::Lazy) { + cont.reset(std::make_shared>(resolver, current_pk_)); + } else { + if (initialized_collections_.insert({result_type_, typeid(typename CollectionType::value_type), std::string{id}}).second) { + cont.reset(std::make_shared>(resolver, std::vector())); + } + + const auto ti = std::type_index(typeid(value_type)); + type_stack_.push(ti); + auto obj = std::make_shared(); + access::process(*this, *obj); + type_stack_.pop(); + auto ptr = typename CollectionType::value_type(std::make_shared>(object_resolver, obj)); + const auto pk = ptr.primary_key(); + if (ptr.primary_key().is_valid()) { + cont.push_back(ptr); + } + } +} + template bool query_result_impl::fetch(Type &obj) { bool first = true; diff --git a/include/matador/sql/statement.hpp b/include/matador/sql/statement.hpp index cc71ab5..31e8382 100644 --- a/include/matador/sql/statement.hpp +++ b/include/matador/sql/statement.hpp @@ -187,10 +187,7 @@ utils::result, utils::error> statement::fetch_one() { }); auto first = records.begin(); if (first == records.end()) { - return utils::failure(utils::error{ - error_code::FETCH_FAILED, - "Failed to find entity." - }); + return utils::failure(utils::error{error_code::FETCH_FAILED,"Failed to find entity."}); } return utils::ok(first.optr()); @@ -198,6 +195,7 @@ utils::result, utils::error> statement::fetch_one() { template utils::result, utils::error> statement::fetch_one_raw() { + std::cout << statement_proxy_->sql() << std::endl; auto result = statement_proxy_->fetch(*bindings_); if (!result.is_ok()) { return utils::failure(result.err()); diff --git a/source/orm/sql/connection.cpp b/source/orm/sql/connection.cpp index 4861b42..beb0a04 100644 --- a/source/orm/sql/connection.cpp +++ b/source/orm/sql/connection.cpp @@ -47,7 +47,7 @@ connection::connection(const std::string& dns, const logger_ptr &sql_logger) connection::connection(const std::string &dns, const std::shared_ptr &resolver, const logger_ptr &sql_logger) -: connection(connection_info::parse(dns), std::move(resolver), sql_logger) +: connection(connection_info::parse(dns), resolver, sql_logger) {} connection::connection(const connection &x) { @@ -165,6 +165,7 @@ utils::result connection::exists(const std::string &table_na utils::result connection::execute(const std::string &sql) const { logger_->on_execute(sql); + std::cout << sql << std::endl; return connection_->execute(sql); } @@ -203,6 +204,7 @@ bool has_unknown_columns(const std::vector &columns) { utils::result, utils::error> connection::fetch(const query_context &ctx) const { logger_->on_fetch(ctx.sql); + std::cout << ctx.sql << std::endl; return connection_->fetch(ctx); } diff --git a/test/backends/QueryTest.cpp b/test/backends/QueryTest.cpp index 3b9b77e..adca692 100644 --- a/test/backends/QueryTest.cpp +++ b/test/backends/QueryTest.cpp @@ -821,6 +821,10 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel .and_then( [this] { return repo.attach("recipes"); } ) .and_then([this] {return repo.create(db); }); + REQUIRE(result.is_ok()); + + repo.initialize_executor(db); + REQUIRE(db.exists(RECIPE.table_name())); REQUIRE(db.exists(INGREDIENT.table_name())); REQUIRE(db.exists(RECIPE_INGREDIENT.table_name())); @@ -845,9 +849,9 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel } std::vector recipes{ - {7, "Apple Crumble"}, - {8, "Beans Chili"}, - {9, "Fruit Salad"} + {8, "Apple Crumble"}, + {9, "Beans Chili"}, + {10, "Fruit Salad"} }; for (const auto &r: recipes) { @@ -860,14 +864,14 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel } std::vector> recipe_ingredients { - { 7, 1 }, - { 7, 4 }, - { 7, 5 }, - { 8, 6 }, - { 8, 7 }, - { 9, 1 }, - { 9, 2 }, - { 9, 3 } + { 8, 1 }, + { 8, 4 }, + { 8, 5 }, + { 9, 6 }, + { 9, 7 }, + { 10, 1 }, + { 10, 2 }, + { 10, 3 } }; for (const auto & [recipe_id, ingredient_id]: recipe_ingredients) { @@ -883,15 +887,31 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel 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}) + auto recipes_result = query::select({r.id, r.name }) .from(r) - .join_left(ri).on(r.id == ri.recipe_id) - .join_left(i).on(ri.ingredient_id == i.id) + .order_by({r.id}).asc() + .fetch_all(db); + REQUIRE(recipes_result.is_ok()); + + for (const auto &reps : *recipes_result) { + REQUIRE(!reps->ingredients.empty()); + for (const auto &ingr : reps->ingredients) { + std::cout << ingr->name << " (" << reps->name << ")" << std::endl; + } + } + + auto ingredients_result = query::select({i.id, i.name, ri.recipe_id, r.name}) + .from(i) + .join_left(ri).on(i.id == ri.ingredient_id) + .join_left(r).on(ri.recipe_id == r.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()); + for (const auto &ingr : *ingredients_result) { + REQUIRE(!ingr->recipes.empty()); + for (const auto &recipe : ingr->recipes) { + std::cout << recipe->name << " (" << ingr->name << ")" << std::endl; + } } } \ No newline at end of file diff --git a/test/models/recipe.hpp b/test/models/recipe.hpp index ac7d318..efc4f10 100644 --- a/test/models/recipe.hpp +++ b/test/models/recipe.hpp @@ -5,6 +5,7 @@ #include "matador/utils/foreign_attributes.hpp" #include "matador/object/object_ptr.hpp" +#include "matador/object/collection.hpp" #include "matador/object/many_to_many_relation.hpp" #include @@ -15,7 +16,7 @@ struct recipe; struct ingredient { unsigned int id{}; std::string name; - std::vector > recipes{}; + object::collection > recipes{}; ingredient() = default; @@ -35,7 +36,7 @@ struct ingredient { struct recipe { unsigned int id{}; std::string name; - std::vector > ingredients{}; + object::collection > ingredients{}; recipe() = default;