380 lines
13 KiB
C++
380 lines
13 KiB
C++
#ifndef QUERY_SESSION_HPP
|
|
#define QUERY_SESSION_HPP
|
|
|
|
#include "matador/orm/error_code.hpp"
|
|
|
|
#include "matador/query/query_builder.hpp"
|
|
#include "matador/query/criteria.hpp"
|
|
#include "matador/query/query.hpp"
|
|
#include "matador/query/generator.hpp"
|
|
#include "matador/query/schema.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 <unordered_map>
|
|
#include <utility>
|
|
|
|
namespace matador::orm {
|
|
utils::error make_error(error_code ec, const std::string &msg);
|
|
|
|
struct session_context {
|
|
session_context(utils::message_bus &bus, std::string dns, const size_t count, const size_t cache_size = 500)
|
|
: bus(bus)
|
|
, dns(std::move(dns))
|
|
, connection_count(count)
|
|
, cache_size(cache_size) {
|
|
}
|
|
utils::message_bus &bus;
|
|
std::string dns;
|
|
size_t connection_count{};
|
|
size_t cache_size{500};
|
|
std::shared_ptr<sql::producer_resolver_factory> resolver_factory = std::make_shared<sql::producer_resolver_factory>();
|
|
};
|
|
|
|
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:
|
|
session(session_context &&ctx, const query::schema &scm);
|
|
|
|
/**
|
|
* 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<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> insert(object::object_ptr<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() const;
|
|
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> describe_table(const std::string &table_name) const;
|
|
[[nodiscard]] bool table_exists(const std::string &table_name) 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;
|
|
[[nodiscard]] std::shared_ptr<sql::producer_resolver_factory> resolver_factory() const override;
|
|
[[nodiscard]] const class query::basic_schema &schema() const;
|
|
|
|
private:
|
|
friend class query_select;
|
|
|
|
static query::fetchable_query build_select_query(query::entity_query_data &&data);
|
|
|
|
private:
|
|
sql::connection_pool pool_;
|
|
mutable sql::statement_cache cache_;
|
|
const sql::dialect &dialect_;
|
|
|
|
const query::basic_schema &schema_;
|
|
mutable std::unordered_map<std::string, std::vector<object::attribute> > prototypes_;
|
|
std::shared_ptr<sql::producer_resolver_factory> resolver_factory_;
|
|
};
|
|
|
|
template<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::insert(Type *obj) {
|
|
std::shared_ptr<Type> ptr(obj);
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
|
|
auto res = query::query::insert()
|
|
.into(it->second.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{ptr});
|
|
}
|
|
|
|
template<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::insert(object::object_ptr<Type> obj) {
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
|
|
auto res = query::query::insert()
|
|
.into(it->second.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(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, const 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::CascadeNoneFetchLazy) {}
|
|
template<class Pointer>
|
|
static void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/ = utils::CascadeNoneFetchLazy) {}
|
|
|
|
template<class ContainerType>
|
|
static void on_has_many(const char * /*id*/,
|
|
ContainerType &/*c*/,
|
|
const char * /*join_column*/,
|
|
const utils::foreign_attributes &/*attr*/ = utils::CascadeNoneFetchLazy) {}
|
|
|
|
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) {
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
using namespace matador::utils;
|
|
using namespace matador::query;
|
|
|
|
const auto col = table_column(it->second.node().info().primary_key_attribute()->name());
|
|
auto res = matador::query::query::update(it->second.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) {
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
using namespace matador::utils;
|
|
using namespace matador::query;
|
|
|
|
const auto col = table_column(it->second.node().info().primary_key_attribute()->name());
|
|
auto res = matador::query::query::remove()
|
|
.from(it->second.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) {
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
const auto &info = it->second.node().info();
|
|
if (!info.has_primary_key()) {
|
|
return utils::failure(make_error(error_code::NoPrimaryKey, "Type hasn't primary key."));
|
|
}
|
|
|
|
query::query_builder eqb(schema_, *this);
|
|
const query::table_column c(&it->second.table(), info.primary_key_attribute()->name());
|
|
using namespace matador::query;
|
|
auto data = eqb.build<Type>(c == utils::_);
|
|
if (!data.is_ok()) {
|
|
return utils::failure(make_error(error_code::FailedToBuildQuery,
|
|
"Failed to build query for 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) {
|
|
return utils::failure(make_error(error_code::FailedToFindObject,
|
|
"Failed to find object of type " + info.name() + " with primary key " +
|
|
std::to_string(pk) + "."));
|
|
}
|
|
return utils::ok(*stmt_result);
|
|
}
|
|
|
|
template<typename Type>
|
|
utils::result<sql::query_result<Type>, utils::error> session::find(query::criteria_ptr clause) {
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
|
|
query::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 " + it->second.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() const {
|
|
const auto it = schema_.find(typeid(Type));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
|
}
|
|
return drop_table(it->second.name());
|
|
}
|
|
|
|
}
|
|
#endif //QUERY_SESSION_HPP
|