#ifndef QUERY_ENTITY_QUERY_BUILDER_HPP #define QUERY_ENTITY_QUERY_BUILDER_HPP #include "matador/query/condition.hpp" #include "matador/query/query_intermediates.hpp" #include "matador/sql/connection.hpp" #include "matador/object/schema.hpp" #include "matador/utils/result.hpp" #include "matador/utils/value.hpp" #include #include 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; matador::access::process(*this, obj); return join_columns_; } template < class V > void on_primary_key(const char * /*id*/, V &, typename std::enable_if::value && !std::is_same::value>::type* = 0) {} void on_primary_key(const char * /*id*/, std::string &, size_t) {} void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {} template void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} template void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} template void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} template 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 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::string root_table_name; std::string pk_column_; std::vector columns; std::vector joins; std::unique_ptr where_clause; }; enum class query_build_error : std::uint8_t { Ok = 0, UnknownType, MissingPrimaryKey, UnexpectedError }; class query_builder_exception final : public std::exception { public: explicit query_builder_exception(const query_build_error error) : error_(error) {} [[nodiscard]] query_build_error error() const { return error_; } private: const query_build_error error_; }; class session_query_builder final { public: explicit session_query_builder(const object::schema &scm) : schema_(scm) {} template utils::result build(const PrimaryKeyType &pk) { auto info = schema_.info(); if (!info) { return utils::failure(query_build_error::UnknownType); } pk_ = pk; table_info_stack_.push(info.value()); processed_tables_.insert(info->get().name()); entity_query_data_ = { info.value().get().name() }; try { access::process(*this, info.value().get().prototype()); return {utils::ok(std::move(entity_query_data_))}; } catch (const query_builder_exception &ex) { return {utils::failure(ex.error())}; } catch (...) { return {utils::failure(query_build_error::UnexpectedError)}; } } template utils::result build() { const auto info = schema_.info(); if (!info) { return utils::failure(query_build_error::UnknownType); } pk_ = nullptr; table_info_stack_.push(info.value()); entity_query_data_ = { info->name() }; try { access::process(*this, info->prototype()); return {utils::ok(std::move(entity_query_data_))}; } catch (const query_builder_exception &ex) { return {utils::failure(ex.error())}; } catch (...) { return {utils::failure(query_build_error::UnexpectedError)}; } } template < class V > void on_primary_key(const char *id, V &, std::enable_if_t && !std::is_same_v>* = nullptr) { push(id); if (!is_root_entity()) { return; } if (pk_.is_null()) { entity_query_data_.pk_column_ = id; } else if (pk_.is_integer()) { auto t = std::make_shared(table_info_stack_.top().get().name()); auto v = *pk_.as(); auto c = sql::column{t, id, ""}; auto co = std::make_unique>(c, query::basic_condition::operand_type::EQUAL, v); entity_query_data_.where_clause = std::move(co); // entity_query_data_.where_clause = query::make_condition(c == v); entity_query_data_.pk_column_ = id; } } void on_primary_key(const char *id, std::string &, size_t); void on_revision(const char *id, unsigned long long &/*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 void on_has_many(const char * id, ContainerType &, const char *join_column, const utils::foreign_attributes &attr) { if (attr.fetch() == utils::fetch_type::EAGER) { const auto info = schema_.info(); if (!info) { throw query_builder_exception{query_build_error::UnknownType}; } auto curr = table_info_stack_.top().get().name(); auto next = info.value().get().name(); if (processed_tables_.count(next) > 0) { return; } table_info_stack_.push(info.value()); processed_tables_.insert(next); typename ContainerType::value_type::value_type obj; access::process(*this , obj); table_info_stack_.pop(); auto pk = info->get().definition().primary_key(); if (!pk) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } append_join(sql::column{table_info_stack_.top().get().name(), table_info_stack_.top().get().definition().primary_key()->name()}, sql::column{info->get().name(), join_column}); } } 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) { if (attr.fetch() != utils::fetch_type::EAGER) { return; } const auto info = schema_.info(); if (!info) { throw query_builder_exception{query_build_error::UnknownType}; } table_info_stack_.push(info.value()); typename ContainerType::value_type::value_type obj; matador::access::process(*this , obj); table_info_stack_.pop(); auto pk = info->get().definition().primary_key(); if (!pk) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } append_join(sql::column{table_info_stack_.top().get().name(), table_info_stack_.top().get().definition().primary_key()->name()}, {id, join_column}); append_join(sql::column{id, inverse_join_column}, sql::column{info->get().name(), pk->name()}); } template void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr) { if (attr.fetch() != utils::fetch_type::EAGER) { return; } const auto info = schema_.info(); if (!info) { throw query_builder_exception{query_build_error::UnknownType}; } table_info_stack_.push(info.value()); typename ContainerType::value_type::value_type obj; matador::access::process(*this , obj); table_info_stack_.pop(); auto pk = info->get().definition().primary_key(); if (!pk) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } const auto join_columns = join_column_collector_.collect(); append_join(sql::column{table_info_stack_.top().get().name(), table_info_stack_.top().get().definition().primary_key()->name()}, sql::column{id, join_columns.inverse_join_column}); append_join(sql::column{id, join_columns.join_column}, sql::column{info->get().name(), pk->name()}); } private: template void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr); void push(const std::string &column_name); [[nodiscard]] bool is_root_entity() const; void append_join(const sql::column &left, const sql::column &right); private: utils::value pk_; std::stack> table_info_stack_; std::unordered_set processed_tables_; const object::schema &schema_; entity_query_data entity_query_data_; int column_index{0}; join_column_collector join_column_collector_; }; template void session_query_builder::on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr) { if (attr.fetch() == utils::fetch_type::EAGER) { const auto info = schema_.info(); if (!info) { throw query_builder_exception{query_build_error::UnknownType}; } auto curr = table_info_stack_.top().get().name(); auto next = info.value().get().name(); if (processed_tables_.count(next) > 0) { return; } processed_tables_.insert(next); table_info_stack_.push(info.value()); typename Pointer::value_type obj; access::process(*this, obj); table_info_stack_.pop(); auto pk = info->get().definition().primary_key(); if (!pk) { throw query_builder_exception{query_build_error::MissingPrimaryKey}; } append_join(sql::column{table_info_stack_.top().get().name(), id}, sql::column{info->get().name(), pk->name()}); } else { push(id); } } } #endif //QUERY_ENTITY_QUERY_BUILDER_HPP