diff --git a/include/matador/orm/session_query_builder.hpp b/include/matador/orm/session_query_builder.hpp index cd6038b..64026e8 100644 --- a/include/matador/orm/session_query_builder.hpp +++ b/include/matador/orm/session_query_builder.hpp @@ -26,43 +26,41 @@ class join_column_collector { public: template - join_columns collect() - { + join_columns collect() { join_columns_ = {}; Type obj; - matador::access::process(*this, obj); + 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*/) {} + void on_primary_key(const char * /*id*/, V &, std::enable_if_t && !std::is_same_v>* = nullptr) {} + static void on_primary_key(const char * /*id*/, std::string &, size_t) {} + static 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) {} + static 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) {} + static 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) {} + static 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) {} + static 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*/) - { + 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 = join_column; join_columns_.inverse_join_column = inverse_join_column; } template - void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {} + static void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {} private: join_columns join_columns_; }; struct entity_query_data { - std::string root_table_name; - std::string pk_column_{}; + std::shared_ptr root_table; + std::string pk_column_name{}; std::vector columns{}; std::vector joins{}; std::unique_ptr where_clause{}; @@ -100,10 +98,10 @@ public: } pk_ = pk; table_info_stack_.push(info.value()); - processed_tables_.insert(info->get().name()); - entity_query_data_ = { info.value().get().name() }; + entity_query_data_ = { std::make_shared(info.value().get().name(), build_alias('t', ++table_index)) }; + processed_tables_.insert({info->get().name(), entity_query_data_.root_table}); try { - access::process(*this, info.value().get().prototype()); + access::process(*this, info->get().prototype()); return {utils::ok(std::move(entity_query_data_))}; } catch (const query_builder_exception &ex) { @@ -121,7 +119,8 @@ public: } pk_ = nullptr; table_info_stack_.push(info.value()); - entity_query_data_ = { info->get().name() }; + entity_query_data_ = { std::make_shared(info.value().get().name(), build_alias('t', ++table_index)) }; + processed_tables_.insert({info->get().name(), entity_query_data_.root_table}); try { access::process(*this, info->get().prototype()); @@ -141,15 +140,14 @@ public: return; } if (pk_.is_null()) { - entity_query_data_.pk_column_ = id; + entity_query_data_.pk_column_name = id; } else if (pk_.is_integer()) { - auto t = std::make_shared(table_info_stack_.top().get().name()); + const auto t = std::make_shared(table_info_stack_.top().get().name()); auto v = *pk_.as(); auto c = sql::column{t, id, ""}; auto co = std::make_unique>(c, query::basic_condition::operand_type::EQUAL, v); entity_query_data_.where_clause = std::move(co); - // entity_query_data_.where_clause = query::make_condition(c == v); - entity_query_data_.pk_column_ = id; + entity_query_data_.pk_column_name = id; } } @@ -182,13 +180,16 @@ public: throw query_builder_exception{query_build_error::UnknownType}; } - auto curr = table_info_stack_.top().get().name(); - auto next = info.value().get().name(); - if (processed_tables_.count(next) > 0) { + const auto curr = processed_tables_.find(table_info_stack_.top().get().name()); + if (curr == processed_tables_.end()) { + throw query_builder_exception{query_build_error::UnexpectedError}; + }; + auto next = processed_tables_.find(info->get().name()); + if (next != processed_tables_.end()) { return; } table_info_stack_.push(info.value()); - processed_tables_.insert(next); + next = processed_tables_.insert({info->get().name(), std::make_shared(info->get().name(), build_alias('t', ++table_index))}).first; typename ContainerType::value_type::value_type obj; access::process(*this , obj); table_info_stack_.pop(); @@ -199,14 +200,14 @@ public: } append_join( - sql::column{std::make_shared(table_info_stack_.top().get().name()), table_info_stack_.top().get().definition().primary_key()->name()}, - sql::column{std::make_shared(info->get().name()), join_column} + sql::column{curr->second, table_info_stack_.top().get().definition().primary_key()->name()}, + sql::column{next->second, join_column} ); } } 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) + void on_has_many_to_many(const char *id, ContainerType &/*cont*/, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) { if (attr.fetch() != utils::fetch_type::EAGER) { return; @@ -217,7 +218,7 @@ public: } table_info_stack_.push(info.value()); typename ContainerType::value_type::value_type obj; - matador::access::process(*this , obj); + access::process(*this , obj); table_info_stack_.pop(); auto pk = info->get().definition().primary_key(); @@ -236,7 +237,7 @@ public: } 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 &/*cont*/, const utils::foreign_attributes &attr) { if (attr.fetch() != utils::fetch_type::EAGER) { return; @@ -247,7 +248,7 @@ public: } table_info_stack_.push(info.value()); typename ContainerType::value_type::value_type obj; - matador::access::process(*this , obj); + access::process(*this , obj); table_info_stack_.pop(); auto pk = info->get().definition().primary_key(); @@ -271,16 +272,18 @@ private: template void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr); void push(const std::string &column_name); + static std::string build_alias(char prefix, unsigned int count); [[nodiscard]] bool is_root_entity() const; void append_join(const sql::column &left, const sql::column &right); private: utils::value pk_; std::stack> table_info_stack_; - std::unordered_set processed_tables_; + std::unordered_map> processed_tables_; const object::schema &schema_; entity_query_data entity_query_data_; - int column_index{0}; + unsigned int column_index{0}; + unsigned int table_index{0}; join_column_collector join_column_collector_; }; @@ -292,12 +295,16 @@ void session_query_builder::on_foreign_object(const char *id, Pointer &, const u if (!info) { throw query_builder_exception{query_build_error::UnknownType}; } - auto curr = table_info_stack_.top().get().name(); - auto next = info.value().get().name(); - if (processed_tables_.count(next) > 0) { + + const auto curr = processed_tables_.find(table_info_stack_.top().get().name()); + if (curr == processed_tables_.end()) { + throw query_builder_exception{query_build_error::UnexpectedError}; + }; + auto next = processed_tables_.find(info->get().name()); + if (next != processed_tables_.end()) { return; } - processed_tables_.insert(next); + next = processed_tables_.insert({info->get().name(), std::make_shared(info->get().name(), build_alias('t', ++table_index))}).first; table_info_stack_.push(info.value()); typename Pointer::value_type obj; access::process(*this, obj); @@ -308,8 +315,8 @@ void session_query_builder::on_foreign_object(const char *id, Pointer &, const u throw query_builder_exception{query_build_error::MissingPrimaryKey}; } append_join( - sql::column{std::make_shared(table_info_stack_.top().get().name()), id}, - sql::column{std::make_shared(info->get().name()), pk->name()} + sql::column{curr->second, id}, + sql::column{next->second, pk->name()} ); } else { push(id); diff --git a/include/matador/sql/column.hpp b/include/matador/sql/column.hpp index 87a86a1..4b73d24 100644 --- a/include/matador/sql/column.hpp +++ b/include/matador/sql/column.hpp @@ -23,7 +23,7 @@ struct column column(const char *name, const std::string& as = ""); // NOLINT(*-explicit-constructor) explicit column(std::string name, std::string as = ""); // NOLINT(*-explicit-constructor) column(sql_function_t func, std::string name); // NOLINT(*-explicit-constructor) - column(const struct table &t, std::string name, std::string as = ""); + column(const struct table &tab, std::string name, std::string as = ""); column(const std::shared_ptr &t, std::string name, std::string as = ""); [[nodiscard]] bool equals(const column &x) const; diff --git a/source/orm/orm/session.cpp b/source/orm/orm/session.cpp index be39c86..8e8f51c 100644 --- a/source/orm/orm/session.cpp +++ b/source/orm/orm/session.cpp @@ -126,10 +126,10 @@ const class sql::dialect &session::dialect() const query::fetchable_query session::build_select_query(entity_query_data &&data) { return query::query::select(data.columns) - .from(data.root_table_name) + .from(*data.root_table) .join_left(data.joins) .where(std::move(data.where_clause)) - .order_by(sql::column{data.root_table_name, data.pk_column_}) + .order_by(sql::column{data.root_table, data.pk_column_name}) .asc(); } diff --git a/source/orm/orm/session_query_builder.cpp b/source/orm/orm/session_query_builder.cpp index ef9de59..43fde1b 100644 --- a/source/orm/orm/session_query_builder.cpp +++ b/source/orm/orm/session_query_builder.cpp @@ -3,39 +3,42 @@ #include namespace matador::orm { - -void session_query_builder::on_primary_key(const char *id, std::string &, size_t) -{ - push(id); - if (!is_root_entity()) { - const auto b = pk_.is_varchar(); - std::cout << "is matching primary key: " << std::boolalpha << b << "\n"; - } +void session_query_builder::on_primary_key(const char *id, std::string &, size_t) { + push(id); + if (!is_root_entity()) { + const auto b = pk_.is_varchar(); + std::cout << "is matching primary key: " << std::boolalpha << b << "\n"; + } } -void session_query_builder::on_revision(const char *id, unsigned long long &/*rev*/) -{ - push(id); +void session_query_builder::on_revision(const char *id, unsigned long long &/*rev*/) { + push(id); } -void session_query_builder::push(const std::string &column_name) -{ - char str[4]; - snprintf(str, 4, "c%02d", ++column_index); - entity_query_data_.columns.emplace_back(table_info_stack_.top().get().name(), column_name, str); +void session_query_builder::push(const std::string &column_name) { + const auto it = processed_tables_.find(table_info_stack_.top().get().name()); + if (it == processed_tables_.end()) { + throw query_builder_exception{query_build_error::UnexpectedError}; + } + entity_query_data_.columns.emplace_back(it->second, column_name, build_alias('c', ++column_index)); +} + +std::string session_query_builder::build_alias(const char prefix, const unsigned int count) { + char str[4]; + snprintf(str, 4, "%c%02d", prefix, count); + + return str; } [[nodiscard]] bool session_query_builder::is_root_entity() const { - return table_info_stack_.size() == 1; + return table_info_stack_.size() == 1; } -void session_query_builder::append_join(const sql::column &left, const sql::column &right) -{ - using namespace matador::query; - entity_query_data_.joins.push_back({ - { right.table_ }, - make_condition(left == right) - }); +void session_query_builder::append_join(const sql::column &left, const sql::column &right) { + using namespace matador::query; + entity_query_data_.joins.push_back({ + {right.table_}, + make_condition(left == right) + }); +} } - -} \ No newline at end of file diff --git a/source/orm/query/query_compiler.cpp b/source/orm/query/query_compiler.cpp index c4d08eb..c2bf3c4 100644 --- a/source/orm/query/query_compiler.cpp +++ b/source/orm/query/query_compiler.cpp @@ -77,7 +77,7 @@ void query_compiler::visit(internal::query_select_part &select_part) void query_compiler::visit(internal::query_from_part &from_part) { query_.table = from_part.table(); - query_.sql += " " + query_compiler::build_table_name(from_part.token(), *dialect_, query_.table); + query_.sql += " " + build_table_name(from_part.token(), *dialect_, query_.table); query_.table_aliases.insert({query_.table.name, query_.table.alias}); } diff --git a/source/orm/sql/column.cpp b/source/orm/sql/column.cpp index 543e66b..128945f 100644 --- a/source/orm/sql/column.cpp +++ b/source/orm/sql/column.cpp @@ -24,8 +24,8 @@ column::column(const sql_function_t func, std::string name) , name(std::move(name)) , function_(func) {} -column::column(const struct sql::table& t, std::string name, std::string as) -: table_(std::make_shared(t)) +column::column(const table& tab, std::string name, std::string as) +: table_(std::make_shared
(tab)) , name(std::move(name)) , alias(std::move(as)) { table_->columns.push_back(*this); diff --git a/test/models/book.hpp b/test/models/book.hpp index 3800c10..e2155d1 100644 --- a/test/models/book.hpp +++ b/test/models/book.hpp @@ -19,8 +19,7 @@ struct book { unsigned short published_in{}; template - void process(Operator &op) - { + void process(Operator &op) { namespace field = matador::access; field::primary_key(op, "id", id); field::attribute(op, "title", title, 511); diff --git a/test/orm/orm/SessionQueryBuilderTest.cpp b/test/orm/orm/SessionQueryBuilderTest.cpp index 152233b..e2f97fa 100644 --- a/test/orm/orm/SessionQueryBuilderTest.cpp +++ b/test/orm/orm/SessionQueryBuilderTest.cpp @@ -1,7 +1,10 @@ +#include #include #include "matador/sql/backend_provider.hpp" #include "matador/sql/connection.hpp" +#include "matador/sql/column.hpp" +#include "matador/sql/table.hpp" #include "matador/query/query.hpp" @@ -20,6 +23,7 @@ using namespace matador::object; using namespace matador::orm; +using namespace matador::query; using namespace matador::sql; using namespace matador::test; @@ -36,7 +40,7 @@ TEST_CASE("Create sql query data for entity with eager has one", "[query][entity auto data = eqb.build(17U); REQUIRE(data.is_ok()); - REQUIRE(data->root_table_name == "flights"); + REQUIRE(data->root_table->name == "flights"); REQUIRE(data->joins.size() == 1); const std::vector expected_columns { { "flights", "id", "c01" }, @@ -51,7 +55,7 @@ TEST_CASE("Create sql query data for entity with eager has one", "[query][entity } std::vector> expected_join_data { - { "airplanes", R"("flights"."airplane_id" = "airplanes"."id")"} + { "airplanes", R"("t01"."airplane_id" = "t02"."id")"} }; query_context qc; @@ -81,7 +85,7 @@ TEST_CASE("Create sql query data for entity with eager belongs to", "[query][ent auto data = eqb.build(17); REQUIRE(data.is_ok()); - REQUIRE(data->root_table_name == "books"); + REQUIRE(data->root_table->name == "books"); REQUIRE(data->joins.size() == 1); const std::vector expected_columns { { "books", "id", "c01" }, @@ -100,7 +104,7 @@ TEST_CASE("Create sql query data for entity with eager belongs to", "[query][ent } std::vector> expected_join_data { - { "authors", R"("books"."author_id" = "authors"."id")"} + { "authors", R"("t01"."author_id" = "t02"."id")"} }; query_context qc; @@ -116,7 +120,7 @@ TEST_CASE("Create sql query data for entity with eager belongs to", "[query][ent REQUIRE(cond == R"("books"."id" = 17)"); auto q = matador::query::query::select(data->columns) - .from(data->root_table_name); + .from(data->root_table->name); for (auto &jd : data->joins) { q.join_left(*jd.join_table) @@ -143,7 +147,7 @@ TEST_CASE("Create sql query data for entity with eager has many belongs to", "[q auto data = eqb.build(17); REQUIRE(data.is_ok()); - REQUIRE(data->root_table_name == "orders"); + REQUIRE(data->root_table->name == "orders"); REQUIRE(data->joins.size() == 1); const std::vector expected_columns = { { "orders", "order_id", "c01" }, @@ -168,7 +172,7 @@ TEST_CASE("Create sql query data for entity with eager has many belongs to", "[q } std::vector> expected_join_data { - { "order_details", R"("orders"."order_id" = "order_details"."order_id")"} + { "order_details", R"("t01"."order_id" = "t02"."order_id")"} }; query_context qc; @@ -197,7 +201,7 @@ TEST_CASE("Create sql query data for entity with eager many to many", "[query][e auto data = eqb.build(17); REQUIRE(data.is_ok()); - REQUIRE(data->root_table_name == "ingredients"); + REQUIRE(data->root_table->name == "ingredients"); REQUIRE(data->joins.size() == 2); const std::vector expected_columns { { "ingredients", "id", "c01" }, @@ -241,7 +245,7 @@ TEST_CASE("Create sql query data for entity with eager many to many (inverse par auto data = eqb.build(17); REQUIRE(data.is_ok()); - REQUIRE(data->root_table_name == "courses"); + REQUIRE(data->root_table->name == "courses"); REQUIRE(data->joins.size() == 2); const std::vector expected_columns { { "courses", "id", "c01" }, @@ -270,4 +274,64 @@ TEST_CASE("Create sql query data for entity with eager many to many (inverse par REQUIRE(data->where_clause); auto cond = data->where_clause->evaluate(db.dialect(), qc); REQUIRE(cond == R"("courses"."id" = 17)"); +} + +namespace matador::test::orm { + +struct employee; + +struct department { + unsigned int id{}; + std::string name; + std::vector> employees; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 63); + field::has_many(op, "employees", employees, "dep_id", utils::fetch_type::EAGER); + } +}; + +struct employee { + unsigned int id{}; + std::string first_name; + std::string last_name; + object_ptr dep; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "first_name", first_name, 63); + field::attribute(op, "last_name", last_name, 63); + field::belongs_to(op, "dep_id", dep, utils::fetch_type::EAGER); + } +}; + +} + +TEST_CASE("Test eager relationship", "[session][eager]") { + using namespace matador::test; + backend_provider::instance().register_backend("noop", std::make_unique()); + connection db("noop://noop.db"); + schema scm("noop"); + auto result = scm.attach("departments") + .and_then( [&scm] { return scm.attach("employees"); } ); + + session_query_builder eqb(scm); + + auto data = eqb.build(); + REQUIRE(data.is_ok()); + + auto ctx = query::select(data->columns) + .from(*data->root_table) + .join_left(data->joins) + .where(std::move(data->where_clause)) + .order_by(column{data->root_table, data->pk_column_name}) + .asc() + .str(db); + + std::cout << ctx << std::endl; } \ No newline at end of file