344 lines
13 KiB
C++
344 lines
13 KiB
C++
#ifndef MATADOR_INSERT_QUERY_BUILDER_HPP
|
|
#define MATADOR_INSERT_QUERY_BUILDER_HPP
|
|
|
|
#include <utility>
|
|
|
|
#include "matador/object/collection.hpp"
|
|
|
|
#include "matador/query/basic_schema.hpp"
|
|
#include "matador/query/intermediates/executable_query.hpp"
|
|
#include "matador/query/insert_step.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<typename BaseType>
|
|
static void on_base(const BaseType&) {}
|
|
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 char * /*join_column*/, 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_;
|
|
};
|
|
|
|
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<std::unique_ptr<insert_step>>, utils::error> build(const object::object_ptr<ObjectType> &ptr) {
|
|
if (const auto it = schema_.find(typeid(ObjectType)); it == schema_.end()) {
|
|
return utils::failure(utils::error{error_code::UnknownType, "Unknown type for insert query"});
|
|
}
|
|
|
|
steps_.clear();
|
|
visited_.clear();
|
|
|
|
ptr_ = ptr;
|
|
const auto result = build_for(ptr, steps_);
|
|
ptr_.reset();
|
|
if (!result) {
|
|
return utils::failure(result.err());
|
|
}
|
|
|
|
// 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(std::move(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 char * /*join_column*/, 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>
|
|
void on_has_many(const char *id,
|
|
object::collection<CollectionType> &objects,
|
|
const char *join_column,
|
|
const utils::foreign_attributes &attr) {
|
|
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
|
|
return;
|
|
}
|
|
|
|
const auto it = schema_.find(std::string{id});
|
|
if (it == schema_.end()) {
|
|
throw query_builder_exception(error_code::UnknownType, "Unknown type " + std::string{id});
|
|
}
|
|
|
|
using relation_value_type = object::many_to_relation<ObjectType, CollectionType>;
|
|
if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) {
|
|
throw query_builder_exception(error_code::InvalidRelationType, "Invalid relation type");
|
|
}
|
|
|
|
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
|
|
if (cit == contexts_by_type_.end()) {
|
|
throw query_builder_exception(error_code::UnknownType, "Unknown type" + std::string{id});
|
|
}
|
|
|
|
for (auto &obj : objects) {
|
|
auto rel = object::make_object<relation_value_type>(join_column, "value", ptr_, obj);
|
|
|
|
relation_steps_.push_back(std::make_unique<insert_step_relation<relation_value_type>>(cit->second.insert, rel));
|
|
}
|
|
}
|
|
|
|
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(error_code::UnknownType, "Unknown type");
|
|
}
|
|
|
|
using relation_value_type = object::many_to_many_relation<ObjectType, ForeignType>;
|
|
if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) {
|
|
throw query_builder_exception(error_code::InvalidRelationType, "Invalid relation type");
|
|
}
|
|
|
|
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
|
|
if (cit == contexts_by_type_.end()) {
|
|
throw query_builder_exception(error_code::UnknownType, "Unknown type");
|
|
}
|
|
|
|
std::ignore = processing_many_to_many_relations_.insert(id);
|
|
std::vector<std::unique_ptr<insert_step>> insert_relation_steps;
|
|
for (auto &obj : objects) {
|
|
if (!obj) {
|
|
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, ptr_, obj);
|
|
|
|
access::process(*this, *rel);
|
|
|
|
insert_relation_steps.push_back(std::make_unique<insert_step_relation<relation_value_type>>(cit->second.insert, rel));
|
|
}
|
|
relation_steps_.insert(relation_steps_.end(), std::make_move_iterator(insert_relation_steps.begin()), std::make_move_iterator(insert_relation_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(error_code::UnknownType, "Unknown type");
|
|
}
|
|
|
|
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(error_code::InvalidRelationType, "Invalid relation type");
|
|
}
|
|
|
|
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
|
|
if (cit == contexts_by_type_.end()) {
|
|
throw query_builder_exception(error_code::UnknownType, "Unknown type");
|
|
}
|
|
|
|
std::ignore = processing_many_to_many_relations_.insert(id);
|
|
std::vector<std::unique_ptr<insert_step>> insert_relation_steps;
|
|
for (auto &obj : objects) {
|
|
if (!obj) {
|
|
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_relation_steps.push_back(std::make_unique<insert_step_relation<relation_value_type>>(cit->second.insert, rel));
|
|
}
|
|
relation_steps_.insert(relation_steps_.end(), std::make_move_iterator(insert_relation_steps.begin()), std::make_move_iterator(insert_relation_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>
|
|
utils::result<void, utils::error> build_for(const object::object_ptr<EntityType> &ptr, std::vector<std::unique_ptr<insert_step>> &steps) {
|
|
if (!ptr) {
|
|
return utils::failure(utils::error{error_code::InvalidObject, "Object is null"});
|
|
}
|
|
|
|
const auto key = make_visit_key<EntityType>(ptr);
|
|
if (visited_.find(key) != visited_.end()) {
|
|
return utils::ok<void>();
|
|
}
|
|
visited_.insert(key);
|
|
|
|
const auto it = schema_.find(typeid(EntityType));
|
|
if (it == schema_.end()) {
|
|
return utils::failure(utils::error{error_code::UnknownType, "Unknown type"});
|
|
}
|
|
|
|
// 1) Traverse relations first => dependencies will be inserted before this object
|
|
try {
|
|
access::process(*this, *ptr);
|
|
} catch (const query_builder_exception &ex) {
|
|
return utils::failure(ex.error());
|
|
}
|
|
|
|
// 2) Build INSERT for this object
|
|
const auto &info = it->second.node().info();
|
|
if (!info.has_primary_key() || it->second.pk_generator().type() == utils::generator_type::None) {
|
|
return utils::failure(utils::error{error_code::MissingPrimaryKey, "Type " + info.name() + " has no primary key"});
|
|
}
|
|
const auto cit = contexts_by_type_.find(it->second.node().info().type_index());
|
|
if (cit == contexts_by_type_.end()) {
|
|
return utils::failure(utils::error{error_code::UnknownType, "Unknown type"});
|
|
}
|
|
|
|
if (it->second.pk_generator().type() == utils::generator_type::Manual) {
|
|
steps.push_back(std::make_unique<insert_step_pk_manual<EntityType>>(cit->second.insert, ptr));
|
|
} else if (it->second.pk_generator().type() == utils::generator_type::Identity) {
|
|
steps.push_back(std::make_unique<insert_step_pk_identity<EntityType>>(cit->second.insert, ptr, info.primary_key_attribute()->name()));
|
|
} else {
|
|
steps.push_back(std::make_unique<insert_step_pk_generated<EntityType>>(cit->second.insert, ptr, it->second.pk_generator()));
|
|
}
|
|
|
|
return utils::ok<void>();
|
|
}
|
|
|
|
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<std::unique_ptr<insert_step>> steps_;
|
|
std::vector<std::unique_ptr<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
|