implemented lazy loading for object_ptr and belongs_to
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
#ifndef QUERY_BUILDER_EXCEPTION_HPP
|
||||
#define QUERY_BUILDER_EXCEPTION_HPP
|
||||
|
||||
#include "matador/utils/error.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace matador::orm {
|
||||
|
||||
enum class query_build_error : std::uint8_t {
|
||||
Ok = 0,
|
||||
UnknownType,
|
||||
MissingPrimaryKey,
|
||||
UnexpectedError,
|
||||
QueryError
|
||||
};
|
||||
|
||||
class query_builder_exception final : public std::exception {
|
||||
public:
|
||||
explicit query_builder_exception(const query_build_error error, utils::error &&err = {});
|
||||
|
||||
[[nodiscard]] query_build_error error_type() const;
|
||||
[[nodiscard]] const utils::error &error() const;
|
||||
|
||||
private:
|
||||
const query_build_error error_type_;
|
||||
utils::error error_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //QUERY_BUILDER_EXCEPTION_HPP
|
||||
+223
-173
@@ -2,8 +2,8 @@
|
||||
#define QUERY_SESSION_HPP
|
||||
|
||||
#include "matador/orm/error_code.hpp"
|
||||
#include "matador/orm/session_query_builder.hpp"
|
||||
|
||||
#include "matador/query/query_builder.hpp"
|
||||
#include "matador/query/criteria.hpp"
|
||||
#include "matador/query/query.hpp"
|
||||
#include "matador/query/generator.hpp"
|
||||
@@ -18,70 +18,83 @@
|
||||
#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;
|
||||
sql::connection_pool &pool;
|
||||
std::string dns;
|
||||
size_t connection_count{};
|
||||
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_;
|
||||
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;
|
||||
: statements_per_column_(statements_per_column) {
|
||||
}
|
||||
|
||||
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) {}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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*/) {}
|
||||
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*/) {}
|
||||
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*/) {}
|
||||
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*/) {}
|
||||
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);
|
||||
@@ -95,8 +108,10 @@ public:
|
||||
*/
|
||||
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> 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);
|
||||
@@ -112,33 +127,40 @@ public:
|
||||
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::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<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::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(entity_query_data &&data);
|
||||
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::schema& schema_;
|
||||
mutable std::unordered_map<std::string, std::vector<object::attribute>> prototypes_;
|
||||
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."));
|
||||
@@ -149,171 +171,199 @@ utils::result<object::object_ptr<Type>, utils::error> session::insert(Type *obj)
|
||||
.values(query::generator::placeholders<Type>())
|
||||
.prepare(*this);
|
||||
if (!res) {
|
||||
return utils::failure(res.err());
|
||||
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});
|
||||
return utils::ok(object::object_ptr{ptr});
|
||||
}
|
||||
|
||||
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)...));
|
||||
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, size_t position)
|
||||
: stmt_(stmt)
|
||||
, binding_position_(position){}
|
||||
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>
|
||||
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);
|
||||
}
|
||||
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) {}
|
||||
static void on_revision(const char * /*id*/, uint64_t &/*rev*/) {
|
||||
}
|
||||
|
||||
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*/) {}
|
||||
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_;
|
||||
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;
|
||||
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());
|
||||
}
|
||||
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});
|
||||
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;
|
||||
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());
|
||||
}
|
||||
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>();
|
||||
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) {
|
||||
// return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
||||
// }
|
||||
if (!info.has_primary_key()) {
|
||||
return utils::failure(make_error(error_code::NoPrimaryKey, "Type hasn't primary key."));
|
||||
}
|
||||
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."));
|
||||
}
|
||||
|
||||
session_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() + "."));
|
||||
}
|
||||
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);
|
||||
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 " + info.name() + " with primary key " + std::to_string(pk) + "."));
|
||||
}
|
||||
return utils::ok(object::object_ptr<Type>{ stmt_result->release() });
|
||||
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."));
|
||||
}
|
||||
const auto it = schema_.find(typeid(Type));
|
||||
if (it == schema_.end()) {
|
||||
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 " + it->second.name() + "."));
|
||||
}
|
||||
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());
|
||||
}
|
||||
auto result = build_select_query(data.release()).prepare(*this);
|
||||
if (!result.is_ok()) {
|
||||
return utils::failure(result.err());
|
||||
}
|
||||
|
||||
return result->template fetch<Type>();
|
||||
return result->template fetch<Type>();
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
#ifndef QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||
#define QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||
|
||||
#include "matador/orm/query_builder_exception.hpp"
|
||||
|
||||
#include "matador/query/criteria.hpp"
|
||||
#include "matador/query/query.hpp"
|
||||
#include "matador/query/schema.hpp"
|
||||
|
||||
#include "matador/sql/executor.hpp"
|
||||
#include "matador/sql/statement.hpp"
|
||||
|
||||
#include "matador/object/join_columns_collector.hpp"
|
||||
#include "matador/object/repository.hpp"
|
||||
#include "matador/query/criteria/criteria_visitor.hpp"
|
||||
|
||||
#include "matador/utils/primary_key_attribute.hpp"
|
||||
#include "matador/utils/result.hpp"
|
||||
#include "matador/utils/value.hpp"
|
||||
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace matador::orm {
|
||||
|
||||
struct entity_query_data {
|
||||
const query::table* root_table{nullptr};
|
||||
std::string pk_column_name{};
|
||||
std::vector<query::table_column> columns{};
|
||||
std::unordered_map<std::string, sql::statement> lazy_loading_statements{};
|
||||
std::vector<query::join_data> joins{};
|
||||
query::criteria_ptr where_clause{};
|
||||
};
|
||||
|
||||
class criteria_transformer final : public query::criteria_visitor {
|
||||
public:
|
||||
criteria_transformer(const query::schema &repo, const std::unordered_map<std::string, query::table>& tables_by_name);
|
||||
void visit( const query::between_criteria& node ) override;
|
||||
void visit( const query::binary_criteria& node ) override;
|
||||
void visit( const query::binary_column_criteria& node ) override;
|
||||
void visit( const query::collection_criteria& node ) override;
|
||||
void visit( const query::collection_query_criteria& node ) override;
|
||||
void visit( const query::like_criteria& node ) override;
|
||||
void visit( const query::logical_criteria& node ) override;
|
||||
void visit( const query::not_criteria& node ) override;
|
||||
|
||||
private:
|
||||
void update_criteria_column(const query::abstract_column_criteria& node) const;
|
||||
|
||||
private:
|
||||
const query::schema &repo_;
|
||||
const std::unordered_map<std::string, query::table>& tables_by_name_;
|
||||
};
|
||||
|
||||
class session_query_builder final {
|
||||
public:
|
||||
session_query_builder(const query::schema &scm, sql::executor &exec)
|
||||
: schema_(scm)
|
||||
, executor_(exec){}
|
||||
|
||||
template<class EntityType>
|
||||
utils::result<entity_query_data, query_build_error> build(query::criteria_ptr clause = {}) {
|
||||
const auto it = schema_.find(typeid(EntityType));
|
||||
if (it == schema_.end()) {
|
||||
return utils::failure(query_build_error::UnknownType);
|
||||
}
|
||||
table_info_stack_.push({it->second.node().info(), it->second.table().as(build_alias('t', ++table_index))});
|
||||
entity_query_data_ = { &table_info_stack_.top().table };
|
||||
processed_tables_.insert({it->second.name(), *entity_query_data_.root_table});
|
||||
try {
|
||||
EntityType obj;
|
||||
access::process(*this, obj);
|
||||
|
||||
if (clause) {
|
||||
criteria_transformer transformer{schema_, processed_tables_};
|
||||
clause->accept(transformer);
|
||||
entity_query_data_.where_clause = std::move(clause);
|
||||
}
|
||||
return {utils::ok(std::move(entity_query_data_))};
|
||||
} catch (const query_builder_exception &ex) {
|
||||
return {utils::failure(ex.error_type())};
|
||||
} catch (...) {
|
||||
return {utils::failure(query_build_error::UnexpectedError)};
|
||||
}
|
||||
}
|
||||
|
||||
template < class V >
|
||||
void on_primary_key(const char *id, V &, const utils::primary_key_attribute& /*attr*/ = utils::default_pk_attributes) {
|
||||
push(id);
|
||||
if (!is_root_entity()) {
|
||||
return;
|
||||
}
|
||||
entity_query_data_.pk_column_name = id;
|
||||
}
|
||||
|
||||
void on_revision(const char *id, uint64_t &/*rev*/);
|
||||
|
||||
template<typename Type>
|
||||
void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||
{
|
||||
push(id);
|
||||
}
|
||||
|
||||
template<class Pointer>
|
||||
void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr) {
|
||||
on_foreign_object(id, obj, attr);
|
||||
}
|
||||
|
||||
template<class Pointer>
|
||||
void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr) {
|
||||
on_foreign_object(id, obj, attr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct NoopDeleter {
|
||||
void operator()(const T*) const {}
|
||||
};
|
||||
|
||||
template<class ContainerType>
|
||||
void on_has_many(const char * /*id*/, ContainerType &, const char *join_column, const utils::foreign_attributes &attr) {
|
||||
if (attr.fetch() != utils::fetch_type::Eager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto it = schema_.find(typeid(typename ContainerType::value_type::value_type));
|
||||
if (it == schema_.end()) {
|
||||
throw query_builder_exception{query_build_error::UnknownType};
|
||||
}
|
||||
|
||||
auto next = processed_tables_.find(it->second.name());
|
||||
if (next != processed_tables_.end()) {
|
||||
// node already processed
|
||||
return;
|
||||
}
|
||||
|
||||
table_info_stack_.push({it->second.node().info(), it->second.table().as(build_alias('t', ++table_index))});
|
||||
next = processed_tables_.insert({it->second.name(), table_info_stack_.top().table}).first;
|
||||
typename ContainerType::value_type::value_type obj;
|
||||
access::process(*this , obj);
|
||||
table_info_stack_.pop();
|
||||
|
||||
if (!it->second.node().info().has_primary_key()) {
|
||||
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||
}
|
||||
|
||||
append_join(
|
||||
query::table_column{&table_info_stack_.top().table, table_info_stack_.top().info.primary_key_attribute()->name()},
|
||||
query::table_column{&next->second, join_column}
|
||||
);
|
||||
|
||||
|
||||
// const auto result = schema_.repo().basic_info(typeid(typename ContainerType::value_type::value_type));
|
||||
// if (!result) {
|
||||
// throw query_builder_exception{query_build_error::UnknownType};
|
||||
// }
|
||||
//
|
||||
// const auto &info = result.value().get();
|
||||
// auto next = processed_tables_.find(info.name());
|
||||
// if (next != processed_tables_.end()) {
|
||||
// return;
|
||||
// }
|
||||
// table_info_stack_.push({*result, std::make_shared<query::table>(info.name(), build_alias('t', ++table_index))});
|
||||
// next = processed_tables_.insert({info.name(), table_info_stack_.top().table}).first;
|
||||
// typename ContainerType::value_type::value_type obj;
|
||||
// access::process(*this , obj);
|
||||
// table_info_stack_.pop();
|
||||
//
|
||||
// if (!info.has_primary_key()) {
|
||||
// throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||
// }
|
||||
//
|
||||
// append_join(
|
||||
// query::column{table_info_stack_.top().table.get(), table_info_stack_.top().info.get().primary_key_attribute()->name()},
|
||||
// query::column{next->second.get(), join_column}
|
||||
// );
|
||||
}
|
||||
|
||||
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) {
|
||||
if (attr.fetch() != utils::fetch_type::Eager) {
|
||||
return;
|
||||
}
|
||||
const auto result = schema_.find(typeid(typename ContainerType::value_type::value_type));
|
||||
if (result == schema_.end()) {
|
||||
throw query_builder_exception{query_build_error::UnknownType};
|
||||
}
|
||||
|
||||
auto next = processed_tables_.find(result->second.name());
|
||||
if (next != processed_tables_.end()) {
|
||||
// attribute was already processed
|
||||
return;
|
||||
}
|
||||
|
||||
auto relation = processed_tables_.find(id);
|
||||
if (relation == processed_tables_.end()) {
|
||||
const auto it = schema_.find(id);
|
||||
if (it == schema_.end()) {
|
||||
throw query_builder_exception{query_build_error::UnknownType};
|
||||
}
|
||||
relation = processed_tables_.emplace(id, it->second.table().as(build_alias('t', ++table_index))).first;
|
||||
}
|
||||
table_info_stack_.push({result->second.node().info(), result->second.table().as(build_alias('t', ++table_index))});
|
||||
next = processed_tables_.insert({result->second.name(), table_info_stack_.top().table}).first;
|
||||
typename ContainerType::value_type::value_type obj;
|
||||
access::process(*this , obj);
|
||||
table_info_stack_.pop();
|
||||
|
||||
if (!result->second.node().info().has_primary_key()) {
|
||||
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||
}
|
||||
|
||||
append_join(
|
||||
query::table_column{&table_info_stack_.top().table, table_info_stack_.top().info.primary_key_attribute()->name()},
|
||||
query::table_column{&relation->second, join_column}
|
||||
);
|
||||
append_join(
|
||||
query::table_column{&relation->second, inverse_join_column},
|
||||
query::table_column{&next->second, result->second.node().info().primary_key_attribute()->name()}
|
||||
);
|
||||
}
|
||||
|
||||
template<class ContainerType>
|
||||
void on_has_many_to_many(const char *id, ContainerType &/*cont*/, const utils::foreign_attributes &attr) {
|
||||
if (attr.fetch() != utils::fetch_type::Eager) {
|
||||
return;
|
||||
}
|
||||
const auto result = schema_.find(typeid(typename ContainerType::value_type::value_type));
|
||||
if (result == schema_.end()) {
|
||||
throw query_builder_exception{query_build_error::UnknownType};
|
||||
}
|
||||
|
||||
auto next = processed_tables_.find(result->second.name());
|
||||
if (next != processed_tables_.end()) {
|
||||
// attribute was already processed
|
||||
return;
|
||||
}
|
||||
|
||||
auto relation = processed_tables_.find(id);
|
||||
if (relation == processed_tables_.end()) {
|
||||
const auto it = schema_.find(id);
|
||||
if (it == schema_.end()) {
|
||||
throw query_builder_exception{query_build_error::UnknownType};
|
||||
}
|
||||
const auto t = it->second.table().as(build_alias('t', ++table_index));
|
||||
relation = processed_tables_.insert({id, t}).first;
|
||||
}
|
||||
table_info_stack_.push({result->second.node().info(), result->second.table().as(build_alias('t', ++table_index))});
|
||||
next = processed_tables_.insert({result->second.name(), table_info_stack_.top().table}).first;
|
||||
typename ContainerType::value_type::value_type obj;
|
||||
access::process(*this , obj);
|
||||
table_info_stack_.pop();
|
||||
|
||||
if (!result->second.node().info().has_primary_key()) {
|
||||
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||
}
|
||||
|
||||
const auto join_columns = join_columns_collector_.collect<typename ContainerType::value_type::value_type>();
|
||||
|
||||
append_join(
|
||||
query::table_column{&table_info_stack_.top().table, table_info_stack_.top().info.primary_key_attribute()->name()},
|
||||
query::table_column{&relation->second, join_columns.inverse_join_column}
|
||||
);
|
||||
append_join(
|
||||
query::table_column{&relation->second, join_columns.join_column},
|
||||
query::table_column{&next->second, result->second.node().info().primary_key_attribute()->name()}
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
template<class Pointer>
|
||||
void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr);
|
||||
void push(const std::string &column_name);
|
||||
static std::string build_alias(char prefix, unsigned int count);
|
||||
[[nodiscard]] bool is_root_entity() const;
|
||||
void append_join(const query::table_column &left, const query::table_column &right);
|
||||
|
||||
private:
|
||||
struct table_info {
|
||||
const object::basic_object_info &info;
|
||||
query::table table;
|
||||
};
|
||||
|
||||
std::stack<table_info> table_info_stack_{};
|
||||
std::unordered_map<std::string, query::table> processed_tables_{};
|
||||
const query::schema &schema_;
|
||||
entity_query_data entity_query_data_{};
|
||||
unsigned int column_index{0};
|
||||
unsigned int table_index{0};
|
||||
object::join_columns_collector join_columns_collector_{};
|
||||
sql::executor &executor_;
|
||||
};
|
||||
|
||||
template<class Pointer>
|
||||
void session_query_builder::on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr) {
|
||||
const auto it = schema_.find(typeid(typename Pointer::value_type));
|
||||
if (it == schema_.end()) {
|
||||
throw query_builder_exception{query_build_error::UnknownType};
|
||||
}
|
||||
if (!it->second.node().info().has_primary_key()) {
|
||||
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||
}
|
||||
|
||||
const auto& info = it->second.node().info();
|
||||
auto foreign_table = it->second.table().as(build_alias('t', ++table_index));
|
||||
if (attr.fetch() == utils::fetch_type::Eager) {
|
||||
|
||||
auto next = processed_tables_.find(info.name());
|
||||
if (next != processed_tables_.end()) {
|
||||
return;
|
||||
}
|
||||
table_info_stack_.push({info, std::move(foreign_table)});
|
||||
next = processed_tables_.insert({info.name(), table_info_stack_.top().table}).first;
|
||||
typename Pointer::value_type obj;
|
||||
access::process(*this, obj);
|
||||
table_info_stack_.pop();
|
||||
|
||||
append_join(
|
||||
query::table_column{&table_info_stack_.top().table, id},
|
||||
query::table_column{&next->second, info.primary_key_attribute()->name()}
|
||||
);
|
||||
} else {
|
||||
push(id);
|
||||
using namespace matador::utils;
|
||||
using namespace matador::query;
|
||||
// create select query
|
||||
auto result = matador::query::query::select(generator::columns<typename Pointer::value_type>(schema_, foreign_table, generator::column_generator_options::ForceLazy))
|
||||
.from(foreign_table)
|
||||
.where(table_column(&foreign_table, info.primary_key_attribute()->name(), "") == _)
|
||||
.prepare(executor_);
|
||||
if (!result) {
|
||||
throw query_builder_exception(query_build_error::QueryError, result.release_error());
|
||||
}
|
||||
|
||||
entity_query_data_.lazy_loading_statements.emplace(id, std::move(result.release()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif //QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||
Reference in New Issue
Block a user