284 lines
10 KiB
C++
284 lines
10 KiB
C++
#ifndef MATADOR_INSERT_QUERY_BUILDER_HPP
|
|
#define MATADOR_INSERT_QUERY_BUILDER_HPP
|
|
|
|
#include "matador/query/basic_schema.hpp"
|
|
#include "matador/query/intermediates/executable_query.hpp"
|
|
#include "matador/query/query.hpp"
|
|
#include "matador/query/query_builder_exception.hpp"
|
|
|
|
namespace matador::query {
|
|
|
|
struct pk_setter {
|
|
const std::string &name;
|
|
std::uint64_t value;
|
|
|
|
template<class V>
|
|
void on_primary_key(const char *id, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
|
|
if (id != nullptr && name == id) {
|
|
pk = static_cast<V>(value);
|
|
}
|
|
}
|
|
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
|
|
template<typename T>
|
|
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
|
|
template<class P>
|
|
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
|
template<class P>
|
|
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
|
|
};
|
|
|
|
struct pk_unset_checker {
|
|
const std::string &name;
|
|
bool unset{true};
|
|
|
|
template<class V>
|
|
void on_primary_key(const char *id, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
|
|
if (id != nullptr && name == id) {
|
|
// Your convention: 0 means unset for integer PKs
|
|
unset = (static_cast<std::uint64_t>(pk) == 0ULL);
|
|
}
|
|
}
|
|
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
|
|
template<typename T>
|
|
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
|
|
template<class P>
|
|
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
|
template<class P>
|
|
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
|
|
};
|
|
|
|
struct pk_value_extractor {
|
|
std::uint64_t value{0};
|
|
|
|
template<class V>
|
|
void on_primary_key(const char * /*id*/, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
|
|
value = static_cast<std::uint64_t>(pk);
|
|
}
|
|
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
|
|
template<typename T>
|
|
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
|
|
template<class P>
|
|
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
|
template<class P>
|
|
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
|
|
};
|
|
|
|
class insert_query_builder {
|
|
public:
|
|
using step_query_t = std::variant<executable_query, fetchable_query>;
|
|
|
|
struct insert_step {
|
|
step_query_t query;
|
|
|
|
// Session uses these to handle manual/sequence/table pre-insert PKs
|
|
utils::generator_type pk_generator{utils::generator_type::Manual};
|
|
std::string pk_name;
|
|
|
|
std::function<bool()> pk_is_unset{};
|
|
std::function<void(std::uint64_t)> set_pk{};
|
|
|
|
// Identity post-insert
|
|
std::function<void(const sql::record &)> apply_returning{};
|
|
};
|
|
|
|
public:
|
|
explicit insert_query_builder(const basic_schema &schema);
|
|
|
|
template<class EntityType>
|
|
utils::result<std::vector<insert_step>, query_build_error> build(const object::object_ptr<EntityType> &ptr) {
|
|
if (const auto it = schema_.find(typeid(EntityType)); it == schema_.end()) {
|
|
return utils::failure(query_build_error::UnknownType);
|
|
}
|
|
|
|
steps_.clear();
|
|
visited_.clear();
|
|
|
|
build_for(ptr);
|
|
|
|
// 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 V >
|
|
static void on_primary_key(const char * /*id*/, V &, 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 &, 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) {
|
|
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 Collection>
|
|
static void on_has_many(const char * /*id*/, Collection &/*con*/, const char * /*join_column*/, const utils::foreign_attributes & ) {}
|
|
template<class Collection>
|
|
void on_has_many_to_many(const char *id, Collection &container, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes & ) {
|
|
if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) {
|
|
return;
|
|
}
|
|
if (current_entity_pk_ == 0ULL) {
|
|
// Without a local PK we cannot create relation rows
|
|
// (PK may be assigned later by Identity; that case needs a different mechanism than requested here)
|
|
return;
|
|
}
|
|
|
|
const auto it = schema_.find(std::string{id});
|
|
if (it == schema_.end()) {
|
|
throw query_builder_exception(query_build_error::UnknownType);
|
|
}
|
|
const table &relation_table = it->second.table();
|
|
|
|
for (auto &obj : container) {
|
|
if (!obj) {
|
|
continue;
|
|
}
|
|
|
|
// Ensure target exists as dependency (deps first)
|
|
if (!obj.is_persistent()) {
|
|
using dep_t = std::remove_reference_t<decltype(*obj)>;
|
|
build_for<dep_t>(obj);
|
|
}
|
|
|
|
// Extract FK value from the foreign object
|
|
pk_value_extractor fk{};
|
|
access::process(fk, *obj);
|
|
if (fk.value == 0ULL) {
|
|
continue;
|
|
}
|
|
|
|
// Build INSERT into relation table with the 2 FK columns
|
|
insert_step rel_step{};
|
|
rel_step.query = executable_query{
|
|
insert()
|
|
.into(relation_table, {table_column{&relation_table, join_column}, table_column{&relation_table, inverse_join_column}})
|
|
.values({utils::database_type{current_entity_pk_}, utils::database_type{fk.value}})
|
|
};
|
|
relation_steps_.push_back(std::move(rel_step));
|
|
}
|
|
}
|
|
template<class C>
|
|
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
|
|
|
|
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) {
|
|
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_name = info.primary_key_attribute()->name();
|
|
step.pk_generator = it->second.pk_generator().type();
|
|
|
|
step.pk_is_unset = [ptr, name = step.pk_name]() {
|
|
pk_unset_checker chk{name};
|
|
access::process(chk, *ptr);
|
|
return chk.unset;
|
|
};
|
|
|
|
step.set_pk = [ptr, name = step.pk_name](std::uint64_t v) {
|
|
pk_setter setter{name, v};
|
|
access::process(setter, *ptr);
|
|
};
|
|
}
|
|
|
|
if (info.has_primary_key() && step.pk_generator == utils::generator_type::Identity) {
|
|
const table_column pk_col(&it->second.table(), step.pk_name);
|
|
step.query = fetchable_query{insert().into(it->second.table()).values(*ptr).returning(pk_col)};
|
|
step.apply_returning = [ptr, pk_name = step.pk_name](const sql::record &rec) {
|
|
if (auto v = rec.at<std::uint64_t>(pk_name); v.has_value()) {
|
|
pk_setter setter{pk_name, *v};
|
|
access::process(setter, *ptr);
|
|
}
|
|
};
|
|
} else {
|
|
step.query = executable_query{insert().into(it->second.table()).values(*ptr)};
|
|
}
|
|
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);
|
|
}
|
|
private:
|
|
const basic_schema &schema_;
|
|
|
|
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::uint64_t current_entity_pk_{0};
|
|
};
|
|
}
|
|
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP
|