356 lines
13 KiB
C++
356 lines
13 KiB
C++
#ifndef QUERY_SESSION_HPP
|
|
#define QUERY_SESSION_HPP
|
|
|
|
#include "matador/orm/error_code.hpp"
|
|
#include "matador/orm/session_query_builder.hpp"
|
|
|
|
#include "matador/query/criteria.hpp"
|
|
#include "matador/query/query.hpp"
|
|
#include "matador/query/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 <unordered_map>
|
|
|
|
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<typename Type>
|
|
class lazy_object_resolver final : public object::object_resolver<Type> {
|
|
public:
|
|
explicit lazy_object_resolver(sql::statement &&stmt) : stmt_(std::move(stmt)) {}
|
|
|
|
Type* resolve(const object::object_proxy<Type>& proxy) const override {
|
|
return proxy.resolve();
|
|
}
|
|
|
|
private:
|
|
sql::statement stmt_;
|
|
};
|
|
|
|
class prototype_builder final {
|
|
public:
|
|
explicit prototype_builder(const std::unordered_map<std::string, sql::statement> &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<typename Type>
|
|
static void on_attribute(const char * /*id*/, Type &/*obj*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
|
template<class Pointer>
|
|
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<class Pointer>
|
|
void on_has_one(const char * /*id*/, Pointer &/*obj*/, const utils::foreign_attributes &/*attr*/) {
|
|
|
|
}
|
|
template<class ContainerType>
|
|
void on_has_many(const char * /*id*/, ContainerType &, const char *join_column, const utils::foreign_attributes &attr) {
|
|
}
|
|
template<class ContainerType>
|
|
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<class ContainerType>
|
|
void on_has_many_to_many(const char *id, ContainerType &/*cont*/, const utils::foreign_attributes &attr) {
|
|
|
|
}
|
|
|
|
private:
|
|
const std::unordered_map<std::string, sql::statement> &statements_per_column_;
|
|
};
|
|
class session final : public sql::executor {
|
|
public:
|
|
explicit session(session_context &&ctx);
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &table_name) const;
|
|
template<typename Type, typename SuperType>
|
|
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &table_name) const;
|
|
|
|
utils::result<void, utils::error> 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<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> insert(Type *obj);
|
|
template< class Type, typename... Args >
|
|
utils::result<object::object_ptr<Type>, utils::error> insert(Args&&... args);
|
|
|
|
template<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> update(const object::object_ptr<Type> &obj);
|
|
template<typename Type>
|
|
utils::result<void, utils::error> remove(const object::object_ptr<Type> &obj);
|
|
|
|
template<typename Type, typename PrimaryKeyType>
|
|
utils::result<object::object_ptr<Type>, utils::error> find(const PrimaryKeyType &pk);
|
|
template<typename Type>
|
|
utils::result<sql::query_result<Type>, utils::error> find(query::criteria_ptr clause = {});
|
|
|
|
template<typename Type>
|
|
utils::result<void, utils::error> drop_table();
|
|
utils::result<void, utils::error> drop_table(const std::string &table_name) const;
|
|
|
|
[[nodiscard]] utils::result<sql::query_result<sql::record>, utils::error> fetch_all(const sql::query_context &q) const;
|
|
[[nodiscard]] utils::result<size_t, utils::error> execute(const std::string &sql) const;
|
|
|
|
[[nodiscard]] std::vector<object::attribute_definition> 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<std::unique_ptr<sql::query_result_impl>, utils::error> fetch(const sql::query_context &ctx) const override;
|
|
[[nodiscard]] utils::result<size_t, utils::error> execute(const sql::query_context &ctx) const override;
|
|
[[nodiscard]] utils::result<sql::statement, utils::error> 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<object::repository> schema_;
|
|
mutable std::unordered_map<std::string, object::object_definition> prototypes_;
|
|
};
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] utils::result<void, utils::error> session::attach(const std::string &table_name) const {
|
|
return schema_->attach<Type>(table_name);
|
|
}
|
|
|
|
template<typename Type, typename SuperType>
|
|
utils::result<void, utils::error> session::attach( const std::string& table_name ) const {
|
|
return schema_->attach<Type, SuperType>(table_name);
|
|
}
|
|
|
|
template<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::insert(Type *obj) {
|
|
auto info = schema_->info<Type>();
|
|
if (!info) {
|
|
return utils::failure(info.err());
|
|
}
|
|
|
|
auto res = query::query::insert()
|
|
.into(info->get().name(), query::generator::columns<Type>(*schema_))
|
|
.values(query::generator::placeholders<Type>())
|
|
.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<class Type, typename ... Args>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::insert( Args&&... args ) {
|
|
return insert(new Type(std::forward<Args>(args)...));
|
|
}
|
|
|
|
class pk_object_binder final {
|
|
public:
|
|
explicit pk_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<class Type>
|
|
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<class ContainerType>
|
|
static void on_has_many(const char * /*id*/,
|
|
ContainerType &/*c*/,
|
|
const char * /*join_column*/,
|
|
const utils::foreign_attributes &/*attr*/ = utils::default_foreign_attributes) {}
|
|
template<class ContainerType>
|
|
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<class ContainerType>
|
|
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<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::update( const object::object_ptr<Type>& obj ) {
|
|
auto info = schema_->info<Type>();
|
|
if (!info) {
|
|
return utils::failure(info.err());
|
|
}
|
|
using namespace matador::utils;
|
|
using namespace matador::query;
|
|
|
|
const auto col = column(info.value().get().definition().primary_key()->name());
|
|
auto res = matador::query::query::update(info->get().name())
|
|
.set(generator::column_value_pairs<Type>())
|
|
.where(col == _)
|
|
.prepare(*this);
|
|
if (!res) {
|
|
return utils::failure(res.err());
|
|
}
|
|
|
|
res->bind(*obj);
|
|
pk_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<typename Type>
|
|
utils::result<void, utils::error> session::remove( const object::object_ptr<Type>& obj ) {
|
|
auto info = schema_->info<Type>();
|
|
if (!info) {
|
|
return utils::failure(info.err());
|
|
}
|
|
using namespace matador::utils;
|
|
using namespace matador::query;
|
|
|
|
const auto col = column(info.value().get().definition().primary_key()->name());
|
|
auto res = matador::query::query::remove()
|
|
.from( info->get().name() )
|
|
.where(col == _)
|
|
.prepare(*this);
|
|
if (!res) {
|
|
return utils::failure(res.err());
|
|
}
|
|
|
|
pk_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<void>();
|
|
}
|
|
|
|
template<typename Type, typename PrimaryKeyType>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::find(const PrimaryKeyType& pk) {
|
|
auto info = schema_->info<Type>();
|
|
if (!info) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
if (!info.value().get().definition().has_primary_key()) {
|
|
return utils::failure(make_error(error_code::NoPrimaryKey, "Type hasn't primary key."));
|
|
}
|
|
|
|
const auto& type_info = info.value().get();
|
|
|
|
session_query_builder eqb(*schema_, *this);
|
|
const query::column col(query::table{type_info.reference_column()->table_name()}, type_info.reference_column()->name());
|
|
using namespace matador::query;
|
|
auto data = eqb.build<Type>(col == utils::_);
|
|
if (!data.is_ok()) {
|
|
return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build query for type " + type_info.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<PrimaryKeyType&>(pk)).template fetch_one<Type>();
|
|
if (stmt_result && !stmt_result.value()) {
|
|
return utils::failure(make_error(error_code::FailedToFindObject, "Failed to find object of type " + type_info.name() + " with primary key " + std::to_string(pk) + "."));
|
|
}
|
|
return utils::ok(object::object_ptr<Type>{ stmt_result->release() });
|
|
}
|
|
|
|
template<typename Type>
|
|
utils::result<sql::query_result<Type>, utils::error> session::find(query::criteria_ptr clause) {
|
|
auto info = schema_->info<Type>();
|
|
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<Type>(std::move(clause));
|
|
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<Type>();
|
|
}
|
|
|
|
template<typename Type>
|
|
utils::result<void, utils::error> session::drop_table() {
|
|
auto info = schema_->info<Type>();
|
|
if (info) {
|
|
return drop_table(info->get().name());
|
|
}
|
|
|
|
return utils::failure(info.err());
|
|
}
|
|
|
|
}
|
|
#endif //QUERY_SESSION_HPP
|