#ifndef RELATION_COMPLETER_HPP #define RELATION_COMPLETER_HPP #include "matador/object/internal/shadow_schema.hpp" #include "matador/object/many_to_many_relation.hpp" #include "matador/object/join_columns_collector.hpp" #include "matador/object/object_ptr.hpp" #include "matador/object/schema_node.hpp" #include "matador/logger/log_manager.hpp" #include #include #include namespace matador::object { class join_column_finder final { public: template static bool has_join_column(const std::string &join_column) { join_column_finder finder(join_column); Type obj; finder.found_ = false; access::process(finder, obj); return finder.found_; } template static void on_primary_key(const char * /*id*/, PrimaryKeyType &/*pk*/, std::enable_if_t && !std::is_same_v> * = nullptr) {} static void on_primary_key(const char * /*id*/, std::string &/*pk*/, size_t /*size*/) {} 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*/) { found_ = requested_join_column_ == id; } template static void on_has_one(const char * /*id*/, ForeignPointerType &/*obj*/, const utils::foreign_attributes &/*attr*/) {} template static void on_has_many(const char *id, CollectionType &, const char *join_column, const utils::foreign_attributes &attr) {} template static 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 static void on_has_many_to_many(const char * /*id*/, CollectionType & /*collection*/, const utils::foreign_attributes &/*attr*/) {} private: explicit join_column_finder(std::string join_column) : requested_join_column_(std::move(join_column)) {} private: std::string requested_join_column_; bool found_ = false; }; /* * 1. has_many (MM) * no belongs to * relation table is needed * - element type is a foreign table (FT), * then relation table must look like follows: * relation_table * where MM and FT must be defined as belongs to * - element type if a builtin type BT (i.e. string, int, etc.), * then the relation table must look like follows: * relation_table * where MM as belongs to and BT as given type * * 2. has_many_to_many (MM1, MM2) * relation_table is needed * relation_table * where MM1 and MM2 must be defined as belongs to * * 3. hans_many (MM) <-> belongs_to (BT) * belongs_to has foreign key to the has_many side * no relation table needed * * 4. has_one to belongs_to * no relation table is needed * * 5. has_many (MM) <-> has_one (HO) * invalid relation -> error * * 6. has_one * no has_many or belongs_to * invalid relation -> error */ template class relation_completer final { private: using node_ptr = std::shared_ptr; public: using endpoint_ptr = std::shared_ptr; static void complete(const std::shared_ptr &node) { internal::shadow_schema shadow(node->schema_); relation_completer completer(shadow); completer.complete_node_relations(node); } template static void on_primary_key(const char * /*id*/, PrimaryKeyType &/*pk*/, std::enable_if_t && !std::is_same_v> * = nullptr) {} static void on_primary_key(const char * /*id*/, std::string &/*pk*/, size_t /*size*/) {} 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 relation_completer(internal::shadow_schema &shadow) : schema_(shadow) , log_(logger::create_logger("relation_completer")) { } void complete_node_relations(const std::shared_ptr &node) { nodes_.push(node); Type obj; access::process(*this, obj); nodes_.pop(); } 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: std::stack nodes_; internal::shadow_schema &schema_; logger::logger log_; join_columns_collector join_columns_collector_{}; }; inline void dump(std::ostream &os, const std::shared_ptr &node) { 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() << ")"; if (it->second->foreign_endpoint()) { os << " <---> " << it->second->node().name() << "::" << it->second->foreign_endpoint()->field_name() << " (" << it->second->foreign_endpoint()->type_name() << ")\n"; } else { os << " -> " << it->second->node().type_index().name() << "\n"; } } } 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*/) { // Shortcut to a value type of object_ptr::value_type in a collection using value_type = typename CollectionType::value_type::value_type; using relation_value_type = many_to_many_relation; // Check if the object_ptr type is already inserted in the schema (by id) auto result = schema_.find_node(typeid(value_type)); if (!result) { // Todo: throw internal error or attach node return; } const auto foreign_node = result.value(); // has foreign node corresponding join column (join_column) if (const auto it = foreign_node->info_->find_relation_endpoint(typeid(Type)); it != foreign_node->info().endpoint_end()) { // corresponding belongs_to is available and was called (has_many <-> belongs_to) // complete the relation const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, foreign_node); nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); link_relation_endpoints(local_endpoint, it->second); } else if (join_column_finder::has_join_column(join_column)) { // corresponding belongs_to is available but was not called (has_many <-> belongs_to) // prepare the relation const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, foreign_node); nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); } else { // A relation table is necessary // } else if (const auto endpoint = nodes_.top()->info().find_relation_endpoint(typeid(relation_value_type)); endpoint == nodes_.top()->info().endpoint_end()) { // Endpoint was not found. // Always attach a many-to-many relation type. If later a // belongs-to relation handles this relation, the many-to-many // relation is maybe detached. log_.debug("node '%s' has has many foreign keys '%s' mapped by '%s'", nodes_.top()->name().c_str(), id, join_column); result = schema_node::make_relation_node( schema_.schema(), id, [join_column] { return std::make_unique("id", join_column); }); if (!result) { // Todo: throw internal error return; } const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, result.value()); const auto foreign_endpoint = std::make_shared("id", relation_type::BELONGS_TO, nodes_.top()); nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); result.value()->info_->register_relation_endpoint(nodes_.top()->type_index(), foreign_endpoint); link_relation_endpoints(local_endpoint, foreign_endpoint); const auto foreign_value_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, foreign_node); result.value()->info_->register_relation_endpoint(typeid(value_type), foreign_value_endpoint); // dump( std::cout, nodes_.top() ); // dump( std::cout, result.value() ); // if (const auto detach_result = schema_.detach_node(foreign_node); !detach_result) { // // Todo: throw internal error // return; // } } // } else { // if (const auto rit = foreign_node->info_->find_relation_endpoint(nodes_.top()->type_index()); // rit != foreign_node->info().endpoint_end()) { // if (rit->second->is_belongs_to()) { // rit->second->node_ = foreign_node; // const auto localEndpoint = std::make_shared(id, relation_type::HAS_MANY, nodes_.top()); // nodes_.top()->info_->register_relation_endpoint(nodes_.top()->type_index(), localEndpoint); // link_relation_endpoints(localEndpoint, rit->second); // } else { // // Todo: throw internal error relation node has invalid type // } // } else { // // Todo: throw internal error couldn't find relation node // } // } // auto local_it = nodes_.top()->info().find_relation_endpoint(typeid(value_type)); // if (local_it == nodes_.top()->info().endpoint_end()) { // const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, result.value()); // local_it = nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); // } // auto foreign_it = result.value()->info().find_relation_endpoint(typeid(value_type)); // if (foreign_it == result.value()->info().endpoint_end()) { // const auto foreign_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, nodes_.top()); // foreign_it = result.value()->info_->register_relation_endpoint(typeid(Type), foreign_endpoint); // } // link_relation_endpoints(local_it->second, foreign_it->second); /* if (const auto endpoint = nodes_.top()->info().find_relation_endpoint(typeid(relation_value_type)); endpoint == nodes_.top()->info().endpoint_end()) { // Endpoint was not found log_.debug("node '%s' has has many foreign keys '%s' mapped by '%s'", nodes_.top()->name().c_str(), id, join_column); result = schema_node::make_relation_node( schema_.schema(), id, [join_column] { return std::make_unique("id", join_column); }); if (!result) { // Todo: throw internal error return; } const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, nodes_.top()); const auto foreign_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, result.value()); nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); foreign_endpoint->node_->info_->register_relation_endpoint(nodes_.top()->type_index(), foreign_endpoint); link_relation_endpoints(local_endpoint, foreign_endpoint); } else { const auto &foreign_node = result.value(); if (const auto rit = foreign_node->info_->find_relation_endpoint(nodes_.top()->type_index()); rit != foreign_node->info().endpoint_end()) { if (rit->second->is_belongs_to()) { rit->second->node_ = foreign_node; const auto localEndpoint = std::make_shared(id, relation_type::HAS_MANY, nodes_.top()); nodes_.top()->info_->register_relation_endpoint(nodes_.top()->type_index(), localEndpoint); link_relation_endpoints(localEndpoint, rit->second); } else { // Todo: throw internal error relation node has invalid type } } else { // Todo: throw internal error couldn't find relation node } } */ } 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*/) { using value_type = typename CollectionType::value_type; using relation_value_type = many_to_relation; const auto result = schema_node::make_relation_node( schema_.schema(), id, [join_column] { return std::make_unique(join_column, "value"); }); if (!result) { // Todo: throw internal exception } // const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, nodes_.top()); // const auto foreign_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, result.value()); const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, result.value()); const auto foreign_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, nodes_.top()); nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); result.value()->info_->register_relation_endpoint(nodes_.top()->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*/) { using relation_value_type = many_to_many_relation; using value_type = typename CollectionType::value_type::value_type; // Check if the object_ptr type is already inserted in the schema (by id) auto result = schema_.find_node(typeid(value_type)); if (!result) { // Todo: throw internal error or attach node return; } const auto foreign_node = result.value(); result = schema_.find_node(id); if (!result) { // Relation not found. auto creator = [join_column, inverse_join_column] { return std::make_unique(join_column, inverse_join_column); }; result = schema_node::make_relation_node(schema_.schema(), id, std::move(creator)); if (!result) { // Todo: throw internal error return; } auto& node = result.value(); const auto local_endpoint = std::make_shared(id, relation_type::HAS_MANY, node); const auto join_endpoint = std::make_shared(join_column, relation_type::BELONGS_TO, nodes_.top()); const auto inverse_join_endpoint = std::make_shared(inverse_join_column, relation_type::BELONGS_TO, foreign_node); const auto foreign_endpoint = std::make_shared(id, relation_type::HAS_MANY, node); // register relation endpoint in local node nodes_.top()->info_->register_relation_endpoint(typeid(typename CollectionType::value_type::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(typename CollectionType::value_type::value_type), inverse_join_endpoint); // link endpoints link_relation_endpoints(local_endpoint, join_endpoint); link_relation_endpoints(foreign_endpoint, inverse_join_endpoint); } } template template void relation_completer::on_has_many_to_many(const char *id, CollectionType &collection, const utils::foreign_attributes &attr) { const auto join_columns = join_columns_collector_.collect(); on_has_many_to_many( id, collection, join_columns.inverse_join_column.c_str(), join_columns.join_column.c_str(), attr); } template template void relation_completer::on_has_one(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)); const auto result = schema_.find_node(ti); if (!result) { // Node was not found // Todo: Throw internal error return; } auto local_it = nodes_.top()->info().find_relation_endpoint(typeid(value_type)); if (local_it == nodes_.top()->info().endpoint_end()) { const auto local_endpoint = std::make_shared(id, relation_type::HAS_ONE, result.value()); local_it = nodes_.top()->info_->register_relation_endpoint(typeid(value_type), local_endpoint); } if (const auto foreign_it = result.value()->info().find_relation_endpoint(typeid(Type)); foreign_it != result.value()->info().endpoint_end()) { link_relation_endpoints(local_it->second, foreign_it->second); } } template template 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)); auto result = schema_.find_node(ti); if (!result) { // Type was not found // Todo: Throw internal error return; } // Type was found const auto &foreign_node = result.value(); // Check foreign node and relation endpoint if (const auto it = foreign_node->info_->find_relation_endpoint(nodes_.top()->type_index()); it != foreign_node->info().endpoint_end()) { // Found corresponding relation endpoint in the foreign node if (it->second->is_has_one()) { const auto endpoint = std::make_shared(id, relation_type::BELONGS_TO, foreign_node); nodes_.top()->info_->register_relation_endpoint(ti, endpoint); link_relation_endpoints(endpoint, it->second); } else if (it->second->is_has_many()) { const auto endpoint = std::make_shared(id, relation_type::BELONGS_TO, foreign_node); nodes_.top()->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 // (detach), and the endpoints must be adjusted const auto foreign_endpoint = it->second->foreign_endpoint(); const auto detach_result = schema_.detach_node(foreign_endpoint->node_); foreign_endpoint->node_ = foreign_node; // foreign_endpoint->node_ = nodes_.top(); nodes_.top()->info_->register_relation_endpoint(nodes_.top()->type_index(), foreign_endpoint); } else { // check type } } else { // Relation node was not found, create only endpoint. const auto endpoint = std::make_shared(id, relation_type::BELONGS_TO, foreign_node); nodes_.top()->info_->register_relation_endpoint(ti, endpoint); } } template void relation_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 relation_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 //RELATION_COMPLETER_HPP