diff --git a/include/matador/sql/entity_query_builder.hpp b/include/matador/sql/entity_query_builder.hpp index 914fe09..2ae7149 100644 --- a/include/matador/sql/entity_query_builder.hpp +++ b/include/matador/sql/entity_query_builder.hpp @@ -14,6 +14,49 @@ namespace matador::sql { +struct join_columns +{ + std::string join_column; + std::string inverse_join_column; +}; + +class join_column_collector +{ +public: + template + join_columns collect() + { + join_columns_ = {}; + Type obj; + + matador::utils::access::process(*this, obj); + + return join_columns_; + } + template < class V > + void on_primary_key(const char * /*id*/, V &, typename std::enable_if::value && !std::is_same::value>::type* = 0) {} + void on_primary_key(const char * /*id*/, std::string &, size_t) {} + void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {} + template + void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + template + void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} + template + void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} + template + void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr) {} + template + void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) + { + join_columns_.join_column = inverse_join_column; + join_columns_.inverse_join_column = join_column; + } + template + void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {} + +private: + join_columns join_columns_; +}; struct join_data { table join_table; @@ -148,28 +191,52 @@ public: template void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) { - if (attr.fetch() == utils::fetch_type::EAGER) { - const auto info = schema_.info(); - if (!info) { - throw query_builder_exception{query_build_error::UnknownType}; - } - table_info_stack_.push(info.value()); - typename ContainerType::value_type::value_type obj; - matador::utils::access::process(*this , obj); - table_info_stack_.pop(); - - auto pk = info->prototype.primary_key(); - if (!pk) { - throw query_builder_exception{query_build_error::MissingPrimaryKey}; - } - - append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_column}); - append_join({info->name, pk->name()}, {id, inverse_join_column}); + if (attr.fetch() != utils::fetch_type::EAGER) { + return; } + const auto info = schema_.info(); + if (!info) { + throw query_builder_exception{query_build_error::UnknownType}; + } + table_info_stack_.push(info.value()); + typename ContainerType::value_type::value_type obj; + matador::utils::access::process(*this , obj); + table_info_stack_.pop(); + + auto pk = info->prototype.primary_key(); + if (!pk) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + + append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_column}); + append_join({info->name, pk->name()}, {id, inverse_join_column}); } template - void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr) + { + if (attr.fetch() != utils::fetch_type::EAGER) { + return; + } + const auto info = schema_.info(); + if (!info) { + throw query_builder_exception{query_build_error::UnknownType}; + } + table_info_stack_.push(info.value()); + typename ContainerType::value_type::value_type obj; + matador::utils::access::process(*this , obj); + table_info_stack_.pop(); + + auto pk = info->prototype.primary_key(); + if (!pk) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + + const auto join_columns = join_column_collector_.collect(); + + append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_columns.join_column}); + append_join({info->name, pk->name()}, {id, join_columns.inverse_join_column}); + } private: template @@ -184,6 +251,7 @@ private: const schema &schema_; entity_query_data entity_query_data_; int column_index{0}; + join_column_collector join_column_collector_; }; template diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c4aa1de..2f823df 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,7 @@ add_executable(tests BackendProviderTest.cpp models/product.hpp models/order.hpp - models/order_details.h + models/order_details.hpp ColumnDefinitionGeneratorTest.cpp ColumnGeneratorTest.cpp ValueGeneratorTest.cpp @@ -42,7 +42,8 @@ add_executable(tests ValueTest.cpp ResultTest.cpp utils/auto_reset_event.hpp - utils/auto_reset_event.cpp) + utils/auto_reset_event.cpp + models/student.hpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain diff --git a/test/EntityQueryBuilderTest.cpp b/test/EntityQueryBuilderTest.cpp index 71fc1ae..22a748d 100644 --- a/test/EntityQueryBuilderTest.cpp +++ b/test/EntityQueryBuilderTest.cpp @@ -7,6 +7,7 @@ #include "models/book.hpp" #include "models/recipe.hpp" #include "models/order.hpp" +#include "models/student.hpp" using namespace matador::sql; @@ -165,4 +166,48 @@ TEST_CASE("Create sql query data for entity with eager many to many", "[query][e REQUIRE(data->where_clause); auto cond = data->where_clause->evaluate(db.dialect(), qc); REQUIRE(cond == R"("ingredients"."id" = 17)"); +} + +TEST_CASE("Create sql query data for entity with eager many to many (inverse part)", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + scm.attach("students"); + scm.attach("courses"); + scm.attach("student_courses"); + + entity_query_builder eqb(scm); + + auto data = eqb.build(17); + + REQUIRE(data.is_ok()); + REQUIRE(data->root_table_name == "courses"); + REQUIRE(data->joins.size() == 2); + const std::vector expected_columns { + { "courses", "id", "c01" }, + { "courses", "title", "c02" }, + { "students", "id", "c03" }, + { "students", "name", "c04" } + }; + REQUIRE(data->columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(data->columns[i])); + } + + std::vector> expected_join_data { + { "courses", R"("courses"."id" = "student_courses"."course_id")"}, + { "students", R"("students"."id" = "student_courses"."student_id")"} + }; + + query_context qc; + size_t index{0}; + for (const auto &jd : data->joins) { + REQUIRE(jd.join_table.name == expected_join_data[index].first); + REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second); + ++index; + } + + REQUIRE(data->where_clause); + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == R"("courses"."id" = 17)"); } \ No newline at end of file diff --git a/test/models/order.hpp b/test/models/order.hpp index 0fe9536..64d5f12 100644 --- a/test/models/order.hpp +++ b/test/models/order.hpp @@ -1,7 +1,7 @@ #ifndef QUERY_ORDER_HPP #define QUERY_ORDER_HPP -#include "order_details.h" +#include "order_details.hpp" #include "matador/utils/access.hpp" diff --git a/test/models/order_details.h b/test/models/order_details.hpp similarity index 85% rename from test/models/order_details.h rename to test/models/order_details.hpp index 04be995..3a7dba0 100644 --- a/test/models/order_details.h +++ b/test/models/order_details.hpp @@ -1,5 +1,5 @@ -#ifndef QUERY_ORDER_DETAILS_H -#define QUERY_ORDER_DETAILS_H +#ifndef QUERY_ORDER_DETAILS_HPP +#define QUERY_ORDER_DETAILS_HPP #include "product.hpp" @@ -25,4 +25,4 @@ struct order_details }; } -#endif //QUERY_ORDER_DETAILS_H +#endif //QUERY_ORDER_DETAILS_HPP diff --git a/test/models/student.hpp b/test/models/student.hpp new file mode 100644 index 0000000..b1b07a4 --- /dev/null +++ b/test/models/student.hpp @@ -0,0 +1,68 @@ +#ifndef QUERY_STUDENT_HPP +#define QUERY_STUDENT_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include "matador/sql/entity.hpp" +#include "matador/sql/has_many_to_many_relation.hpp" + +#include +#include + +namespace matador::test { + +class course; + +struct student +{ + unsigned long id{}; + std::string name; + std::vector> courses; + + student() = default; + explicit student(unsigned long id, std::string name) + : id(id) + , name(std::move(name)) {} + + template < class Operator > + void process(Operator &op) + { + namespace field = matador::utils::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + field::has_many_to_many(op, "student_courses", courses, "student_id", "course_id", utils::fetch_type::LAZY); + } + +}; + +struct course +{ + unsigned long id{}; + std::string title; + std::vector> students; + + course() = default; + explicit course(unsigned long id, std::string title) + : id(id) + , title(std::move(title)) {} + + template < class Operator > + void process(Operator &op) + { + namespace field = matador::utils::access; + field::primary_key(op, "id", id); + field::attribute(op, "title", title, 255); + field::has_many_to_many(op, "student_courses", students, utils::fetch_type::EAGER); + } +}; + +class student_course : public sql::has_many_to_many_relation +{ +public: + student_course() + : has_many_to_many_relation("student_id", "course_id") {} +}; + +} +#endif //QUERY_STUDENT_HPP