173 lines
5.2 KiB
C++
173 lines
5.2 KiB
C++
#ifndef MATADOR_DEPENDENCY_COLLECTOR_HPP
|
|
#define MATADOR_DEPENDENCY_COLLECTOR_HPP
|
|
|
|
#include "matador/utils/field_attributes.hpp"
|
|
#include "matador/utils/foreign_attributes.hpp"
|
|
#include "matador/utils/primary_key_attribute.hpp"
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <typeindex>
|
|
#include <cstdint>
|
|
#include <unordered_map>
|
|
|
|
namespace matador::query {
|
|
enum class pk_strategy_kind {
|
|
manual,
|
|
sequence,
|
|
table,
|
|
identity
|
|
};
|
|
|
|
enum class pk_state {
|
|
unknown, // z.B. identity vor insert, oder manual aber nicht gesetzt
|
|
known // id ist gesetzt/verfügbar
|
|
};
|
|
|
|
struct fk_ref {
|
|
std::string fk_column; // z.B. "author_id" (optional, für Debug/SQL)
|
|
void* parent_object_ptr; // Adresse des referenzierten Objekts (type-erased)
|
|
std::type_index parent_type;
|
|
};
|
|
|
|
struct pk_accessor {
|
|
// obj zeigt auf konkrete Entity-Instanz
|
|
bool (*is_known)(void* obj) = nullptr;
|
|
void (*set_u64)(void* obj, std::uint64_t id) = nullptr;
|
|
std::uint64_t (*get_u64)(void* obj) = nullptr;
|
|
};
|
|
|
|
struct entity_meta {
|
|
pk_strategy_kind strategy{};
|
|
pk_accessor pk;
|
|
// optional: table name, pk column name, etc.
|
|
};
|
|
|
|
struct meta_registry {
|
|
std::unordered_map<std::type_index, entity_meta> by_type;
|
|
|
|
const entity_meta& get(const std::type_index t) const {
|
|
return by_type.at(t);
|
|
}
|
|
|
|
template<class T>
|
|
void register_type(entity_meta m) {
|
|
by_type.emplace(std::type_index(typeid(T)), m);
|
|
}
|
|
};
|
|
|
|
struct dependency_collector {
|
|
std::vector<fk_ref> refs;
|
|
|
|
template<class V>
|
|
static void on_primary_key(const char*, V&, const utils::primary_key_attribute& /*attr*/ = utils::default_pk_attributes) {}
|
|
|
|
static void on_revision(const char*, std::uint64_t&) {}
|
|
|
|
template<class V>
|
|
static void on_attribute(const char*, V&, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
|
|
|
template<class Pointer>
|
|
void on_belongs_to(const char* id, Pointer& p, utils::foreign_attributes &attr) {
|
|
// Erwartung: Pointer verhält sich wie matador::object::object_ptr<T>
|
|
// also: p.get() liefert raw ptr, und Pointer::value_type / element_type ist T.
|
|
auto* raw = p.get();
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
|
|
using parent_type = typename Pointer::value_type; // falls object_ptr<T> so definiert ist
|
|
// Falls dein object_ptr anders heißt (z.B. element_type), hier anpassen.
|
|
|
|
refs.push_back(fk_ref{
|
|
id ? std::string{id} : std::string{},
|
|
static_cast<void*>(raw),
|
|
typeid(parent_type)
|
|
});
|
|
}
|
|
|
|
template<class Pointer>
|
|
static void on_has_one(const char*, Pointer&, const auto&) {}
|
|
|
|
template<class Container>
|
|
static void on_has_many(const char*, Container&, const char*, const auto&) {}
|
|
|
|
template<class Container>
|
|
static void on_has_many_to_many(const char*, Container&, const char*, const char*, const auto&) {}
|
|
|
|
template<class Container>
|
|
static void on_has_many_to_many(const char*, Container&, const auto&) {}
|
|
};
|
|
|
|
struct flush_node {
|
|
void* object_ptr = nullptr; // raw pointer (nicht owning)
|
|
std::type_index type = typeid(void);
|
|
|
|
pk_strategy_kind pk_strategy = pk_strategy_kind::manual;
|
|
pk_accessor pk{};
|
|
|
|
pk_state state = pk_state::unknown;
|
|
|
|
// eingehende Dependencies: child hängt von parents ab
|
|
std::vector<std::size_t> depends_on; // Indizes in nodes[]
|
|
bool inserted = false;
|
|
};
|
|
|
|
struct flush_plan {
|
|
std::vector<flush_node> nodes;
|
|
|
|
// map raw ptr -> node index
|
|
std::unordered_map<void*, std::size_t> index_by_ptr;
|
|
};
|
|
|
|
flush_plan build_nodes(const std::vector<std::pair<void*, std::type_index>>& new_objects, const meta_registry& reg) {
|
|
flush_plan plan;
|
|
plan.nodes.reserve(new_objects.size());
|
|
|
|
for (const auto& [ptr, ti] : new_objects) {
|
|
const auto& meta = reg.get(ti);
|
|
|
|
flush_node n;
|
|
n.object_ptr = ptr;
|
|
n.type = ti;
|
|
n.pk_strategy = meta.strategy;
|
|
n.pk = meta.pk;
|
|
n.state = n.pk.is_known(n.object_ptr) ? pk_state::known : pk_state::unknown;
|
|
|
|
plan.index_by_ptr.emplace(ptr, plan.nodes.size());
|
|
plan.nodes.push_back(n);
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
void add_edges_from_process(flush_plan& plan, const matador::query::process_registry& preg) {
|
|
using namespace matador::query;
|
|
for (std::size_t i = 0; i < plan.nodes.size(); ++i) { auto& node = plan.nodes[i];
|
|
dependency_collector dc;
|
|
preg.get(node.type).collect_deps(node.object_ptr, dc);
|
|
|
|
// 1) belongs_to: node (child) depends on parent
|
|
for (const auto& ref : dc.refs) {
|
|
auto it = plan.index_by_ptr.find(ref.parent_object_ptr);
|
|
if (it == plan.index_by_ptr.end()) {
|
|
// Parent ist nicht Teil des Plans (externe persistent entity?)
|
|
// Policy: wenn parent PK known -> ok; sonst Fehler oder cascade-add
|
|
continue;
|
|
}
|
|
node.depends_on.push_back(it->second);
|
|
}
|
|
|
|
// 2) has_many: inverse; daraus macht man Kanten child -> this(parent)
|
|
for (const auto& child : dc.has_many_children) {
|
|
auto it_child = plan.index_by_ptr.find(child.child_object_ptr);
|
|
if (it_child == plan.index_by_ptr.end()) continue;
|
|
|
|
auto& child_node = plan.nodes[it_child->second];
|
|
child_node.depends_on.push_back(i);
|
|
}
|
|
|
|
// 3) many-to-many: als separate work items (nicht in depends_on quetschen)
|
|
// dc.many_to_many_links -> später join-table tasks planen
|
|
} }
|
|
}
|
|
#endif //MATADOR_DEPENDENCY_COLLECTOR_HPP
|