query/include/matador/query/dependency_collector.hpp

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