diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 1c1eb1d..9be198b 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -13,3 +13,11 @@ target_link_libraries(sandbox PRIVATE ${CMAKE_DL_LIBS} ${SQLite3_LIBRARIES} ) + +add_executable(work work.cpp) +target_link_libraries(work PRIVATE + matador-core + matador-orm + ${CMAKE_DL_LIBS} + ${SQLite3_LIBRARIES} +) diff --git a/demo/recipe.hpp b/demo/recipe.hpp new file mode 100644 index 0000000..1c81007 --- /dev/null +++ b/demo/recipe.hpp @@ -0,0 +1,61 @@ +#ifndef QUERY_RECIPE_HPP +#define QUERY_RECIPE_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include "matador/object/collection.hpp" +#include "matador/object/object_ptr.hpp" +#include "matador/object/many_to_many_relation.hpp" + +#include + +namespace demo { + +struct recipe; +struct ingredient +{ + unsigned int id{}; + std::string name; + matador::object::collection> recipes{}; + + ingredient()= default; + ingredient(const unsigned int id, std::string name) + : id(id), name(std::move(name)) {} + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + field::has_many_to_many(op, "recipe_ingredients", recipes, "ingredient_id", "recipe_id", matador::utils::fetch_type::EAGER); + } +}; + +struct recipe +{ + unsigned int id{}; + std::string name; + matador::object::collection> ingredients{}; + + recipe()= default; + recipe(const unsigned int id, std::string name) + : id(id), name(std::move(name)) {} + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + field::has_many_to_many(op, "recipe_ingredients", ingredients, matador::utils::fetch_type::LAZY); + } +}; + +class recipe_ingredient : public matador::object::many_to_many_relation { +public: + recipe_ingredient() : many_to_many_relation("recipe_id", "ingredient_id") {} +}; + +} + +#endif //QUERY_RECIPE_HPP diff --git a/demo/sandbox.cpp b/demo/sandbox.cpp index 2ca4460..564f832 100644 --- a/demo/sandbox.cpp +++ b/demo/sandbox.cpp @@ -4,6 +4,7 @@ #include "author.hpp" #include "book.hpp" +#include "recipe.hpp" #include @@ -54,6 +55,51 @@ * - set foreign endpoint of (2) to endpoint (1) * - check relation endpoints... */ + +namespace demo { +struct names { + unsigned int id{}; + std::vector names_list; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::has_many(op, "name_list", names_list, "names_id", matador::utils::fetch_type::EAGER); + } +}; + +struct user; +struct profile { + unsigned int id{}; + std::string first_name; + std::string last_name; + matador::object::object_ptr user; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::attribute( op, "first_name", first_name, 255 ); + field::attribute( op, "last_name", last_name, 255 ); + field::belongs_to( op, "user_id", user, matador::utils::default_foreign_attributes ); + } +}; +struct user { + unsigned int id{}; + std::string username; + matador::object::object_ptr profile; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::attribute( op, "username", username, 255 ); + field::has_one(op, "profile_id", profile, matador::utils::default_foreign_attributes ); + } +}; +} + using namespace demo; using namespace matador; @@ -62,6 +108,15 @@ int main() { logger::add_log_sink(logger::create_stdout_sink()); { + // has_many with builtin-type + object::schema schema; + + auto result = schema.attach("names"); + + schema.dump(std::cout); + } + { + // has_many to belongs_to object::schema schema; auto result = schema.attach("authors") @@ -70,6 +125,7 @@ int main() { schema.dump(std::cout); } { + // belongs_to to has_many object::schema schema; auto result = schema.attach("books") @@ -77,4 +133,40 @@ int main() { schema.dump(std::cout); } + { + // has_many_to_many (with join columns first) + object::schema schema; + + auto result = schema.attach("ingredients") + .and_then([&schema] { return schema.attach("recipes"); }); + + schema.dump(std::cout); + } + { + // has_many_to_many (with join columns last) + object::schema schema; + + auto result = schema.attach("recipes") + .and_then([&schema] { return schema.attach("ingredients"); }); + + schema.dump(std::cout); + } + { + // belongs_to to has_one + object::schema schema; + + auto result = schema.attach("profiles") + .and_then([&schema] { return schema.attach("users"); }); + + schema.dump(std::cout); + } + { + // has_one to belongs_to + object::schema schema; + + auto result = schema.attach("users") + .and_then([&schema] { return schema.attach("profiles"); }); + + schema.dump(std::cout); + } } diff --git a/demo/work.cpp b/demo/work.cpp index 614335c..87d0ec3 100644 --- a/demo/work.cpp +++ b/demo/work.cpp @@ -11,17 +11,38 @@ #include "work/admin/UserSession.hpp" #include "work/jobs/Job.hpp" -#include "work/jobs/Task.hpp"" +#include "work/jobs/Task.hpp" #include "work/jobs/Payload.hpp" #include "work/jobs/IdPayload.hpp" #include "work/jobs/IdListPayload.hpp" +#include "matador/utils/default_type_traits.hpp" #include "matador/object/schema.hpp" #include "matador/sql/connection.hpp" #include "matador/orm/session.hpp" +template <> struct matador::utils::data_type_traits { + static basic_type type(std::size_t /*size*/) { return basic_type::type_uint64; } + static void read_value(attribute_reader &reader, const char *id, size_t index, nullptr_t &/*value*/) { + + } + static void bind_value(attribute_writer &binder, size_t index, nullptr_t &/*value*/) { + + } +}; + +template <> struct matador::utils::data_type_traits { + static basic_type type(std::size_t /*size*/) { return basic_type::type_uint64; } + static void read_value(attribute_reader &reader, const char *id, size_t index, nullptr_t &/*value*/) { + + } + static void bind_value(attribute_writer &binder, size_t index, nullptr_t &/*value*/) { + + } +}; + using namespace matador; using namespace work::models; @@ -51,36 +72,38 @@ using namespace work::models; int main() { object::schema schema("Administration"); - auto result = schema.attach("collection_center") + auto result = schema.attach("collection_centers") .and_then([&schema] { return schema.attach("user_directories"); }) .and_then([&schema] { return schema.attach("ldap_group_schema_settings"); }) .and_then([&schema] { return schema.attach("ldap_import_settings"); }) .and_then([&schema] { return schema.attach("ldap_user_schema_settings"); }) - .and_then([&schema] { return schema.attach("internal_user_directories"); }) - .and_then([&schema] { return schema.attach("ldap_user_directories"); } ) + .and_then([&schema] { return schema.attach("internal_user_directories"); }) + .and_then([&schema] { return schema.attach("ldap_user_directories"); } ) .and_then([&schema] { return schema.attach("login_histories"); }) .and_then([&schema] { return schema.attach("scenarios"); }) .and_then([&schema] { return schema.attach("users"); }) .and_then([&schema] { return schema.attach("user_sessions"); }) .and_then([&schema] { return schema.attach("jobs"); }) .and_then([&schema] { return schema.attach("payloads"); }) - .and_then([&schema] { return schema.attach("id_list_payloads"); }) - .and_then([&schema] { return schema.attach("id_payloads"); }) + .and_then([&schema] { return schema.attach("id_list_payloads"); }) + .and_then([&schema] { return schema.attach("id_payloads"); }) .and_then([&schema] { return schema.attach("tasks"); }); if (!result.is_ok()) { + std::cout << "error: " << result.err().message() << std::endl; return 0; } - const std::string dns{"sqlite://demo.db"}; - sql::connection c(dns); - - result = c.open(); - - // orm::session s() - if (!result.is_ok()) { - return 0; - } + schema.dump(std::cout); + // const std::string dns{"sqlite://demo.db"}; + // sql::connection c(dns); + // + // result = c.open(); + // + // // orm::session s() + // if (!result.is_ok()) { + // return 0; + // } return 0; diff --git a/demo/work/admin/CollectionCenter.hpp b/demo/work/admin/CollectionCenter.hpp index 84d5f2a..0d090a8 100644 --- a/demo/work/admin/CollectionCenter.hpp +++ b/demo/work/admin/CollectionCenter.hpp @@ -4,6 +4,7 @@ #include "../core/Model.hpp" #include "matador/object/collection.hpp" +#include "matador/object/object_ptr.hpp" #include "matador/utils/base_class.hpp" #include "matador/utils/enum_mapper.hpp" @@ -29,7 +30,7 @@ struct CollectionCenter : core::Model { std::string name; matador::utils::blob symbol; CollectionCenterType type{CollectionCenterType::NoType}; - matador::object::collection users; + matador::object::collection> users; template void process( Operator& op ) { diff --git a/demo/work/admin/LoginHistory.hpp b/demo/work/admin/LoginHistory.hpp index 86e27eb..536b8e6 100644 --- a/demo/work/admin/LoginHistory.hpp +++ b/demo/work/admin/LoginHistory.hpp @@ -7,7 +7,7 @@ #include "../core/Model.hpp" -#include "matador/object/object_ptr.hpp"" +#include "matador/object/object_ptr.hpp" #include "matador/utils/base_class.hpp" #include "matador/utils/enum_mapper.hpp" diff --git a/demo/work/admin/Scenario.hpp b/demo/work/admin/Scenario.hpp index 8ba4a68..ebe46e8 100644 --- a/demo/work/admin/Scenario.hpp +++ b/demo/work/admin/Scenario.hpp @@ -42,7 +42,7 @@ struct Scenario : core::Model { std::string description; matador::utils::blob symbol; ScenarioState state{ScenarioState::InvalidState}; - matador::object::collection collection_center; + matador::object::collection> collection_center; template void process( Operator& op ) { diff --git a/demo/work/admin/User.hpp b/demo/work/admin/User.hpp index 2e6d5a5..49032a1 100644 --- a/demo/work/admin/User.hpp +++ b/demo/work/admin/User.hpp @@ -6,7 +6,7 @@ #include "../core/Model.hpp" #include "../core/Types.hpp" -#include "matador/object/object_ptr.hpp"" +#include "matador/object/object_ptr.hpp" #include "matador/utils/base_class.hpp" #include "matador/utils/types.hpp" diff --git a/demo/work/admin/UserSession.hpp b/demo/work/admin/UserSession.hpp index 5bebf1b..d2b1ef7 100644 --- a/demo/work/admin/UserSession.hpp +++ b/demo/work/admin/UserSession.hpp @@ -8,7 +8,7 @@ #include "../core/Model.hpp" #include "../core/Types.hpp" -#include "matador/object/object_ptr.hpp"" +#include "matador/object/object_ptr.hpp" #include "matador/utils/base_class.hpp" diff --git a/demo/work/jobs/IdListPayload.hpp b/demo/work/jobs/IdListPayload.hpp index ff75438..19894ed 100644 --- a/demo/work/jobs/IdListPayload.hpp +++ b/demo/work/jobs/IdListPayload.hpp @@ -5,6 +5,8 @@ #include "matador/object/collection.hpp" +#include "matador/utils/foreign_attributes.hpp" + namespace work::models::jobs { struct IdListPayload : Payload { matador::object::collection ids; @@ -12,7 +14,7 @@ struct IdListPayload : Payload { void process( Operator& op ) { namespace field = matador::access; field::process( op, *matador::base_class( this ) ); - field::has_many( op, "payload_ids", ids ); + field::has_many( op, "payload_ids", ids, "payload_id", matador::utils::default_foreign_attributes ); } }; } diff --git a/demo/work/jobs/Job.hpp b/demo/work/jobs/Job.hpp index 1e350ab..8a20840 100644 --- a/demo/work/jobs/Job.hpp +++ b/demo/work/jobs/Job.hpp @@ -35,7 +35,7 @@ struct Job : core::Model { field::attribute( op, "state", state ); field::attribute( op, "mode", mode ); field::attribute( op, "created_at", created_at ); - field::belongs_to( op, "payload", payload, matador::utils::default_foreign_attributes ); + field::has_one(op, "payload", payload, matador::utils::default_foreign_attributes ); field::belongs_to( op, "task", task, matador::utils::default_foreign_attributes ); field::attribute( op, "user_info", user_info ); } diff --git a/demo/work/jobs/Payload.hpp b/demo/work/jobs/Payload.hpp index 4c32b0d..0ee0922 100644 --- a/demo/work/jobs/Payload.hpp +++ b/demo/work/jobs/Payload.hpp @@ -6,13 +6,17 @@ #include "matador/utils/base_class.hpp" namespace work::models::jobs { +struct Job; struct Payload : core::Model { std::string type; + matador::object::object_ptr job; + template void process( Operator& op ) { namespace field = matador::access; field::process( op, *matador::base_class( this ) ); field::attribute( op, "type", type, 255 ); + field::belongs_to( op, "job", job, matador::utils::default_foreign_attributes ); } }; } diff --git a/include/matador/object/join_columns_collector.hpp b/include/matador/object/join_columns_collector.hpp new file mode 100644 index 0000000..5125778 --- /dev/null +++ b/include/matador/object/join_columns_collector.hpp @@ -0,0 +1,51 @@ +#ifndef JOIN_COLUMNS_COLLECTOR_HPP +#define JOIN_COLUMNS_COLLECTOR_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/field_attributes.hpp" + +#include + +namespace matador::object { +struct join_columns { + std::string join_column; + std::string inverse_join_column; +}; + +class join_columns_collector final { +public: + template + join_columns collect() { + join_columns_ = {}; + Type obj; + + access::process(*this, obj); + + return join_columns_; + } + template < class V > + 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 + static void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + template + static void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} + template + static void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} + template + 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*/) { + join_columns_.join_column = join_column; + join_columns_.inverse_join_column = inverse_join_column; + } + template + static void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {} + +private: + join_columns join_columns_; +}; +} + +#endif //JOIN_COLUMNS_COLLECTOR_HPP diff --git a/include/matador/object/schema.hpp b/include/matador/object/schema.hpp index dc9d61f..7103b7c 100644 --- a/include/matador/object/schema.hpp +++ b/include/matador/object/schema.hpp @@ -2,6 +2,8 @@ #define SCHEMA_HPP #include "matador/logger/log_manager.hpp" + +#include "matador/object/join_columns_collector.hpp" #include "matador/object/many_to_many_relation.hpp" #include "matador/object/primary_key_resolver.hpp" #include "matador/object/error_code.hpp" @@ -105,8 +107,8 @@ public: template void on_has_many_to_many(const char *id, CollectionType &collection, 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 &collection, const utils::foreign_attributes &attr); + template + void on_has_many_to_many(const char *id, CollectionType &collection, const utils::foreign_attributes &attr); private: explicit relation_completer(const std::shared_ptr &node) @@ -123,6 +125,7 @@ private: std::shared_ptr node_; schema &schema_; logger::logger log_; + join_columns_collector join_columns_collector_; }; @@ -140,9 +143,9 @@ public: template [[nodiscard]] utils::result attach(const std::string &name, const std::string &parent = "") { // if (has_node(name)) { - // return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + name + "' already exists")); + // return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + name + "' already exists")); // } - auto node = acquire_node(name); + auto node = schema_node::make_node(*this, name); relation_completer::complete(node); if (auto result = attach_node(node, parent); !result) { return utils::failure(result.err()); @@ -239,25 +242,11 @@ private: [[nodiscard]] utils::result find_node(const std::string &name) const; [[nodiscard]] utils::result find_node(const std::type_index &type_index) const; - template - node_ptr acquire_node(const std::string &name) { - if (const auto it = expected_node_map_.find(typeid(Type)); it != expected_node_map_.end()) { - const auto node = it->second; - expected_node_map_.erase(it); - node->update_name(name); - - return node; - } - - return schema_node::make_node(*this, name); - } - - [[nodiscard]] bool has_node(const std::string &name) const; [[nodiscard]] bool has_node(const std::type_index &index) const; [[nodiscard]] bool has_node(const std::type_index &index, const std::string &name) const; - static void push_back_child(const node_ptr &parent, const node_ptr &child); + static void insert_node(const node_ptr &parent, const node_ptr &child); void remove_node(const node_ptr &node); private: @@ -269,7 +258,6 @@ private: t_node_map nodes_by_name_; t_type_index_node_map nodes_by_type_; - t_type_index_node_map expected_node_map_; logger::logger log_; }; @@ -330,70 +318,130 @@ template template void relation_completer::on_has_many(const char *id, CollectionType &, const char *join_column, const utils::foreign_attributes &, - std::enable_if_t::value> - * /*unused*/) { + std::enable_if_t::value>* /*unused*/) { using value_type = typename CollectionType::value_type; + using relation_value_type = many_to_relation; - const auto node = schema_node::make_relation_node >( + const auto node = schema_node::make_relation_node( schema_, id, [join_column] { - return new many_to_many_relation(join_column, "value"); + return std::make_unique(join_column, "value"); }); - const auto result = schema_.attach >(id); + const auto result = schema_.attach_node(node, ""); if (!result) { // Todo: throw internal exception } + + const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, node_); + const auto foreign_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, node); + node_->info_->register_relation_endpoint(typeid(value_type), local_endpoint); + foreign_endpoint->node_->info_->register_relation_endpoint(node_->type_index(), foreign_endpoint); + link_relation_endpoints(local_endpoint, foreign_endpoint); +} + +template +template +void relation_completer::on_has_many_to_many(const char *id, + CollectionType &/*collection*/, + const char *join_column, + const char *inverse_join_column, + const utils::foreign_attributes &/*attr*/) { + auto result = schema_.find_node(id); + if (result) { + const auto foreign_node = result.value(); + const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, node_); + node_->info_->register_relation_endpoint(typeid(CollectionType::value_type::value_type), local_endpoint); + const auto it = foreign_node->info_->find_relation_endpoint(node_->type_index()); + if (it == foreign_node->info().endpoint_end()) { + // Todo: Throw error + return; + } + link_relation_endpoints(local_endpoint, it->second); + } else { + using relation_value_type = many_to_many_relation; + auto creator = [join_column, inverse_join_column] { + return std::make_unique(join_column, inverse_join_column); + }; + + auto node = schema_node::make_relation_node(schema_, id, std::move(creator)); + + auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, node_); + auto join_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, node); + auto inverse_join_endpoint = std::make_shared(inverse_join_column, relation_type::BELONGS_TO, node); + node_->info_->register_relation_endpoint(typeid(CollectionType::value_type::value_type), local_endpoint); + node->info_->register_relation_endpoint(node_->type_index(), join_endpoint); + link_relation_endpoints(local_endpoint, join_endpoint); + node->info_->register_relation_endpoint(typeid(CollectionType::value_type::value_type), inverse_join_endpoint); + result = schema_.attach_node(node, ""); + if (!result) { + // Todo: throw internal error + return; + } + } } template template -void relation_completer::on_has_many_to_many(const char *id, CollectionType &/*collection*/, - const char *join_column, - const char *inverse_join_column, - const utils::foreign_attributes &attr) { - auto result = schema_.find_node(id); - if (result) { - } else { - // - // using relation_type = many_to_many_relation; - // auto creator = [join_column, inverse_join_column] { - // return new many_to_many_relation(join_column, inverse_join_column); - // }; - - // auto node = schema_node::make_relation_node(schema_, id); - - // schema_.attach_node(node, typeid(relation_type)); - } -} - -template -template -void relation_completer::on_has_many_to_many(const char *id, ContainerType &collection, - const utils::foreign_attributes &attr) { +void relation_completer::on_has_many_to_many(const char *id, + CollectionType &/*collection*/, + const utils::foreign_attributes &/*attr*/) { auto result = schema_.find_node(id); if (!result) { - // using relation_type = many_to_many_relation; - // auto creator = [attr] { - // return new relation_type(attr.join_column, attr.inverse_join_column); - // }; + const auto join_columns = join_columns_collector_.collect(); + using relation_value_type = many_to_many_relation; + auto creator = [&join_columns] { + return std::make_unique(join_columns.inverse_join_column, join_columns.join_column); + }; + + auto node = schema_node::make_relation_node(schema_, id, std::move(creator)); + + auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, node_); + auto join_endpoint = std::make_shared(join_columns.join_column, relation_type::BELONGS_TO, node); + auto inverse_join_endpoint = std::make_shared(join_columns.inverse_join_column, relation_type::BELONGS_TO, node); + node_->info_->register_relation_endpoint(typeid(CollectionType::value_type::value_type), local_endpoint); + node->info_->register_relation_endpoint(node_->type_index(), inverse_join_endpoint); + link_relation_endpoints(local_endpoint, inverse_join_endpoint); + node->info_->register_relation_endpoint(typeid(CollectionType::value_type::value_type), join_endpoint); + result = schema_.attach_node(node, ""); + if (!result) { + // Todo: throw internal error + return; + } + } else { + const auto foreign_node = result.value(); + const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, node_); + node_->info_->register_relation_endpoint(typeid(CollectionType::value_type::value_type), local_endpoint); + const auto it = foreign_node->info_->find_relation_endpoint(node_->type_index()); + if (it == foreign_node->info().endpoint_end()) { + // Todo: Throw internal error + return; + } + link_relation_endpoints(local_endpoint, it->second); } } template template -void relation_completer::on_has_one(const char *id, ForeignPointerType &/*obj*/, +void relation_completer::on_has_one(const char *id, + ForeignPointerType &/*obj*/, const utils::foreign_attributes &/*attr*/) { auto ti = std::type_index(typeid(typename ForeignPointerType::value_type)); - if (const auto result = schema_.find_node(ti); !result.is_ok() && schema_.expected_node_map_.count(ti) == 0) { - schema_.expected_node_map_.insert({ - ti, schema_node::make_node(schema_, ti.name()) - }); + if (const auto result = schema_.find_node(ti); !result.is_ok()) { + // Node was not found + // Create node without the foreign relation endpoint + log_.debug("node '%s' has foreign key '%s' has one '%s'", node_->name().c_str(), id, ti.name()); + node_->info_->register_relation_endpoint(ti, std::make_shared(id, relation_type::HAS_ONE, node_)); } else { const auto &foreign_node = result.value(); - if (const auto rit = foreign_node->info().find_relation_endpoint(ti); rit != foreign_node->info().endpoint_end()) { - if (rit->second->is_has_many()) { - } else if (rit->second->is_has_one()) { + if (const auto it = foreign_node->info().find_relation_endpoint(typeid(Type)); it != foreign_node->info().endpoint_end()) { + if (it->second->is_belongs_to()) { + it->second->node_ = foreign_node; + const auto endpoint = std::make_shared(id, relation_type::HAS_ONE, node_); + node_->info_->register_relation_endpoint(ti, endpoint); + link_relation_endpoints(endpoint, it->second); } else { + // Todo: Throw internal error + return; } } } @@ -401,7 +449,8 @@ void relation_completer::on_has_one(const char *id, ForeignPointerType &/* template template -void relation_completer::on_belongs_to(const char *id, ForeignPointerType & /*obj*/, +void relation_completer::on_belongs_to(const char *id, + ForeignPointerType & /*obj*/, const utils::foreign_attributes & /*attr*/) { using value_type = typename ForeignPointerType::value_type; const auto ti = std::type_index(typeid(value_type)); @@ -416,7 +465,11 @@ void relation_completer::on_belongs_to(const char *id, ForeignPointerType // Check foreign node and relation endpoint if (const auto it = foreign_node->info_->find_relation_endpoint(typeid(Type)); it != foreign_node->info().endpoint_end()) { // Found corresponding relation endpoint in the foreign node - if (it->second->foreign_endpoint()->node().type_index() == typeid(many_to_many_relation)) { + if (it->second->is_has_one()) { + const auto endpoint = std::make_shared(id, relation_type::BELONGS_TO, node_); + node_->info_->register_relation_endpoint(ti, endpoint); + link_relation_endpoints(endpoint, it->second); + } else if (it->second->foreign_endpoint()->node().type_index() == typeid(many_to_many_relation)) { // Endpoint is a "many_to_many_relation". This means there // is a "many_to_many_relation" node attached. Because of being a // "belongs_to"-relation the "many_to_many_relation" can be removed @@ -429,10 +482,9 @@ void relation_completer::on_belongs_to(const char *id, ForeignPointerType // check type } } else { - // Relation node was not found, create endpoint. + // Relation node was not found, create only endpoint. const auto endpoint = std::make_shared(id, relation_type::BELONGS_TO, node_); node_->info_->register_relation_endpoint(ti, endpoint); - link_relation_endpoints(endpoint, it->second); } } } diff --git a/include/matador/orm/session_query_builder.hpp b/include/matador/orm/session_query_builder.hpp index 430d79a..9c9ca8e 100644 --- a/include/matador/orm/session_query_builder.hpp +++ b/include/matador/orm/session_query_builder.hpp @@ -8,6 +8,7 @@ #include "matador/sql/connection.hpp" +#include "matador/object/join_columns_collector.hpp" #include "matador/object/schema.hpp" #include "matador/utils/result.hpp" @@ -18,48 +19,6 @@ namespace matador::orm { -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; - - access::process(*this, obj); - - return join_columns_; - } - template < class V > - 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 - static void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} - template - static void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} - template - static void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} - template - 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*/) { - join_columns_.join_column = join_column; - join_columns_.inverse_join_column = inverse_join_column; - } - template - 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::shared_ptr root_table; std::string pk_column_name{}; @@ -255,7 +214,7 @@ public: throw query_builder_exception{query_build_error::MissingPrimaryKey}; } - const auto join_columns = join_column_collector_.collect(); + const auto join_columns = join_columns_collector_.collect(); append_join( sql::column{table_info_stack_.top().table, table_info_stack_.top().info.get().definition().primary_key()->name()}, @@ -288,7 +247,7 @@ private: entity_query_data entity_query_data_; unsigned int column_index{0}; unsigned int table_index{0}; - join_column_collector join_column_collector_; + object::join_columns_collector join_columns_collector_; }; template diff --git a/source/core/object/schema.cpp b/source/core/object/schema.cpp index 7ca3d06..6f0df05 100644 --- a/source/core/object/schema.cpp +++ b/source/core/object/schema.cpp @@ -70,10 +70,6 @@ utils::result, utils::error> schema::refer return utils::ok((*result)->info().reference_column()); } - if (const auto it = expected_node_map_.find(type_index); it != expected_node_map_.end()) { - return utils::ok(it->second->info().reference_column()); - } - return utils::failure(result.err()); } @@ -81,7 +77,12 @@ void schema::dump(std::ostream &os) const { for (const auto &node : *this) { os << "node [" << node.name() << "] (" << node.type_index().name() << ")\n"; for (auto it = node.info().endpoint_begin(); it != node.info().endpoint_end(); ++it) { - os << " " << node.name() << "::" << it->second->field_name() << " (" << it->second->type_name() << ") <---> " << it->second->foreign_endpoint()->node().name() << "::" << it->second->foreign_endpoint()->field_name() << " (" << it->second->foreign_endpoint()->type_name() << ")\n"; + os << " " << /*node.name() << "::" <<*/ it->second->field_name() << " (" << it->second->type_name() << ")"; + if (it->second->foreign_endpoint()) { + os << " <---> " << it->second->foreign_endpoint()->node().name() << "::" << it->second->foreign_endpoint()->field_name() << " (" << it->second->foreign_endpoint()->type_name() << ")\n"; + } else { + os << "\n"; + } } } } @@ -104,7 +105,7 @@ utils::result schema::attach_node(const std::sha parent_node = *result; } - push_back_child(parent_node, node); + insert_node(parent_node, node); // Todo: check return value nodes_by_name_.insert({node->name(), node})/*.first*/; @@ -123,7 +124,7 @@ utils::result schema::attach_node(const std::sha return result; } - push_back_child(*result, node); + insert_node(*result, node); // Todo: check return value nodes_by_name_.insert({node->name(), node})/*.first*/; @@ -150,7 +151,7 @@ utils::result schema::find_node(const std::type_ return utils::ok(i->second); } -void schema::push_back_child(const node_ptr &parent, const node_ptr &child) { +void schema::insert_node(const node_ptr &parent, const node_ptr &child) { child->parent_ = parent; child->previous_sibling_ = parent->last_child_->previous_sibling_; child->next_sibling_ = parent->last_child_; diff --git a/test/orm/orm/SessionQueryBuilderTest.cpp b/test/orm/orm/SessionQueryBuilderTest.cpp index 024f56b..e617440 100644 --- a/test/orm/orm/SessionQueryBuilderTest.cpp +++ b/test/orm/orm/SessionQueryBuilderTest.cpp @@ -197,8 +197,8 @@ TEST_CASE("Create sql query data for entity with eager many to many", "[query][e connection db("noop://noop.db"); schema scm("noop"); auto result = scm.attach("recipes") - .and_then( [&scm] { return scm.attach("ingredients"); } ) - .and_then( [&scm] { return scm.attach("recipe_ingredients"); } ); + .and_then( [&scm] { return scm.attach("ingredients"); } ); + // .and_then( [&scm] { return scm.attach("recipe_ingredients"); } ); session_query_builder eqb(scm); @@ -242,8 +242,8 @@ TEST_CASE("Create sql query data for entity with eager many to many (inverse par connection db("noop://noop.db"); schema scm("noop"); auto result = scm.attach("students") - .and_then( [&scm] { return scm.attach("courses"); } ) - .and_then( [&scm] { return scm.attach("student_courses"); } ); + .and_then( [&scm] { return scm.attach("courses"); } ); + // .and_then( [&scm] { return scm.attach("student_courses"); } ); session_query_builder eqb(scm);