#ifndef FOREIGN_NODE_COMPLETER_HPP #define FOREIGN_NODE_COMPLETER_HPP #include "matador/object/internal/shadow_repository.hpp" #include "matador/object/many_to_many_relation.hpp" #include "matador/object/repository_node.hpp" #include "matador/object/join_columns_collector.hpp" #include "matador/object/object_ptr.hpp" #include "matador/utils/primary_key_attribute.hpp" #include "matador/logger/log_manager.hpp" #include #include "repository.hpp" namespace matador::object { /** * Processes the given node and ensures * that all foreign nodes needed by the given node * relations are attached in schema. */ template class foreign_node_completer final { private: using node_ptr = std::shared_ptr; public: template static void complete(const std::shared_ptr &node) { internal::shadow_repository shadow(node->repo_); foreign_node_completer completer(shadow); completer.complete_node(node); } template static void on_primary_key(const char * /*id*/, PrimaryKeyType &/*pk*/, const utils::primary_key_attribute & /*attr*/ = utils::default_pk_attributes) {} static void on_revision(const char * /*id*/, uint64_t &/*rev*/) {} template static void on_attribute(const char * /*id*/, AttributeType &/*val*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} template static void on_attribute(const char * /*id*/, std::optional &/*val*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} template void on_belongs_to(const char *id, ForeignPointerType &/*obj*/, const utils::foreign_attributes &/*attr*/); template void on_has_one(const char * /*id*/, ForeignPointerType &/*obj*/, const utils::foreign_attributes &/*attr*/); 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(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 &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, CollectionType &collection, const utils::foreign_attributes &attr); private: explicit foreign_node_completer(internal::shadow_repository &shadow) : repo_(shadow) , log_(logger::create_logger("node_completer")) {} void complete_node(const std::shared_ptr &node) { nodes_.push(node); NodeType obj; access::process(*this, obj); nodes_.pop(); } template void attach_node(const std::string &name = "") { if (repo_.contains(typeid(Type))) { return; } const auto node = repository_node::make_node(repo_.repo(), name); if (auto result = repo_.attach_node(node)) { complete(result.value()); } } template void attach_relation_node(const std::string &name, const std::string &join_column, const std::string &inverse_join_column) { using relation_value_type = many_to_many_relation; using value_type = Type; // Check if the object_ptr type is already inserted in the schema (by id) auto result = repo_.find_node(typeid(value_type)); if (!result) { // Todo: throw internal error or attach node return; } const auto foreign_node = result.value(); result = repo_.find_node(name); if (result) { return; } // Relation does not exist. Create it. auto creator = [join_column, inverse_join_column] { return std::make_unique(join_column, inverse_join_column); }; auto node = repository_node::make_node(repo_.repo(), name, std::move(creator)); result = repo_.attach_node(node); if (!result) { // Todo: throw internal error return; } complete(result.value()); // auto& node = result.value(); const auto local_endpoint = std::make_shared(name, relation_type::HasMany, node); const auto join_endpoint = std::make_shared(join_column, relation_type::BelongsTo, nodes_.top()); const auto inverse_join_endpoint = std::make_shared(inverse_join_column, relation_type::BelongsTo, foreign_node); const auto foreign_endpoint = std::make_shared(name, relation_type::HasMany, node); // register relation endpoint in local node nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); // register relation endpoint in foreign node foreign_node->info_->register_relation_endpoint(nodes_.top()->type_index(), foreign_endpoint); // register endpoints in relation node node->info_->register_relation_endpoint(nodes_.top()->type_index(), join_endpoint); node->info_->register_relation_endpoint(typeid(value_type), inverse_join_endpoint); // link endpoints link_relation_endpoints(local_endpoint, join_endpoint); link_relation_endpoints(foreign_endpoint, inverse_join_endpoint); } private: using endpoint_ptr = std::shared_ptr; static void register_relation_endpoints(const endpoint_ptr &endpoint, const endpoint_ptr &other_endpoint); static void link_relation_endpoints(const endpoint_ptr &endpoint, const endpoint_ptr &other_endpoint); private: template friend class foreign_node_completer; private: std::stack nodes_; internal::shadow_repository &repo_; logger::logger log_; join_columns_collector join_columns_collector_{}; }; template template void foreign_node_completer::on_belongs_to(const char * /*id*/, ForeignPointerType &, const utils::foreign_attributes &) { attach_node(); } template template void foreign_node_completer::on_has_one(const char *, ForeignPointerType &, const utils::foreign_attributes &) { attach_node(); } template template void foreign_node_completer::on_has_many(const char * /*id*/, CollectionType &, const char * /*join_column*/, const utils::foreign_attributes & /*attr*/, std::enable_if_t::value> *) { attach_node(); } template template void foreign_node_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*/) { if (!repo_.expecting_relation_node(id)) { repo_.expect_relation_node(id, typeid(typename CollectionType::value_type::value_type)); } else { attach_relation_node(id, join_column, inverse_join_column); repo_.remove_expected_relation_node(id); } } template template void foreign_node_completer::on_has_many_to_many(const char *id, CollectionType & /*collection*/, const utils::foreign_attributes & /*attr*/) { if (!repo_.expecting_relation_node(id)) { repo_.expect_relation_node(id, typeid(typename CollectionType::value_type::value_type)); } else { const auto join_columns = join_columns_collector_.collect(); attach_relation_node(id, join_columns.join_column, join_columns.inverse_join_column); repo_.remove_expected_relation_node(id); } } template void foreign_node_completer::register_relation_endpoints(const endpoint_ptr &endpoint, const endpoint_ptr &other_endpoint) { endpoint->node_->info_->register_relation_endpoint(other_endpoint->node_->type_index(), endpoint); other_endpoint->node_->info_->register_relation_endpoint(endpoint->node_->type_index(), other_endpoint); link_relation_endpoints(endpoint, other_endpoint); } template void foreign_node_completer::link_relation_endpoints(const endpoint_ptr &endpoint, const endpoint_ptr &other_endpoint) { endpoint->link_foreign_endpoint(other_endpoint); other_endpoint->link_foreign_endpoint(endpoint); } } #endif //FOREIGN_NODE_COMPLETER_HPP