#ifndef QUERY_SESSION_HPP #define QUERY_SESSION_HPP #include "matador/orm/error_code.hpp" #include "matador/orm/session_query_builder.hpp" #include "matador/query/condition.hpp" #include "matador/query/query.hpp" #include "matador/sql/column_generator.hpp" #include "matador/sql/connection.hpp" #include "matador/sql/connection_pool.hpp" #include "matador/sql/executor.hpp" #include "matador/sql/statement.hpp" #include "matador/sql/statement_cache.hpp" #include "matador/object/object_ptr.hpp" #include "matador/object/object_definition.hpp" #include "matador/object/repository.hpp" #include namespace matador::orm { utils::error make_error(error_code ec, const std::string &msg); struct session_context { utils::message_bus &bus; sql::connection_pool &pool; size_t cache_size{500}; }; template class lazy_object_resolver final : public object::object_resolver { public: explicit lazy_object_resolver(sql::statement &&stmt) : stmt_(std::move(stmt)) {} Type* resolve(const object::object_proxy& proxy) const override { return proxy.resolve(); } private: sql::statement stmt_; }; class prototype_builder final { public: explicit prototype_builder(const std::unordered_map &statements_per_column) : statements_per_column_(statements_per_column) {} template < typename Type > Type build() const { Type obj; access::process(*this, obj); return obj; } template < class V > static void on_primary_key(const char * /*id*/, V &/*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*/, Type &/*obj*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} template void on_belongs_to(const char *id, Pointer &/*obj*/, const utils::foreign_attributes &/*attr*/) { const auto it = statements_per_column_.find(id); if (it == statements_per_column_.end()) { return; } } template void on_has_one(const char * /*id*/, Pointer &/*obj*/, const utils::foreign_attributes &/*attr*/) { } template void on_has_many(const char * /*id*/, ContainerType &, const char *join_column, const utils::foreign_attributes &attr) { } 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) { } template void on_has_many_to_many(const char *id, ContainerType &/*cont*/, const utils::foreign_attributes &attr) { } private: const std::unordered_map &statements_per_column_; }; class session final : public sql::executor { public: explicit session(session_context &&ctx); template [[nodiscard]] utils::result attach(const std::string &table_name) const; template [[nodiscard]] utils::result attach(const std::string &table_name) const; utils::result create_schema() const; /** * Insert the given object into the session. * * @tparam Type Type of object to insert * @param obj Object to insert * @return Inserted object */ template utils::result, utils::error> insert(Type *obj); template< class Type, typename... Args > utils::result, utils::error> insert(Args&&... args); template utils::result, utils::error> update(const object::object_ptr &obj); template utils::result, utils::error> find(const PrimaryKeyType &pk); template utils::result, utils::error> find(); template utils::result drop_table(); utils::result drop_table(const std::string &table_name) const; [[nodiscard]] utils::result, utils::error> fetch_all(const sql::query_context &q) const; [[nodiscard]] utils::result execute(const std::string &sql) const; [[nodiscard]] std::vector describe_table(const std::string &table_name) const; [[nodiscard]] bool table_exists(const std::string &table_name) const; void dump_schema(std::ostream &os) const; [[nodiscard]] utils::result, utils::error> fetch(const sql::query_context &ctx) const override; [[nodiscard]] utils::result execute(const sql::query_context &ctx) const override; [[nodiscard]] utils::result prepare(const sql::query_context &ctx) override; [[nodiscard]] std::string str( const sql::query_context& ctx ) const override; [[nodiscard]] const sql::dialect& dialect() const override; private: friend class query_select; static query::fetchable_query build_select_query(entity_query_data &&data); private: mutable sql::statement_cache cache_; const sql::dialect &dialect_; std::unique_ptr schema_; mutable std::unordered_map prototypes_; }; template [[nodiscard]] utils::result session::attach(const std::string &table_name) const { return schema_->attach(table_name); } template utils::result session::attach( const std::string& table_name ) const { return schema_->attach(table_name); } template utils::result, utils::error> session::insert(Type *obj) { auto info = schema_->info(); if (!info) { return utils::failure(info.err()); } auto res = query::query::insert() .into(info->get().name(), sql::column_generator::generate(*schema_, true)) .values(*obj) .prepare(*this); if (!res) { return utils::failure(res.err()); } if (const auto insert_result = res->bind(*obj).execute(); !insert_result.is_ok()) { return utils::failure(insert_result.err()); } return utils::ok(object::object_ptr{obj}); } template utils::result, utils::error> session::insert( Args&&... args ) { return insert(new Type(std::forward(args)...)); } class update_object_binder final { public: explicit update_object_binder(sql::statement &stmt, size_t position) : stmt_(stmt) , binding_position_(position){} template < class Type > sql::statement& bind(Type &obj) { access::process(*this, obj); return stmt_; } template void on_primary_key(const char * /*id*/, Type &x, const utils::primary_key_attribute& /*attr*/ = utils::default_pk_attributes) { stmt_.bind(binding_position_, x); } static void on_revision(const char * /*id*/, uint64_t &/*rev*/) {} template < class Type > static void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} template < class Pointer > static void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/ = utils::default_foreign_attributes) {} template < class Pointer > static void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/ = utils::default_foreign_attributes) {} template static void on_has_many(const char * /*id*/, ContainerType &/*c*/, const char * /*join_column*/, const utils::foreign_attributes &/*attr*/ = utils::default_foreign_attributes) {} template static 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*/) {} template static void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {} private: sql::statement &stmt_; size_t binding_position_{0}; sql::object_pk_binder pk_binder_; }; template utils::result, utils::error> session::update( const object::object_ptr& obj ) { auto info = schema_->info(); if (!info) { return utils::failure(info.err()); } using namespace matador::utils; using namespace matador::query; const auto col = sql::column(info.value().get().definition().primary_key()->name()); auto res = matador::query::query::update(info->get().name()) .set(*obj) .where(col == _) .prepare(*this); if (!res) { return utils::failure(res.err()); } res->bind(*obj); update_object_binder binder(res.value(), res->bind_pos()); if (const auto update_result = binder.bind(*obj).execute(); !update_result.is_ok()) { return utils::failure(update_result.err()); } return utils::ok(object::object_ptr{obj}); } template utils::result, utils::error> session::find( const PrimaryKeyType& pk ) { auto info = schema_->info(); if (!info) { return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); } session_query_builder eqb(*schema_, *this); auto data = eqb.build(pk); if (!data.is_ok()) { return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build query for type " + info->get().name() + ".")); } auto res = build_select_query(data.release()).prepare(*this); if (!res) { return utils::failure(res.err()); } auto stmt_result = res->bind(0, const_cast(pk)).template fetch_one(); if (stmt_result && !stmt_result.value()) { return utils::failure(make_error(error_code::FailedToFindObject, "Failed to find object of type " + info->get().name() + " with primary key " + std::to_string(pk) + ".")); } return utils::ok(object::object_ptr{ stmt_result->release() }); } template utils::result, utils::error> session::find() { auto info = schema_->info(); if (!info) { return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); } session_query_builder eqb(*schema_, *this); auto data = eqb.build(); if (!data.is_ok()) { return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build query for type " + info->get().name() + ".")); } auto result = build_select_query(data.release()).prepare(*this); if (!result.is_ok()) { return utils::failure(result.err()); } return result->template fetch(); } template utils::result session::drop_table() { auto info = schema_->info(); if (info) { return drop_table(info->get().name()); } return utils::failure(info.err()); } } #endif //QUERY_SESSION_HPP