primary key generator progress: added primary_key_generator_finder

This commit is contained in:
Sascha Kühl 2026-02-17 22:43:53 +01:00
parent 38dc42ade8
commit 0876535b44
10 changed files with 241 additions and 12 deletions

View File

@ -0,0 +1,129 @@
#ifndef MATADOR_PK_FIELD_LOCATOR_HPP
#define MATADOR_PK_FIELD_LOCATOR_HPP
#include "matador/utils/primary_key_attribute.hpp"
#include "matador/utils/field_attributes.hpp"
#include "matador/utils/foreign_attributes.hpp"
#include <variant>
#include <array>
#include <stdexcept>
#include <string>
namespace matador::object::detail {
using uuid16 = std::array<std::uint8_t, 16>;
using pk_value = std::variant<std::int64_t, std::string, uuid16>;
enum class pk_kind { integer, string, uuid };
struct pk_field_descriptor {
std::size_t offset{};
pk_kind kind{pk_kind::integer};
bool (*is_known_at)(const void* obj, std::size_t offset) = nullptr;
pk_value (*get_at)(const void* obj, std::size_t offset) = nullptr;
void (*set_at)(void* obj, std::size_t offset, const pk_value& v) = nullptr;
};
template < typename Type >
struct pk_field_locator {
Type *base{nullptr};
pk_field_descriptor desc{};
bool found{false};
explicit pk_field_locator (Type &obj)
: base(&obj) {}
template <typename PrimaryKeyType>
void on_primary_key(const char *, PrimaryKeyType &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
// Offset bestimmen
const auto* obj_bytes = reinterpret_cast<std::uint8_t*>(base);
const auto* pk_bytes = reinterpret_cast<std::uint8_t*>(&pk);
desc.offset = static_cast<std::size_t>(pk_bytes - obj_bytes);
if constexpr (std::is_integral_v<PrimaryKeyType> || std::is_enum_v<PrimaryKeyType>) {
desc.kind = pk_kind::integer;
desc.is_known_at = [](void *obj, const std::size_t off) -> bool {
auto *p = reinterpret_cast<PrimaryKeyType *>(static_cast<std::uint8_t *>(obj) + off);
return *p != PrimaryKeyType{};
};
desc.get_at = [](void *obj, const std::size_t off) -> pk_value {
auto *p = reinterpret_cast<PrimaryKeyType *>(static_cast<std::uint8_t *>(obj) + off);
return static_cast<std::int64_t>(*p);
};
desc.set_at = [](void *obj, const std::size_t off, const pk_value &v) {
auto *p = reinterpret_cast<PrimaryKeyType *>(static_cast<std::uint8_t *>(obj) + off);
if (auto *i = std::get_if<std::int64_t>(&v)) {
*p = static_cast<PrimaryKeyType>(*i);
} else {
throw std::logic_error("PK type mismatch: expected integer");
}
};
} else if constexpr (std::is_same_v<PrimaryKeyType, std::string>) {
desc.kind = pk_kind::string;
desc.is_known_at = [](void *obj, const std::size_t off) -> bool {
const auto *p = reinterpret_cast<std::string *>(static_cast<std::uint8_t *>(obj) + off);
return !p->empty();
};
desc.get_at = [](void *obj, const std::size_t off) -> pk_value {
auto *p = reinterpret_cast<std::string *>(static_cast<std::uint8_t *>(obj) + off);
return *p;
};
desc.set_at = [](void *obj, const std::size_t off, const pk_value &v) {
auto *p = reinterpret_cast<std::string *>(static_cast<std::uint8_t *>(obj) + off);
if (auto *s = std::get_if<std::string>(&v)) {
*p = *s;
} else {
throw std::logic_error("PK type mismatch: expected string");
}
};
} else if constexpr (std::is_same_v<PrimaryKeyType, uuid16>) {
desc.kind = pk_kind::uuid;
desc.is_known_at = [](void *obj, const std::size_t off) -> bool {
auto *p = reinterpret_cast<uuid16 *>(static_cast<std::uint8_t *>(obj) + off);
// “unknown” = all zeros
for (auto b: *p) { if (b != 0) return true; }
return false;
};
desc.get_at = [](void *obj, const std::size_t off) -> pk_value {
auto *p = reinterpret_cast<uuid16 *>(static_cast<std::uint8_t *>(obj) + off);
return *p;
};
desc.set_at = [](void *obj, const std::size_t off, const pk_value &v) {
auto *p = reinterpret_cast<uuid16 *>(static_cast<std::uint8_t *>(obj) + off);
if (auto *u = std::get_if<uuid16>(&v)) {
*p = *u;
} else {
throw std::logic_error("PK type mismatch: expected uuid16");
}
};
} else {
throw std::logic_error("Unsupported primary key field type");
}
found = true;
}
static void on_revision(const char *, std::uint64_t &) {}
template <typename V>
static void on_attribute(const char *, V &, const utils::field_attributes & = matador::utils::null_attributes) {}
template <typename Pointer>
static void on_belongs_to(const char *, Pointer &, const utils::foreign_attributes &) {}
template <typename Pointer>
static void on_has_one(const char *, Pointer &, const utils::foreign_attributes &) {}
template <typename Container>
static void on_has_many(const char *, Container &, const char *, const utils::foreign_attributes &) {}
template <typename Container>
static void on_has_many_to_many(const char *, Container &, const char *, const char *,const utils::foreign_attributes &) {}
template <typename Container>
static void on_has_many_to_many(const char *, Container &, const utils::foreign_attributes &) {}
};
}
#endif //MATADOR_PK_FIELD_LOCATOR_HPP

View File

@ -5,6 +5,7 @@
#include "matador/query/select_query_builder.hpp" #include "matador/query/select_query_builder.hpp"
#include "matador/query/criteria.hpp" #include "matador/query/criteria.hpp"
#include "matador/query/insert_query_builder.hpp"
#include "matador/query/query.hpp" #include "matador/query/query.hpp"
#include "matador/query/generator.hpp" #include "matador/query/generator.hpp"
#include "matador/query/schema.hpp" #include "matador/query/schema.hpp"
@ -153,12 +154,8 @@ public:
* @param obj Object to insert * @param obj Object to insert
* @return Inserted object * @return Inserted object
*/ */
// template<typename Type>
// utils::result<object::object_ptr<Type>, utils::error> insert(Type *obj);
template<typename Type> template<typename Type>
utils::result<object::object_ptr<Type>, utils::error> insert(object::object_ptr<Type> obj); utils::result<object::object_ptr<Type>, utils::error> insert(object::object_ptr<Type> obj);
// template<class Type, typename... Args>
// utils::result<object::object_ptr<Type>, utils::error> insert(Args &&... args);
template<typename Type> template<typename Type>
utils::result<object::object_ptr<Type>, utils::error> update(const object::object_ptr<Type> &obj); utils::result<object::object_ptr<Type>, utils::error> update(const object::object_ptr<Type> &obj);
@ -205,6 +202,54 @@ private:
template<typename Type> template<typename Type>
utils::result<object::object_ptr<Type>, utils::error> session::insert(object::object_ptr<Type> obj) { utils::result<object::object_ptr<Type>, utils::error> session::insert(object::object_ptr<Type> obj) {
const auto it = schema_.find(typeid(Type));
if (it == schema_.end()) {
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
}
// Build dependency-ordered insert steps (deps first, root last)
query::insert_query_builder iqb(schema_);
auto steps = iqb.build(obj);
if (!steps.is_ok()) {
return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build insert dependency queries."));
}
// Execute all steps; for Identity steps read RETURNING and write pk back into the object
for (auto &step : *steps) {
if (!step.has_returning) {
const auto exec_res = step.query.execute(*this);
if (!exec_res.is_ok()) {
return utils::failure(exec_res.err());
}
continue;
}
auto stmt_res = step.query.prepare(*this);
if (!stmt_res.is_ok()) {
return utils::failure(stmt_res.err());
}
// RETURNING produces a result set; fetch the first row (single-row insert)
auto rec_res = stmt_res->fetch_one(); // const overload => std::optional<sql::record>
if (!rec_res.is_ok()) {
return utils::failure(rec_res.err());
}
if (!rec_res.value().has_value()) {
return utils::failure(make_error(error_code::FailedToFindObject, "INSERT ... RETURNING did not return a row."));
}
if (step.apply_returning) {
step.apply_returning(*rec_res.value());
}
}
return utils::ok(obj);
const auto it = schema_.find(typeid(Type)); const auto it = schema_.find(typeid(Type));
if (it == schema_.end()) { if (it == schema_.end()) {
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));

View File

@ -16,17 +16,51 @@
namespace matador::query { namespace matador::query {
class schema_node final { class schema_node final {
public: public:
schema_node(class table tab, const object::repository_node& node); schema_node(class table tab, utils::generator_type generator_type, const object::repository_node& node);
[[nodiscard]] const std::string& name() const; [[nodiscard]] const std::string& name() const;
[[nodiscard]] const class table& table() const; [[nodiscard]] const class table& table() const;
[[nodiscard]] utils::generator_type generator_type() const;
[[nodiscard]] const object::repository_node& node() const; [[nodiscard]] const object::repository_node& node() const;
private: private:
class table table_; class table table_;
utils::generator_type generator_type_{};
const object::repository_node& node_; const object::repository_node& node_;
}; };
class primary_key_generator_finder final {
public:
template< typename Type >
utils::generator_type find(const object::object_info<Type>& info) {
generator_type_ = utils::generator_type::Manually;
access::process(*this, info.prototype());
return generator_type_;
}
template<class V>
void on_primary_key(const char * /*id*/, V &/*pk*/, const utils::primary_key_attribute &attr = utils::default_pk_attributes) {
generator_type_ = attr.generator();
}
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 & ) {}
private:
utils::generator_type generator_type_{};
};
class basic_schema { class basic_schema {
public: public:
using schema_node_map = std::unordered_map<std::type_index, schema_node>; using schema_node_map = std::unordered_map<std::type_index, schema_node>;

View File

@ -114,7 +114,7 @@ private:
// 2) Build INSERT for this object // 2) Build INSERT for this object
// For Identity PK strategy: append RETURNING(pk_column) // For Identity PK strategy: append RETURNING(pk_column)
const auto &info = it->second.node().info(); const auto &info = it->second.node().info();
if (info.has_primary_key() && info.primary_key_attribute()->generator() == utils::generator_type::Identity) { if (info.has_primary_key() && it->second.generator_type() == utils::generator_type::Identity) {
const std::string pk_name = info.primary_key_attribute()->name(); const std::string pk_name = info.primary_key_attribute()->name();
const table_column pk_col(&it->second.table(), pk_name); const table_column pk_col(&it->second.table(), pk_name);
insert_step step { insert_step step {

View File

@ -270,7 +270,7 @@ private:
[[nodiscard]] static sql::query_context build_add_constraint_context(const object::repository_node& node, const object::restriction& cons, const sql::connection &conn) ; [[nodiscard]] static sql::query_context build_add_constraint_context(const object::repository_node& node, const object::restriction& cons, const sql::connection &conn) ;
[[nodiscard]] static sql::query_context build_drop_constraint_context(const object::repository_node& node, const object::restriction& cons, const sql::connection &conn) ; [[nodiscard]] static sql::query_context build_drop_constraint_context(const object::repository_node& node, const object::restriction& cons, const sql::connection &conn) ;
iterator insert_table(const std::type_index& ti, const object::repository_node &node); iterator insert_table(const std::type_index& ti, const object::repository_node &node, utils::generator_type generator_type);
private: private:
template<typename Type> template<typename Type>
@ -289,7 +289,10 @@ utils::result<void, utils::error> schema::drop_table(const sql::connection &conn
template <typename Type> template <typename Type>
void schema_observer<Type>::on_attach(const object::repository_node &node, const Type &/*prototype*/) const { void schema_observer<Type>::on_attach(const object::repository_node &node, const Type &/*prototype*/) const {
const auto it = schema_.insert_table(typeid(Type), node); primary_key_generator_finder finder;
const auto generator_type = finder.find(node.info<Type>().get());
const auto it = schema_.insert_table(typeid(Type), node, generator_type);
if (!it->second.node().info().has_primary_key()) { if (!it->second.node().info().has_primary_key()) {
return; return;

View File

@ -122,6 +122,7 @@ add_library(matador-core STATIC
utils/version.cpp utils/version.cpp
../../include/matador/object/collection_utils.hpp ../../include/matador/object/collection_utils.hpp
object/collection_utils.cpp object/collection_utils.cpp
../../include/matador/object/pk_field_locator.hpp
) )
target_link_libraries(matador-core ${CMAKE_DL_LIBS}) target_link_libraries(matador-core ${CMAKE_DL_LIBS})

View File

@ -189,6 +189,7 @@ add_library(matador-orm STATIC
sql/statement.cpp sql/statement.cpp
sql/statement_cache.cpp sql/statement_cache.cpp
../../include/matador/query/insert_query_builder.hpp ../../include/matador/query/insert_query_builder.hpp
query/abstract_pk_generator.hpp
) )
target_include_directories(matador-orm target_include_directories(matador-orm

View File

@ -0,0 +1,11 @@
#ifndef MATADOR_ABSTRACT_PK_GENERATOR_HPP
#define MATADOR_ABSTRACT_PK_GENERATOR_HPP
namespace matador::query {
class abstract_pk_generator {
public:
virtual ~abstract_pk_generator() = default;
virtual unsigned int generate() = 0;
};
}
#endif //MATADOR_ABSTRACT_PK_GENERATOR_HPP

View File

@ -3,8 +3,9 @@
#include "matador/sql/executor.hpp" #include "matador/sql/executor.hpp"
namespace matador::query { namespace matador::query {
schema_node::schema_node(class table tab, const object::repository_node& node) schema_node::schema_node(class table tab, utils::generator_type generator_type, const object::repository_node& node)
: table_(std::move(tab)) : table_(std::move(tab))
, generator_type_(generator_type)
, node_(std::move(node)) { , node_(std::move(node)) {
} }
@ -16,6 +17,10 @@ const table &schema_node::table() const {
return table_; return table_;
} }
utils::generator_type schema_node::generator_type() const {
return generator_type_;
}
const object::repository_node& schema_node::node() const { const object::repository_node& schema_node::node() const {
return node_; return node_;
} }

View File

@ -165,11 +165,11 @@ sql::query_context schema::build_drop_constraint_context(const object::repositor
.compile(conn); .compile(conn);
} }
schema::iterator schema::insert_table(const std::type_index &ti, const object::repository_node &node) { schema::iterator schema::insert_table(const std::type_index &ti, const object::repository_node &node, const utils::generator_type generator_type) {
std::vector<table_column> columns; std::vector<table_column> columns;
for (const auto &attr: node.info().attributes()) { for (const auto &attr: node.info().attributes()) {
columns.emplace_back(nullptr, attr.name(), attr.type(), attr.attributes()); columns.emplace_back(nullptr, attr.name(), attr.type(), attr.attributes());
} }
return schema_nodes_.insert({ti, schema_node{table(node.name(), columns), node}}).first; return schema_nodes_.insert({ti, schema_node{table(node.name(), columns), generator_type, node}}).first;
} }
} // namespace matador::query } // namespace matador::query