diff --git a/demo/sandbox.cpp b/demo/sandbox.cpp index 867832a..f7d208b 100644 --- a/demo/sandbox.cpp +++ b/demo/sandbox.cpp @@ -98,6 +98,30 @@ struct user { field::has_one(op, "profile_id", profile, matador::utils::default_foreign_attributes ); } }; + +struct person { + unsigned int id{}; + std::string name; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::attribute( op, "name", name, 255 ); + } +}; + +struct person_repo { + unsigned int id{}; + matador::object::collection> person_list; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::has_many( op, "person_list", person_list, "person_id", matador::utils::default_foreign_attributes ); + } +}; } using namespace demo; @@ -107,66 +131,84 @@ int main() { // logger::default_min_log_level(logger::log_level::LVL_DEBUG); // 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 with builtin-type + // has_many with foreign without belongs_to object::schema schema; - auto result = schema.attach("names"); + auto result = schema.attach("person_repo") + .and_then([&schema] { return schema.attach("persons"); }); schema.dump(std::cout); } { - // has_many to belongs_to + // has_many with foreign without belongs_to object::schema schema; - auto result = schema.attach("authors") - .and_then([&schema] { return schema.attach("books"); }); - - schema.dump(std::cout); - } - { - // belongs_to to has_many - object::schema schema; - - auto result = schema.attach("books") - .and_then([&schema] { return schema.attach("authors"); }); - - 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"); }); + auto result = schema.attach("persons") + .and_then([&schema] { return schema.attach("person_repo"); }); schema.dump(std::cout); } + // { + // // has_many to belongs_to + // object::schema schema; + // + // auto result = schema.attach("authors") + // .and_then([&schema] { return schema.attach("books"); }); + // + // schema.dump(std::cout); + // } + // { + // // belongs_to to has_many + // object::schema schema; + // + // auto result = schema.attach("books") + // .and_then([&schema] { return schema.attach("authors"); }); + // + // 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 4eb1c26..df14c7b 100644 --- a/demo/work.cpp +++ b/demo/work.cpp @@ -70,13 +70,13 @@ using namespace work::models; // payload.is_polymorphic_type(); int main() { - logger::default_min_log_level(logger::log_level::LVL_DEBUG); - logger::add_log_sink(logger::create_stdout_sink()); + // logger::default_min_log_level(logger::log_level::LVL_DEBUG); + // logger::add_log_sink(logger::create_stdout_sink()); const object::schema schema("Administration"); - sql::connection_pool pool("postgres://news:news@127.0.0.1:15432/matador", 4); - // sql::connection_pool pool("postgres://test:test123!@127.0.0.1:5432/matador", 4); + // sql::connection_pool pool("postgres://news:news@127.0.0.1:15432/matador", 4); + sql::connection_pool pool("postgres://test:test123!@127.0.0.1:5432/matador", 4); orm::session ses(pool); diff --git a/demo/work/admin/CollectionCenter.hpp b/demo/work/admin/CollectionCenter.hpp index 0d090a8..aedf89f 100644 --- a/demo/work/admin/CollectionCenter.hpp +++ b/demo/work/admin/CollectionCenter.hpp @@ -39,7 +39,7 @@ struct CollectionCenter : core::Model { field::attribute( op, "name", name, 511 ); field::attribute( op, "symbol", symbol ); field::attribute( op, "type", type ); - field::has_many( op, "collection_center_users", users, "collection_center_id", matador::utils::fetch_type::LAZY ); + field::has_many( op, "collection_center_users", users, "users_id", matador::utils::fetch_type::LAZY ); } }; diff --git a/include/matador/object/relation_completer.hpp b/include/matador/object/relation_completer.hpp index 66771d2..338d77c 100644 --- a/include/matador/object/relation_completer.hpp +++ b/include/matador/object/relation_completer.hpp @@ -9,10 +9,55 @@ #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 @@ -110,6 +155,18 @@ private: 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 &, @@ -118,7 +175,7 @@ void relation_completer::on_has_many(const char *id, CollectionType &, 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; + 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)); @@ -126,17 +183,79 @@ void relation_completer::on_has_many(const char *id, CollectionType &, // Todo: throw internal error or attach node return; } - auto local_it = nodes_.top()->info().find_relation_endpoint(typeid(value_type)); - if (local_it == nodes_.top()->info().endpoint_end()) { + 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()); - 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); + 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()) { @@ -308,13 +427,16 @@ void relation_completer::on_belongs_to(const char *id, // 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()) { + 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); + 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 diff --git a/include/matador/object/schema.hpp b/include/matador/object/schema.hpp index 717a359..7678d0d 100644 --- a/include/matador/object/schema.hpp +++ b/include/matador/object/schema.hpp @@ -39,6 +39,7 @@ public: template [[nodiscard]] utils::result attach(const std::string &name, const std::string &parent = "") { if (const auto it = nodes_by_type_.find(typeid(Type)); it == nodes_by_type_.end() ) { + // if the type was not found auto node = schema_node::make_node(*this, name); if (auto result = attach_node(node, parent); !result) { return utils::failure(result.err()); @@ -148,6 +149,7 @@ public: const std::type_index &type_index) const; void dump(std::ostream &os) const; + static void dump(std::ostream &os, const node_ptr& node); private: using t_node_map = std::unordered_map; diff --git a/source/core/object/schema.cpp b/source/core/object/schema.cpp index 8fc7e23..3070408 100644 --- a/source/core/object/schema.cpp +++ b/source/core/object/schema.cpp @@ -64,16 +64,21 @@ utils::result, utils::error> schema::refer 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() << ")"; - 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 << "\n"; - } - } + dump(os, node); } + os << "\n"; +} + +void schema::dump( std::ostream& os, const node_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().name() << " (type: " << it->second->node().type_index().name() << ")\n"; + } + } } utils::result schema::attach_node(const std::shared_ptr &node, diff --git a/source/orm/orm/session.cpp b/source/orm/orm/session.cpp index 56674cf..3bd3c36 100644 --- a/source/orm/orm/session.cpp +++ b/source/orm/orm/session.cpp @@ -25,13 +25,12 @@ utils::result session::create_schema() const { for (const auto &node: *schema_) { for (auto it = node->info().endpoint_begin(); it != node->info().endpoint_end(); ++it) { + std::cout << "Dependency graph " << node->name() << " (" << node.get() << ")" << " -> " << it->second->node_ptr()->name() << " (" << it->second->node_ptr().get() << ")" << std::endl; dependency_graph[node->name()].push_back(it->second->node().name()); - auto n = it->second->node_ptr(); - auto nn = node->name(); - if (it->second->is_has_one()) { - continue; - } + // if (it->second->is_has_many()) { + // continue; + // } if (const auto dit = in_degree.find(it->second->node().name()); dit == in_degree.end()) { in_degree[it->second->node().name()] = std::make_pair(1, it->second->node_ptr()); } else { @@ -62,9 +61,9 @@ utils::result session::create_schema() const { } } - for (const auto &it : in_degree) { - std::cout << "In degree table " << it.second.second->name() << " (" << it.second.first << ")" << std::endl; - } + for (const auto &it : in_degree) { + std::cout << "In degree table " << it.second.second->name() << " (" << it.second.first << ")" << std::endl; + } while (!zero_in_degree.empty()) { @@ -84,25 +83,21 @@ utils::result session::create_schema() const { // Step 3: Check for cycles if (sorted_order.size() != in_degree.size()) { + std::cout << "Cycle detected in table dependencies (sorted order size: " << sorted_order.size() << ", in degree size: " << in_degree.size() << ")" << std::endl; // throw std::logic_error("Cycle detected in table dependencies"); } // Step 4: Create tables in the sorted order + auto c = pool_.acquire(); for (const auto &node : sorted_order) { std::cout << "Creating table " << node->name() << std::endl; - // schema_. - // auto result = query::query::create() - // - // .table(table_name, /* Pass table definition here */) - // - // .execute(*c); - // - // if (!result) { - // - // return utils::failure(result.err()); - // - // } + // auto result = query::query::create() + // .table(node->name(), node->info().definition().columns()) + // .execute(*c); + // if (!result) { + // return utils::failure(result.err()); + // } } // auto c = pool_.acquire();