339 lines
13 KiB
C++
339 lines
13 KiB
C++
#ifndef QUERY_SESSION_HPP
|
|
#define QUERY_SESSION_HPP
|
|
|
|
#include "matador/orm/error_code.hpp"
|
|
|
|
#include "matador/query/select_query_builder.hpp"
|
|
#include "matador/query/criteria.hpp"
|
|
#include "matador/query/insert_query_builder.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/resolver_service.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::resolver_service> resolver_service = std::make_shared<sql::resolver_service>();
|
|
};
|
|
|
|
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(object::object_ptr<Type> obj);
|
|
|
|
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<sql::execute_result, 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<sql::execute_result, 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::resolver_service> resolver() const override;
|
|
[[nodiscard]] const query::basic_schema &schema() const;
|
|
|
|
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::resolver_service> resolver_service_;
|
|
};
|
|
|
|
template<typename Type>
|
|
utils::result<object::object_ptr<Type>, utils::error> session::insert(object::object_ptr<Type> obj) {
|
|
if (obj.is_persistent()) {
|
|
return utils::ok(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."));
|
|
}
|
|
|
|
// Build dependency-ordered insert steps (deps first, root last)
|
|
query::insert_query_builder iqb(schema_);
|
|
auto steps = iqb.build(obj);
|
|
if (!steps.is_ok()) {
|
|
return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build insert dependency queries."));
|
|
}
|
|
|
|
// Execute all steps; for Identity steps read RETURNING and write pk back into the object
|
|
for (auto &step : *steps) {
|
|
if (step.pk_is_unset && step.set_pk) {
|
|
if (step.pk_generator == utils::generator_type::Manual) {
|
|
if (step.pk_is_unset()) {
|
|
return utils::failure(make_error(error_code::NoPrimaryKey, "Manual primary key is required but unset."));
|
|
}
|
|
} else if (step.pk_generator == utils::generator_type::Sequence) {
|
|
if (step.pk_is_unset()) {
|
|
// hard-coded naming as you specified earlier
|
|
// <table_name>_seq (table name known in schema meta; if you prefer, store it in step too)
|
|
// For now, we derive from the schema type used for the root insert: simplistic but works if table name == entity name.
|
|
const std::string seq_name = it->second.name() + "_seq";
|
|
|
|
auto id_res = query::query::select().nextval(seq_name).fetch_value<std::uint64_t>(*this);
|
|
if (!id_res.is_ok() || !id_res.value().has_value()) {
|
|
return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to obtain next sequence value."));
|
|
}
|
|
step.set_pk(*id_res.value());
|
|
}
|
|
} else if (step.pk_generator == utils::generator_type::Table) {
|
|
if (step.pk_is_unset()) {
|
|
// TODO: implement table id generation; same idea as above:
|
|
// UPDATE <table>_tbl_seq ... RETURNING ...
|
|
return utils::failure(make_error(error_code::Failed, "Table primary key generator not implemented yet."));
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Execute step ---
|
|
if (std::holds_alternative<query::executable_query>(step.query)) {
|
|
const auto &q = std::get<query::executable_query>(step.query);
|
|
const auto exec_res = q.execute(*this);
|
|
if (!exec_res.is_ok()) {
|
|
return utils::failure(exec_res.err());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const auto &q = std::get<query::fetchable_query>(step.query);
|
|
auto rec_res = q.fetch_one(*this);
|
|
if (!rec_res.is_ok()) {
|
|
return utils::failure(rec_res.err());
|
|
}
|
|
if (!rec_res.value().has_value()) {
|
|
return utils::failure(make_error(error_code::FailedToFindObject, "INSERT ... RETURNING did not return a row."));
|
|
}
|
|
if (step.apply_returning) {
|
|
step.apply_returning(*rec_res.value());
|
|
}
|
|
}
|
|
|
|
obj.change_state(object::object_state::Persistent);
|
|
return utils::ok(obj);
|
|
}
|
|
|
|
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<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::select_query_builder eqb(schema_);
|
|
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 = data->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::select_query_builder eqb(schema_);
|
|
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 = data->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
|