query/include/matador/query/insert_query_builder.hpp

349 lines
13 KiB
C++

#ifndef MATADOR_INSERT_QUERY_BUILDER_HPP
#define MATADOR_INSERT_QUERY_BUILDER_HPP
#include "matador/object/collection.hpp"
#include "matador/query/basic_schema.hpp"
#include "matador/query/intermediates/executable_query.hpp"
#include "matador/query/query.hpp"
#include "matador/query/query_contexts.hpp"
#include "matador/query/query_builder_exception.hpp"
#include "matador/sql/statement.hpp"
#include "matador/utils/primary_key_accessor.hpp"
namespace matador::sql {
class statement_cache;
}
namespace matador::query {
template < class ObjectType >
class has_many_linker {
public:
has_many_linker(const object::object_ptr<ObjectType> &ptr, std::string join_column)
: ptr_(ptr), join_column_(std::move(join_column)) {}
template < class PrimaryKeyType >
static void on_primary_key(const char * /*id*/, PrimaryKeyType &, const utils::primary_key_attribute& /*attr*/) {}
static void on_revision(const char * /*id*/, uint64_t &/*rev*/) {}
template<typename Type>
static void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/) {}
template<class Pointer>
static void on_belongs_to(const char * /*id*/, Pointer &/*obj*/, const utils::foreign_attributes &/*attr*/) {}
void on_belongs_to(const char *id, object::object_ptr<ObjectType> &obj, const utils::foreign_attributes &/*attr*/) {
if (id != join_column_) {
return;
}
obj = ptr_;
}
template<class Pointer>
static void on_has_one(const char * /*id*/, Pointer &/*obj*/, const utils::foreign_attributes &/*attr*/) {}
template<class CollectionType>
static void on_has_many(const char * /*id*/, CollectionType &/*con*/, const char *, const utils::foreign_attributes &/*attr*/, std::enable_if_t<!object::is_object_ptr<typename CollectionType::value_type>::value> * = nullptr) {}
template<class Collection>
static void on_has_many_to_many(const char * /*id*/, Collection &/*container*/, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
template<class Collection>
static void on_has_many_to_many(const char * /*id*/, Collection &/*container*/, const utils::foreign_attributes & ) {}
private:
object::object_ptr<ObjectType> ptr_;
std::string join_column_;
};
struct insert_step {
sql::query_context ctx;
utils::result<sql::statement, utils::error> acquire_and_bind(sql::statement_cache &cache) const;
// Session uses these to handle manual/sequence/table pre-insert PKs
utils::generator_type pk_generator{utils::generator_type::Manual};
utils::primary_key_accessor pk_accessor;
// Identity post-insert
std::function<void(const sql::record &)> apply_returning{};
std::function<void(const utils::identifier &)> apply_primary_key{};
std::function<void(sql::statement &)> bind_object{};
std::function<void()> make_object_persistent{};
};
template<class ObjectType>
class insert_query_builder {
public:
explicit insert_query_builder(const basic_schema &schema, const std::unordered_map<std::type_index, query_contexts> &contexts_by_type)
: schema_(schema)
, contexts_by_type_{contexts_by_type}
{}
utils::result<std::vector<insert_step>, query_build_error> build(const object::object_ptr<ObjectType> &ptr) {
if (const auto it = schema_.find(typeid(ObjectType)); it == schema_.end()) {
return utils::failure(query_build_error::UnknownType);
}
steps_.clear();
visited_.clear();
ptr_ = ptr;
build_for(ptr, steps_);
ptr_.reset();
// relation inserts must run after all entity inserts were collected
for (auto &s : relation_steps_) {
steps_.push_back(std::move(s));
}
relation_steps_.clear();
return utils::ok(steps_);
}
template < class PrimaryKeyType >
static void on_primary_key(const char * /*id*/, PrimaryKeyType &, const utils::primary_key_attribute& /*attr*/) {}
static void on_revision(const char * /*id*/, uint64_t &/*rev*/) {}
template<typename Type>
static void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/) {}
template<class Pointer>
void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {
on_foreign_object(obj, attr);
}
template<class Pointer>
void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {
on_foreign_object(obj, attr);
}
template<class CollectionType>
void on_has_many(const char * /*id*/, object::collection<object::object_ptr<CollectionType>> &objects, const char *join_column, const utils::foreign_attributes &attr) {
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
return;
}
has_many_linker<ObjectType> linker(ptr_, join_column);
for (auto &obj : objects) {
if (obj.is_transient()) {
build_for(obj, relation_steps_);
}
access::process(linker, *obj);
}
}
template<class CollectionType>
static void on_has_many(const char * /*id*/, object::collection<CollectionType> &/*con*/, const char *, const utils::foreign_attributes &/*attr*/) {}
template<class ForeignType>
void on_has_many_to_many(const char *id, object::collection<object::object_ptr<ForeignType>> &objects, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) {
if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) {
return;
}
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
return;
}
if (processing_many_to_many_relations_.find(id) != processing_many_to_many_relations_.end()) {
return;
}
const auto it = schema_.find(std::string{id});
if (it == schema_.end()) {
throw query_builder_exception(query_build_error::UnknownType);
}
using relation_value_type = object::many_to_many_relation<ForeignType, ObjectType>;
if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) {
throw query_builder_exception(query_build_error::InvalidRelationType);
}
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
if (cit == contexts_by_type_.end()) {
throw query_builder_exception(query_build_error::UnknownType);
}
const auto rel_it = processing_many_to_many_relations_.insert(id);
std::vector<insert_step> rel_steps;
for (auto &obj : objects) {
if (!obj) {
continue;
}
if (obj.is_persistent()) {
continue;
}
// Ensure target exists as dependency (deps first)
if (obj.is_transient()) {
build_for(obj, relation_steps_);
}
auto rel = object::make_object<relation_value_type>(join_column, inverse_join_column, obj, ptr_);
access::process(*this, *rel);
insert_step step{};
step.pk_generator = utils::generator_type::None;
step.ctx = cit->second.insert;
step.bind_object = [rel](sql::statement &stmt) { stmt.bind(*rel); };
step.make_object_persistent = [] {};
rel_steps.push_back(std::move(step));
}
relation_steps_.insert(relation_steps_.end(), rel_steps.begin(), rel_steps.end());
processing_many_to_many_relations_.erase(id);
}
template<class ForeignType>
void on_has_many_to_many(const char *id, object::collection<object::object_ptr<ForeignType>> &objects, const utils::foreign_attributes &attr) {
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
return;
}
if (processing_many_to_many_relations_.find(id) != processing_many_to_many_relations_.end()) {
return;
}
object::join_columns_collector collector;
auto join_columns = collector.collect<ForeignType>();
const auto it = schema_.find(std::string{id});
if (it == schema_.end()) {
throw query_builder_exception(query_build_error::UnknownType);
}
using relation_value_type = object::many_to_many_relation<ForeignType, ObjectType>;
if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) {
throw query_builder_exception(query_build_error::InvalidRelationType);
}
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
if (cit == contexts_by_type_.end()) {
throw query_builder_exception(query_build_error::UnknownType);
}
const auto rel_it = processing_many_to_many_relations_.insert(id);
std::vector<insert_step> rel_steps;
for (auto &obj : objects) {
if (!obj) {
continue;
}
if (obj.is_persistent()) {
continue;
}
// Ensure target exists as dependency (deps first)
if (obj.is_transient()) {
build_for(obj, relation_steps_);
}
auto rel = object::make_object<relation_value_type>(join_columns.inverse_join_column, join_columns.join_column, obj, ptr_);
access::process(*this, *rel);
insert_step step{};
step.pk_generator = utils::generator_type::None;
step.ctx = cit->second.insert;
step.bind_object = [rel](sql::statement &stmt) { stmt.bind(*rel); };
step.make_object_persistent = [] {};
rel_steps.push_back(std::move(step));
}
relation_steps_.insert(relation_steps_.end(), rel_steps.begin(), rel_steps.end());
processing_many_to_many_relations_.erase(id);
}
private:
template<class EntityType>
static std::pair<std::type_index, const void *> make_visit_key(const object::object_ptr<EntityType> &ptr) {
return {std::type_index(typeid(EntityType)), static_cast<const void *>(&(*ptr))};
}
struct visit_key_hash {
size_t operator()(const std::pair<std::type_index, const void *> &p) const noexcept {
// combine hashes (simple + sufficient here)
const size_t h1 = p.first.hash_code();
const size_t h2 = std::hash<const void *>{}(p.second);
return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
}
};
template<class EntityType>
void build_for(const object::object_ptr<EntityType> &ptr, std::vector<insert_step> &steps) {
if (!ptr) {
return;
}
const auto key = make_visit_key<EntityType>(ptr);
if (visited_.find(key) != visited_.end()) {
return;
}
visited_.insert(key);
const auto it = schema_.find(typeid(EntityType));
if (it == schema_.end()) {
throw query_builder_exception(query_build_error::UnknownType);
}
// 1) Traverse relations first => dependencies will be inserted before this object
access::process(*this, *ptr);
// 2) Build INSERT for this object
const auto &info = it->second.node().info();
insert_step step{};
if (info.has_primary_key()) {
step.pk_generator = it->second.pk_generator().type();
}
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
if (cit == contexts_by_type_.end()) {
throw query_builder_exception(query_build_error::UnknownType);
}
step.ctx = cit->second.insert;
step.bind_object = [ptr](sql::statement &stmt) { stmt.bind(*ptr); };
if (info.has_primary_key() && step.pk_generator == utils::generator_type::Identity) {
const auto pk_name = info.primary_key_attribute()->name();
const table_column pk_col(&it->second.table(), pk_name);
// step.query = fetchable_query{insert().into(it->second.table()).values(*ptr).returning(pk_col)};
step.apply_returning = [ptr, &step, pk_name = pk_name](const sql::record &rec) {
const auto& f = rec.at(pk_name);
utils::identifier id;
id.assign(f.value());
step.pk_accessor.set(*ptr, id);
};
} else if (info.has_primary_key() && step.pk_generator == utils::generator_type::Sequence || step.pk_generator == utils::generator_type::Table) {
step.apply_primary_key = [ptr, &step](const utils::identifier &id) {
step.pk_accessor.set(*ptr, id);
};
}
step.make_object_persistent = [ptr] { ptr.change_state(object::object_state::Persistent); };
steps.push_back(std::move(step));
}
template<class Pointer>
void on_foreign_object(Pointer &obj, const utils::foreign_attributes & /*attr*/) {
if (!obj) {
return;
}
// Dependency only matters if the referenced object must be inserted
if (obj.is_persistent()) {
return;
}
using dep_t = std::remove_reference_t<decltype(*obj)>;
build_for<dep_t>(obj, steps_);
}
private:
const basic_schema &schema_;
const std::unordered_map<std::type_index, query_contexts> &contexts_by_type_;
object::object_ptr<ObjectType> ptr_;
std::vector<insert_step> steps_;
std::vector<insert_step> relation_steps_;
std::unordered_set<std::pair<std::type_index, const void *>, visit_key_hash> visited_;
std::unordered_set<std::string> processing_many_to_many_relations_;
};
}
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP