#ifndef QUERY_ENTITY_QUERY_BUILDER_HPP #define QUERY_ENTITY_QUERY_BUILDER_HPP #include "matador/orm/query_builder_exception.hpp" #include "matador/query/criteria.hpp" #include "matador/query/query.hpp" #include "matador/query/schema.hpp" #include "matador/sql/executor.hpp" #include "matador/sql/statement.hpp" #include "matador/object/join_columns_collector.hpp" #include "matador/object/repository.hpp" #include "matador/query/criteria/criteria_visitor.hpp" #include "matador/utils/primary_key_attribute.hpp" #include "matador/utils/result.hpp" #include "matador/utils/value.hpp" #include #include namespace matador::orm { struct entity_query_data { const query::table* root_table{nullptr}; std::string pk_column_name{}; std::vector columns{}; std::unordered_map lazy_loading_statements{}; std::vector joins{}; query::criteria_ptr where_clause{}; }; class criteria_transformer final : public query::criteria_visitor { public: criteria_transformer(const query::schema &repo, const std::unordered_map& tables_by_name); void visit( const query::between_criteria& node ) override; void visit( const query::binary_criteria& node ) override; void visit( const query::binary_column_criteria& node ) override; void visit( const query::collection_criteria& node ) override; void visit( const query::collection_query_criteria& node ) override; void visit( const query::like_criteria& node ) override; void visit( const query::logical_criteria& node ) override; void visit( const query::not_criteria& node ) override; private: void update_criteria_column(const query::abstract_column_criteria& node) const; private: const query::schema &repo_; const std::unordered_map& tables_by_name_; }; class session_query_builder final { public: session_query_builder(const query::schema &scm, sql::executor &exec) : schema_(scm) , executor_(exec){} template utils::result build(query::criteria_ptr clause = {}) { const auto it = schema_.find(typeid(EntityType)); if (it == schema_.end()) { return utils::failure(query_build_error::UnknownType); } table_info_stack_.push({it->second.node().info(), it->second.table().as(build_alias('t', ++table_index))}); entity_query_data_ = { &table_info_stack_.top().table }; processed_tables_.insert({it->second.name(), *entity_query_data_.root_table}); try { EntityType obj; access::process(*this, obj); if (clause) { criteria_transformer transformer{schema_, processed_tables_}; clause->accept(transformer); entity_query_data_.where_clause = std::move(clause); } return {utils::ok(std::move(entity_query_data_))}; } catch (const query_builder_exception &ex) { return {utils::failure(ex.error_type())}; } catch (...) { return {utils::failure(query_build_error::UnexpectedError)}; } } template < class V > void on_primary_key(const char *id, V &, const utils::primary_key_attribute& /*attr*/ = utils::default_pk_attributes) { push(id); if (!is_root_entity()) { return; } entity_query_data_.pk_column_name = id; } void on_revision(const char *id, uint64_t &/*rev*/); template void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) { push(id); } template void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr) { on_foreign_object(id, obj, attr); } template void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr) { on_foreign_object(id, obj, attr); } template struct NoopDeleter { void operator()(const T*) const {} }; template void on_has_many(const char * /*id*/, ContainerType &, const char *join_column, const utils::foreign_attributes &attr) { if (attr.fetch() != utils::fetch_type::Eager) { return; } const auto it = schema_.find(typeid(typename ContainerType::value_type::value_type)); if (it == schema_.end()) { throw query_builder_exception{query_build_error::UnknownType}; } auto next = processed_tables_.find(it->second.name()); if (next != processed_tables_.end()) { // node already processed return; } table_info_stack_.push({it->second.node().info(), it->second.table().as(build_alias('t', ++table_index))}); next = processed_tables_.insert({it->second.name(), table_info_stack_.top().table}).first; typename ContainerType::value_type::value_type obj; access::process(*this , obj); table_info_stack_.pop(); if (!it->second.node().info().has_primary_key()) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } append_join( query::table_column{&table_info_stack_.top().table, table_info_stack_.top().info.primary_key_attribute()->name()}, query::table_column{&next->second, join_column} ); // const auto result = schema_.repo().basic_info(typeid(typename ContainerType::value_type::value_type)); // if (!result) { // throw query_builder_exception{query_build_error::UnknownType}; // } // // const auto &info = result.value().get(); // auto next = processed_tables_.find(info.name()); // if (next != processed_tables_.end()) { // return; // } // table_info_stack_.push({*result, std::make_shared(info.name(), build_alias('t', ++table_index))}); // next = processed_tables_.insert({info.name(), table_info_stack_.top().table}).first; // typename ContainerType::value_type::value_type obj; // access::process(*this , obj); // table_info_stack_.pop(); // // if (!info.has_primary_key()) { // throw query_builder_exception{query_build_error::MissingPrimaryKey}; // } // // append_join( // query::column{table_info_stack_.top().table.get(), table_info_stack_.top().info.get().primary_key_attribute()->name()}, // query::column{next->second.get(), join_column} // ); } template void on_has_many_to_many(const char *id, ContainerType &/*cont*/, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) { if (attr.fetch() != utils::fetch_type::Eager) { return; } const auto result = schema_.find(typeid(typename ContainerType::value_type::value_type)); if (result == schema_.end()) { throw query_builder_exception{query_build_error::UnknownType}; } auto next = processed_tables_.find(result->second.name()); if (next != processed_tables_.end()) { // attribute was already processed return; } auto relation = processed_tables_.find(id); if (relation == processed_tables_.end()) { const auto it = schema_.find(id); if (it == schema_.end()) { throw query_builder_exception{query_build_error::UnknownType}; } relation = processed_tables_.emplace(id, it->second.table().as(build_alias('t', ++table_index))).first; } table_info_stack_.push({result->second.node().info(), result->second.table().as(build_alias('t', ++table_index))}); next = processed_tables_.insert({result->second.name(), table_info_stack_.top().table}).first; typename ContainerType::value_type::value_type obj; access::process(*this , obj); table_info_stack_.pop(); if (!result->second.node().info().has_primary_key()) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } append_join( query::table_column{&table_info_stack_.top().table, table_info_stack_.top().info.primary_key_attribute()->name()}, query::table_column{&relation->second, join_column} ); append_join( query::table_column{&relation->second, inverse_join_column}, query::table_column{&next->second, result->second.node().info().primary_key_attribute()->name()} ); } template void on_has_many_to_many(const char *id, ContainerType &/*cont*/, const utils::foreign_attributes &attr) { if (attr.fetch() != utils::fetch_type::Eager) { return; } const auto result = schema_.find(typeid(typename ContainerType::value_type::value_type)); if (result == schema_.end()) { throw query_builder_exception{query_build_error::UnknownType}; } auto next = processed_tables_.find(result->second.name()); if (next != processed_tables_.end()) { // attribute was already processed return; } auto relation = processed_tables_.find(id); if (relation == processed_tables_.end()) { const auto it = schema_.find(id); if (it == schema_.end()) { throw query_builder_exception{query_build_error::UnknownType}; } const auto t = it->second.table().as(build_alias('t', ++table_index)); relation = processed_tables_.insert({id, t}).first; } table_info_stack_.push({result->second.node().info(), result->second.table().as(build_alias('t', ++table_index))}); next = processed_tables_.insert({result->second.name(), table_info_stack_.top().table}).first; typename ContainerType::value_type::value_type obj; access::process(*this , obj); table_info_stack_.pop(); if (!result->second.node().info().has_primary_key()) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } const auto join_columns = join_columns_collector_.collect(); append_join( query::table_column{&table_info_stack_.top().table, table_info_stack_.top().info.primary_key_attribute()->name()}, query::table_column{&relation->second, join_columns.inverse_join_column} ); append_join( query::table_column{&relation->second, join_columns.join_column}, query::table_column{&next->second, result->second.node().info().primary_key_attribute()->name()} ); } private: template void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr); void push(const std::string &column_name); static std::string build_alias(char prefix, unsigned int count); [[nodiscard]] bool is_root_entity() const; void append_join(const query::table_column &left, const query::table_column &right); private: struct table_info { const object::basic_object_info &info; query::table table; }; std::stack table_info_stack_{}; std::unordered_map processed_tables_{}; const query::schema &schema_; entity_query_data entity_query_data_{}; unsigned int column_index{0}; unsigned int table_index{0}; object::join_columns_collector join_columns_collector_{}; sql::executor &executor_; }; template void session_query_builder::on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr) { const auto it = schema_.find(typeid(typename Pointer::value_type)); if (it == schema_.end()) { throw query_builder_exception{query_build_error::UnknownType}; } if (!it->second.node().info().has_primary_key()) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } const auto& info = it->second.node().info(); auto foreign_table = it->second.table().as(build_alias('t', ++table_index)); if (attr.fetch() == utils::fetch_type::Eager) { auto next = processed_tables_.find(info.name()); if (next != processed_tables_.end()) { return; } table_info_stack_.push({info, std::move(foreign_table)}); next = processed_tables_.insert({info.name(), table_info_stack_.top().table}).first; typename Pointer::value_type obj; access::process(*this, obj); table_info_stack_.pop(); append_join( query::table_column{&table_info_stack_.top().table, id}, query::table_column{&next->second, info.primary_key_attribute()->name()} ); } else { push(id); using namespace matador::utils; using namespace matador::query; // create select query auto result = matador::query::query::select(generator::columns(schema_, foreign_table, generator::column_generator_options::ForceLazy)) .from(foreign_table) .where(table_column(&foreign_table, info.primary_key_attribute()->name(), "") == _) .prepare(executor_); if (!result) { throw query_builder_exception(query_build_error::QueryError, result.release_error()); } entity_query_data_.lazy_loading_statements.emplace(id, std::move(result.release())); } } } #endif //QUERY_ENTITY_QUERY_BUILDER_HPP