diff --git a/Todo.md b/Todo.md index de3b18e..6955f58 100644 --- a/Todo.md +++ b/Todo.md @@ -3,4 +3,45 @@ - Add is_valid() method to connection & connection_impl - Read in entity fields - Add special handling for update in backends -- Add ODBC/SQL Server backend \ No newline at end of file +- Add ODBC/SQL Server backend + + +Fetch eager strategies +====================== + +ONE TO ONE/MANY +*person* *address* +- has one address - belongs to person + +=> join "address" on "person.id" == "address.person_id" + +*address* *person* +- belongs to person - has one address + +=> join "person" on "address.person_id" == "person.id" + +*book* *author* +- belongs to author - has many books + +- => join "author" on "book.author_id" == "author.id" + +HAS MANY TO ONE (WITHOUT RELATION TABLE) + +*author* *book* +- has many books - belongs to author + +- => join "book" on "author.id" == "book.author_id" + +if "has many" type has primary key & field "author_id" +if table name belongs to entity template type? + +HAS MANY TO MANY (WITHOUT RELATION TABLE) + +*student* *student_course* *course* +- has many courses - belongs to student + - belongs to course - has many students + +=> join "student_course" on "student.id" == "student_course.student_id" + join "student_course" on "course.id" == "student_course.course_id" + +if has many type hasn't primary key (is relation table) diff --git a/include/matador/sql/column_definition_generator.hpp b/include/matador/sql/column_definition_generator.hpp index 9b389d5..b026330 100644 --- a/include/matador/sql/column_definition_generator.hpp +++ b/include/matador/sql/column_definition_generator.hpp @@ -42,9 +42,11 @@ public: template void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {} template - void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {} + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} template - void on_has_many(const char *, ContainerType &, 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*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} private: data_type_t type_{}; @@ -92,9 +94,11 @@ public: columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column)); } template - void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {} + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} template - void on_has_many(const char *, ContainerType &, 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*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} private: std::pair determine_foreign_ref(const std::type_index &ti); diff --git a/include/matador/sql/column_generator.hpp b/include/matador/sql/column_generator.hpp index 827b44a..ec0e47b 100644 --- a/include/matador/sql/column_generator.hpp +++ b/include/matador/sql/column_generator.hpp @@ -86,7 +86,7 @@ public: } } template - void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr) + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &attr) { if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { return; @@ -101,10 +101,15 @@ public: matador::utils::access::process(*this, obj); table_name_stack_.pop(); } + template - void on_has_many(const char *id, ContainerType &c, 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) + { + } + + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr) { - on_has_many(id, c, "", "", attr); } private: diff --git a/include/matador/sql/entity_query_builder.hpp b/include/matador/sql/entity_query_builder.hpp index ea3e825..31cb661 100644 --- a/include/matador/sql/entity_query_builder.hpp +++ b/include/matador/sql/entity_query_builder.hpp @@ -2,6 +2,7 @@ #define QUERY_ENTITY_QUERY_BUILDER_HPP #include "matador/sql/connection.hpp" +#include "matador/sql/condition.hpp" #include "matador/sql/query_context.hpp" #include "matador/sql/query.hpp" #include "matador/sql/query_intermediates.hpp" @@ -10,6 +11,19 @@ namespace matador::sql { +struct join_data +{ + table join_table; + std::unique_ptr condition; +}; + +struct entity_query_data { + std::string root_table_name; + std::vector columns; + std::vector joins; + std::unique_ptr where_clause; +}; + template < typename PrimaryKeyType > class entity_query_builder { @@ -18,95 +32,172 @@ public: : pk_(pk) , schema_(scm) {} - // determine pk - // collect eager relations for joins template - std::optional build(connection &db) { - EntityType obj; - matador::utils::access::process(*this, obj); - + std::optional build() { const auto info = schema_.info(); if (!info) { return std::nullopt; } - columns_.clear(); - table_name_ = info.value().name; - query q(db, schema_); - return q.select(column_generator::generate(schema_)).from({table_name_}).build(); -// auto from_intermediate = q.select().from({"t"}); -// return {}; + table_info_stack_.push(info.value()); + entity_query_data_ = { info->name }; + EntityType obj; + matador::utils::access::process(*this, obj); + + return std::move(entity_query_data_); } template < class V > void on_primary_key(const char *id, V &, typename std::enable_if::value && !std::is_same::value>::type* = 0) { - auto b = std::is_integral_v; - std::cout << "is matching primary key: " << std::boolalpha << b << "\n"; - columns_.emplace_back(table_name_, id, ""); + push(id); + if (is_root_entity() && std::is_integral_v) { + entity_query_data_.where_clause = make_condition(column{table_info_stack_.top().name, id, ""} == pk_); + } } void on_primary_key(const char *id, std::string &, size_t) { - auto b = std::is_same_v; - std::cout << "is matching primary key: " << std::boolalpha << b << "\n"; - columns_.emplace_back(table_name_, id, ""); + push(id); + if (!is_root_entity()) { + auto b = std::is_same_v; + std::cout << "is matching primary key: " << std::boolalpha << b << "\n"; + } } void on_revision(const char *id, unsigned long long &/*rev*/) { - columns_.emplace_back(table_name_, id, ""); + push(id); } template void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) { - columns_.emplace_back(table_name_, id, ""); + push(id); } template void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr) { if (attr.fetch() == utils::fetch_type::EAGER) { - column col{id}; + const auto info = schema_.info(); + if (!info) { + return; + } + table_info_stack_.push(info.value()); + typename Pointer::value_type obj; + matador::utils::access::process(*this , obj); + table_info_stack_.pop(); - // collect join information (determine primary key of foreign entity) + auto pk = info->prototype.primary_key(); + if (!pk) { + // error, prototype doesn't has a pk + return; + } + append_join({table_info_stack_.top().name, id}, {info->name, pk->name()}); + } else { + push(id); } - columns_.emplace_back(table_name_, id, ""); } template void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr) { if (attr.fetch() == utils::fetch_type::EAGER) { - auto info = schema_.info(); - if (info) { - column lcol(table_name_, id, ""); -// column rcol(info.value()) + const auto info = schema_.info(); + if (!info) { + return; } - // collect join information + table_info_stack_.push(info.value()); + typename Pointer::value_type obj; + matador::utils::access::process(*this, obj); + table_info_stack_.pop(); + + auto pk = info->prototype.primary_key(); + if (!pk) { + // error, prototype doesn't has a pk + return; + } + append_join({table_info_stack_.top().name, id}, {info->name, pk->name()}); + } else { + push(id); } - columns_.emplace_back(table_name_, id, ""); } template - void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr) + void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr) { if (attr.fetch() == utils::fetch_type::EAGER) { - // collect join information + const auto info = schema_.info(); + if (!info) { + return; + } + 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) { + // error, prototype doesn't has a pk + return; + } + + append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {info->name, join_column}); } } template - void on_has_many(const char *id, ContainerType &c, 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) { - on_has_many(id, c, "", "", attr); + if (attr.fetch() == utils::fetch_type::EAGER) { + const auto info = schema_.info(); + if (!info) { + return; + } + 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) { + // error, prototype doesn't has a pk + return; + } + + 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*/) {} + +private: + void 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().name, column_name, str); + } + + [[nodiscard]] bool is_root_entity() const { + return table_info_stack_.size() == 1; + } + + void append_join(const column &left, const column &right) + { + entity_query_data_.joins.push_back({ + { left.table }, + make_condition(left == right) + }); + } private: PrimaryKeyType pk_; - std::vector columns_; + std::stack table_info_stack_; const schema &schema_; - std::string table_name_; + entity_query_data entity_query_data_; + int column_index{0}; }; } diff --git a/include/matador/sql/has_many_to_many_relation.hpp b/include/matador/sql/has_many_to_many_relation.hpp new file mode 100644 index 0000000..7597192 --- /dev/null +++ b/include/matador/sql/has_many_to_many_relation.hpp @@ -0,0 +1,38 @@ +#ifndef QUERY_HAS_MANY_TO_MANY_RELATION_HPP +#define QUERY_HAS_MANY_TO_MANY_RELATION_HPP + +#include "matador/sql/entity.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +namespace matador::sql { + +template < class LocalType, class ForeignType > +class has_many_to_many_relation +{ +public: + has_many_to_many_relation() = default; + has_many_to_many_relation(std::string local_name, std::string remote_name) + : local_name_(std::move(local_name)) + , remote_name_(std::move(remote_name)) {} + + template + void process(Operator &op) { + namespace field = matador::utils::access; + field::belongs_to(op, local_name_.c_str(), local_, utils::default_foreign_attributes); + field::belongs_to(op, remote_name_.c_str(), remote_, utils::default_foreign_attributes); + } + + entity local() const { return local_; } + entity remote() const { return remote_; } + +private: + std::string local_name_; + std::string remote_name_; + sql::entity local_; + sql::entity remote_; +}; + +} +#endif //QUERY_HAS_MANY_TO_MANY_RELATION_HPP diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 8e66461..eb29b52 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -161,6 +161,10 @@ public: { return where_clause(std::make_unique(std::move(cond))); } + query_where_intermediate where(std::unique_ptr &&cond) + { + return where_clause(std::move(cond)); + } query_group_by_intermediate group_by(const column &col); query_order_by_intermediate order_by(const column &col); @@ -178,6 +182,10 @@ public: { return on_clause(std::make_unique(std::move(cond))); } + query_on_intermediate on(std::unique_ptr &&cond) + { + return on_clause(std::move(cond)); + } private: query_on_intermediate on_clause(std::unique_ptr &&cond); diff --git a/include/matador/sql/query_result_impl.hpp b/include/matador/sql/query_result_impl.hpp index c301d5b..334128e 100644 --- a/include/matador/sql/query_result_impl.hpp +++ b/include/matador/sql/query_result_impl.hpp @@ -41,9 +41,11 @@ public: void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {} template - void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {} + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} template - void on_has_many(const char *, ContainerType &, 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*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} private: size_t column_index_{}; diff --git a/include/matador/sql/schema.hpp b/include/matador/sql/schema.hpp index d6ac930..6cde249 100644 --- a/include/matador/sql/schema.hpp +++ b/include/matador/sql/schema.hpp @@ -23,6 +23,7 @@ class schema { public: using repository = std::unordered_map; + using repository_by_name = std::unordered_map>; using iterator = repository::iterator; using const_iterator = repository::const_iterator; @@ -50,6 +51,7 @@ public: } [[nodiscard]] std::optional info(std::type_index ti) const; + [[nodiscard]] std::optional info(const std::string &name) const; template [[nodiscard]] std::pair reference() const @@ -77,6 +79,7 @@ public: private: std::string name_; repository repository_; + repository_by_name repository_by_name_; }; } diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index a3d5c72..2eae418 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -13,6 +13,100 @@ namespace matador::sql { class dialect; +struct join_data +{ + table join_table; + std::unique_ptr condition; +}; + +//class join_collector +//{ +//private: +// explicit join_collector(const sql::schema &ts, std::vector &joins) +// : table_schema_(ts) +// , joins_(joins) {} +// +//public: +// template < class Type > +// static std::vector collect(const sql::schema &ts) +// { +// const auto info = ts.info(); +// if (!info) { +// return {}; +// } +// std::vector joins; +// join_collector collector(ts, joins); +// Type obj; +// matador::utils::access::process(collector, obj); +// return joins; +// } +// +// template < class PrimaryKeyType > +// void on_primary_key(const char *id, PrimaryKeyType &, 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 &, const utils::foreign_attributes &attr) +// { +// if (attr.fetch() == utils::fetch_type::LAZY) { +// push(id); +// } else { +// const auto info = table_schema_.info(); +// if (!info) { +// return; +// } +// table_name_stack_.push(info.value().name); +// typename Pointer::value_type obj; +// matador::utils::access::process(*this, obj); +// table_name_stack_.pop(); +// } +// } +// template +// void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr) +// { +// if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { +// push(id); +// } else { +// const auto info = table_schema_.info(); +// if (!info) { +// return; +// } +// table_name_stack_.push(info.value().name); +// typename Pointer::value_type obj; +// matador::utils::access::process(*this, obj); +// table_name_stack_.pop(); +// } +// } +// template +// void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr) +// { +// if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { +// return; +// } +// const auto info = table_schema_.info(); +// if (!info) { +// return; +// } +// +// table_name_stack_.push(info.value().name); +// typename ContainerType::value_type::value_type obj; +// matador::utils::access::process(*this, obj); +// table_name_stack_.pop(); +// } +// template +// void on_has_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr) +// { +// on_has_many(id, c, "", "", attr); +// } +// +//private: +// const sql::schema &table_schema_; +// std::vector &joins_; +//}; + class session { public: @@ -44,6 +138,9 @@ public: return {}; } + auto columns = sql::column_generator::generate(*schema_); + +// auto joins = sql c->query(*schema_).select({}).from(info->name); // build pk where condition // - check if type has pk diff --git a/include/matador/sql/value_extractor.hpp b/include/matador/sql/value_extractor.hpp index 206d653..89f786f 100644 --- a/include/matador/sql/value_extractor.hpp +++ b/include/matador/sql/value_extractor.hpp @@ -53,9 +53,11 @@ public: values_.emplace_back(fk_value_extractor_.extract(*x)); } template - void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {} + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} template - void on_has_many(const char *, ContainerType &, 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*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} private: template diff --git a/include/matador/utils/access.hpp b/include/matador/utils/access.hpp index ec2bb65..48338cc 100644 --- a/include/matador/utils/access.hpp +++ b/include/matador/utils/access.hpp @@ -64,24 +64,19 @@ void belongs_to(Operator &op, const char *id, Type &value, const foreign_attribu op.on_belongs_to(id, value, attr); } -//template class ContainerType> -//void has_many(Operator &op, const char *id, container &container, const foreign_attributes &attr) { -// op.on_has_many(id, container, attr); -//} - template class ContainerType> -void has_many(Operator &op, const char *id, ContainerType &container, const foreign_attributes &attr) { - op.on_has_many(id, container, attr); +void has_many(Operator &op, ContainerType &container, const char *join_column, const foreign_attributes &attr) { + op.on_has_many(container, join_column, attr); } -//template class ContainerType> -//void has_many(Operator &op, const char *id, container &container, const char *left_column, const char *right_column, const foreign_attributes &attr) { -// op.on_has_many(id, container, left_column, right_column, attr); -//} +template class ContainerType> +void has_many_to_many(Operator &op, const char *id, ContainerType &container, const char *join_column, const char *inverse_join_column, const foreign_attributes &attr) { + op.on_has_many_to_many(id, container, join_column, inverse_join_column, attr); +} template class ContainerType> -void has_many(Operator &op, const char *id, ContainerType &container, const char *left_column, const char *right_column, const foreign_attributes &attr) { - op.on_has_many(id, container, left_column, right_column, attr); +void has_many_to_many(Operator &op, const char *id, ContainerType &container, const foreign_attributes &attr) { + op.on_has_many_to_many(id, container, attr); } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92b9b61..eedc0ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,6 +91,7 @@ set(SQL_HEADER ../include/matador/sql/field.hpp ../include/matador/sql/entity_query_builder.hpp ../include/matador/sql/table_definition.hpp + ../include/matador/sql/has_many_to_many_relation.hpp ) set(QUERY_SOURCES diff --git a/src/sql/condition.cpp b/src/sql/condition.cpp index 44a8991..532dd7e 100644 --- a/src/sql/condition.cpp +++ b/src/sql/condition.cpp @@ -6,7 +6,7 @@ condition::type>::condition(const colu : basic_column_condition(fld, op), value(val) {} -std::string condition::type>::evaluate(dialect &d, query_context &query) const +std::string condition::type>::evaluate(const dialect &d, query_context &query) const { query.bind_vars.emplace_back(field_.name); return d.prepare_identifier(field_) + " " + operand + " " + d.next_placeholder(query.bind_vars); @@ -16,7 +16,7 @@ condition::condition(column col, basic_condition::operand : basic_column_condition(std::move(col), op), query_(q) {} -std::string condition::evaluate(dialect &d, query_context &query) const +std::string condition::evaluate(const dialect &d, query_context &query) const { std::string result(d.prepare_identifier(field_) + " " + operand + " ("); result += (")"); diff --git a/src/sql/dialect.cpp b/src/sql/dialect.cpp index 07433b0..e11c4b9 100644 --- a/src/sql/dialect.cpp +++ b/src/sql/dialect.cpp @@ -37,7 +37,7 @@ std::string dialect::prepare_identifier_string(const std::string &col) const for (auto &part : parts) { escape_quotes_in_identifier(part); - quote_identifier(part); +// quote_identifier(part); } return utils::join(parts, "."); } diff --git a/src/sql/schema.cpp b/src/sql/schema.cpp index d15e1c4..b3b6c39 100644 --- a/src/sql/schema.cpp +++ b/src/sql/schema.cpp @@ -15,7 +15,10 @@ std::string schema::name() const const table_info& schema::attach(const std::type_index ti, const table_info& table) { - return repository_.try_emplace(ti, table).first->second; + + auto &ref = repository_.try_emplace(ti, table).first->second; + repository_by_name_.try_emplace(ref.name, std::ref(ref)); + return ref; } std::optional schema::info(std::type_index ti) const @@ -27,6 +30,15 @@ std::optional schema::info(std::type_index ti) const return it->second; } +std::optional schema::info(const std::string &name) const +{ + const auto it = repository_by_name_.find(name); + if (it == repository_by_name_.end()) { + return std::nullopt; + } + return it->second; +} + std::pair schema::reference(const std::type_index &ti) const { const auto it = repository_.find(ti); diff --git a/test/EntityQueryBuilderTest.cpp b/test/EntityQueryBuilderTest.cpp index 0a42f80..62e7a6c 100644 --- a/test/EntityQueryBuilderTest.cpp +++ b/test/EntityQueryBuilderTest.cpp @@ -1,33 +1,123 @@ #include #include +#include #include #include "models/author.hpp" #include "models/book.hpp" - -struct schiff { - std::string id; - - template - void process(Operator &op) - { - namespace field = matador::utils::access; - field::primary_key(op, "id", id, 255); - } -}; +#include "models/recipe.hpp" +#include "models/order.hpp" using namespace matador::sql; -TEST_CASE("Create sql query for entity", "[query][entity][builder]") { - connection noop("noop://noop.db"); +TEST_CASE("Create sql query data for entity with eager belongs to", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); schema scm("noop"); - scm.attach("authors"); - scm.attach("books"); + scm.attach("authors"); + scm.attach("books"); entity_query_builder eqb(17, scm); - auto context = eqb.build(noop); - std::cout << "SQL: " << context.value().sql << "\n"; - context = eqb.build(noop); + auto data = eqb.build(); + + REQUIRE(data.has_value()); + REQUIRE(data->root_table_name == "books"); + REQUIRE(data->joins.size() == 1); + REQUIRE(data->columns.size() == 9); + REQUIRE(data->where_clause); + + std::vector> expected_join_data { + { "books", "books.author_id = authors.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; + } + + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == "books.id = 17"); + + auto &jd = data->joins.front(); + + auto context = db.query(scm) + .select(data->columns) + .from(data->root_table_name) + .join_left(jd.join_table).on(std::move(jd.condition)) + .where(std::move(data->where_clause)) + .build(); +} + +TEST_CASE("Create sql query data for entity with eager has many belongs to", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + scm.attach("products"); + scm.attach("order_details"); + scm.attach("orders"); + + entity_query_builder eqb(17, scm); + + auto data = eqb.build(); + + REQUIRE(data.has_value()); + REQUIRE(data->root_table_name == "orders"); + REQUIRE(data->joins.size() == 1); + REQUIRE(data->columns.size() == 15); + REQUIRE(data->where_clause); + + std::vector> expected_join_data { + { "orders", "orders.order_id = order_details.order_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; + } + + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == "orders.order_id = 17"); +} + +TEST_CASE("Create sql query data for entity with eager many to many", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + scm.attach("recipes"); + scm.attach("ingredients"); + scm.attach("recipe_ingredients"); + + entity_query_builder eqb(17, scm); + + auto data = eqb.build(); + + REQUIRE(data.has_value()); + REQUIRE(data->root_table_name == "ingredients"); + REQUIRE(data->joins.size() == 2); + REQUIRE(data->columns.size() == 4); + REQUIRE(data->where_clause); + + std::vector> expected_join_data { + { "ingredients", "ingredients.id = recipe_ingredients.ingredient_id"}, + { "recipes", "recipes.id = recipe_ingredients.recipe_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; + } + + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == "ingredients.id = 17"); } \ No newline at end of file diff --git a/test/models/author.hpp b/test/models/author.hpp index 1c8b892..fc0599e 100644 --- a/test/models/author.hpp +++ b/test/models/author.hpp @@ -3,10 +3,14 @@ #include "matador/utils/access.hpp" +#include "matador/sql/entity.hpp" + #include namespace matador::test { +struct book; + struct author { unsigned long id{}; @@ -27,7 +31,7 @@ struct author field::attribute(op, "date_of_birth", date_of_birth, 31); field::attribute(op, "year_of_birth", year_of_birth); field::attribute(op, "distinguished", distinguished); - field::has_many(op, "books", books, "author_id", "id", utils::fetch_type::LAZY); + field::has_many(op, books, "author_id", utils::fetch_type::LAZY); } }; diff --git a/test/models/order.hpp b/test/models/order.hpp index bc8db3a..0fe9536 100644 --- a/test/models/order.hpp +++ b/test/models/order.hpp @@ -42,7 +42,7 @@ struct order field::attribute(op, "ship_region", ship_region, 255); field::attribute(op, "ship_postal_code", ship_postal_code, 255); field::attribute(op, "ship_country", ship_country, 255); - field::has_many(op, "order_details", order_details_, utils::fetch_type::EAGER); + field::has_many(op, order_details_, "order_id", utils::fetch_type::EAGER); } }; diff --git a/test/models/recipe.hpp b/test/models/recipe.hpp index fd6e310..f43ee17 100644 --- a/test/models/recipe.hpp +++ b/test/models/recipe.hpp @@ -6,6 +6,7 @@ #include "matador/utils/foreign_attributes.hpp" #include "matador/sql/entity.hpp" +#include "matador/sql/has_many_to_many_relation.hpp" #include @@ -23,7 +24,7 @@ struct ingredient namespace field = matador::utils::access; field::primary_key(op, "id", id); field::attribute(op, "name", name, 255); - field::has_many(op, "recipes", recipes, "ingredient_id", "recipe_id", utils::fetch_type::EAGER); + field::has_many_to_many(op, "recipe_ingredients", recipes, "ingredient_id", "recipe_id", utils::fetch_type::EAGER); } }; @@ -38,21 +39,15 @@ struct recipe namespace field = matador::utils::access; field::primary_key(op, "id", id); field::attribute(op, "name", name, 255); - field::has_many(op, "ingredients", ingredients, "recipe_id", "ingredient_id", utils::fetch_type::EAGER); + field::has_many_to_many(op, "recipe_ingredients", ingredients, utils::fetch_type::LAZY); } }; -struct recipe_ingredient +class recipe_ingredient : public sql::has_many_to_many_relation { - sql::entity recipe_; - sql::entity ingredient_; - - template - void process(Operator &op) { - namespace field = matador::utils::access; - field::belongs_to(op, "recipe_id", recipe_, utils::default_foreign_attributes); - field::belongs_to(op, "ingredient_id", ingredient_, utils::default_foreign_attributes); - } +public: + recipe_ingredient() + : has_many_to_many_relation("recipe_id", "ingredient_id") {} }; }