From 0876535b44f4e99355b8c3243d59afe075f27b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Tue, 17 Feb 2026 22:43:53 +0100 Subject: [PATCH] primary key generator progress: added primary_key_generator_finder --- include/matador/object/pk_field_locator.hpp | 129 ++++++++++++++++++ include/matador/orm/session.hpp | 53 ++++++- include/matador/query/basic_schema.hpp | 36 ++++- .../matador/query/insert_query_builder.hpp | 2 +- include/matador/query/schema.hpp | 7 +- source/core/CMakeLists.txt | 1 + source/orm/CMakeLists.txt | 3 +- source/orm/query/abstract_pk_generator.hpp | 11 ++ source/orm/query/basic_schema.cpp | 7 +- source/orm/query/schema.cpp | 4 +- 10 files changed, 241 insertions(+), 12 deletions(-) create mode 100644 include/matador/object/pk_field_locator.hpp create mode 100644 source/orm/query/abstract_pk_generator.hpp diff --git a/include/matador/object/pk_field_locator.hpp b/include/matador/object/pk_field_locator.hpp new file mode 100644 index 0000000..ec1bffd --- /dev/null +++ b/include/matador/object/pk_field_locator.hpp @@ -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 +#include +#include +#include + +namespace matador::object::detail { +using uuid16 = std::array; +using pk_value = std::variant; + +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 + 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(base); + const auto* pk_bytes = reinterpret_cast(&pk); + desc.offset = static_cast(pk_bytes - obj_bytes); + if constexpr (std::is_integral_v || std::is_enum_v) { + desc.kind = pk_kind::integer; + + desc.is_known_at = [](void *obj, const std::size_t off) -> bool { + auto *p = reinterpret_cast(static_cast(obj) + off); + return *p != PrimaryKeyType{}; + }; + + desc.get_at = [](void *obj, const std::size_t off) -> pk_value { + auto *p = reinterpret_cast(static_cast(obj) + off); + return static_cast(*p); + }; + + desc.set_at = [](void *obj, const std::size_t off, const pk_value &v) { + auto *p = reinterpret_cast(static_cast(obj) + off); + if (auto *i = std::get_if(&v)) { + *p = static_cast(*i); + } else { + throw std::logic_error("PK type mismatch: expected integer"); + } + }; + } else if constexpr (std::is_same_v) { + desc.kind = pk_kind::string; + + desc.is_known_at = [](void *obj, const std::size_t off) -> bool { + const auto *p = reinterpret_cast(static_cast(obj) + off); + return !p->empty(); + }; + + desc.get_at = [](void *obj, const std::size_t off) -> pk_value { + auto *p = reinterpret_cast(static_cast(obj) + off); + return *p; + }; + + desc.set_at = [](void *obj, const std::size_t off, const pk_value &v) { + auto *p = reinterpret_cast(static_cast(obj) + off); + if (auto *s = std::get_if(&v)) { + *p = *s; + } else { + throw std::logic_error("PK type mismatch: expected string"); + } + }; + } else if constexpr (std::is_same_v) { + desc.kind = pk_kind::uuid; + + desc.is_known_at = [](void *obj, const std::size_t off) -> bool { + auto *p = reinterpret_cast(static_cast(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(static_cast(obj) + off); + return *p; + }; + + desc.set_at = [](void *obj, const std::size_t off, const pk_value &v) { + auto *p = reinterpret_cast(static_cast(obj) + off); + if (auto *u = std::get_if(&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 + static void on_attribute(const char *, V &, const utils::field_attributes & = matador::utils::null_attributes) {} + template + static void on_belongs_to(const char *, Pointer &, const utils::foreign_attributes &) {} + template + static void on_has_one(const char *, Pointer &, const utils::foreign_attributes &) {} + template + static void on_has_many(const char *, Container &, const char *, const utils::foreign_attributes &) {} + template + static void on_has_many_to_many(const char *, Container &, const char *, const char *,const utils::foreign_attributes &) {} + template + static void on_has_many_to_many(const char *, Container &, const utils::foreign_attributes &) {} +}; +} +#endif //MATADOR_PK_FIELD_LOCATOR_HPP diff --git a/include/matador/orm/session.hpp b/include/matador/orm/session.hpp index f2bc823..c5f5359 100644 --- a/include/matador/orm/session.hpp +++ b/include/matador/orm/session.hpp @@ -5,6 +5,7 @@ #include "matador/query/select_query_builder.hpp" #include "matador/query/criteria.hpp" +#include "matador/query/insert_query_builder.hpp" #include "matador/query/query.hpp" #include "matador/query/generator.hpp" #include "matador/query/schema.hpp" @@ -153,12 +154,8 @@ public: * @param obj Object to insert * @return Inserted object */ - // template - // utils::result, utils::error> insert(Type *obj); template utils::result, utils::error> insert(object::object_ptr obj); - // template - // utils::result, utils::error> insert(Args &&... args); template utils::result, utils::error> update(const object::object_ptr &obj); @@ -205,6 +202,54 @@ private: template utils::result, utils::error> session::insert(object::object_ptr 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 + 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)); if (it == schema_.end()) { return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type.")); diff --git a/include/matador/query/basic_schema.hpp b/include/matador/query/basic_schema.hpp index b471a9d..2173413 100644 --- a/include/matador/query/basic_schema.hpp +++ b/include/matador/query/basic_schema.hpp @@ -16,17 +16,51 @@ namespace matador::query { class schema_node final { 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 class table& table() const; + [[nodiscard]] utils::generator_type generator_type() const; [[nodiscard]] const object::repository_node& node() const; private: class table table_; + utils::generator_type generator_type_{}; const object::repository_node& node_; }; +class primary_key_generator_finder final { +public: + template< typename Type > + utils::generator_type find(const object::object_info& info) { + generator_type_ = utils::generator_type::Manually; + + access::process(*this, info.prototype()); + + return generator_type_; + } + template + 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 + static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {} + template + static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + template + static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + template + static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {} + template + 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 + static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} + +private: + utils::generator_type generator_type_{}; +}; + class basic_schema { public: using schema_node_map = std::unordered_map; diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 701e5b6..07269db 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -114,7 +114,7 @@ private: // 2) Build INSERT for this object // For Identity PK strategy: append RETURNING(pk_column) 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 table_column pk_col(&it->second.table(), pk_name); insert_step step { diff --git a/include/matador/query/schema.hpp b/include/matador/query/schema.hpp index d6a7651..e2cad6c 100644 --- a/include/matador/query/schema.hpp +++ b/include/matador/query/schema.hpp @@ -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_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: template @@ -289,7 +289,10 @@ utils::result schema::drop_table(const sql::connection &conn template void schema_observer::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().get()); + + const auto it = schema_.insert_table(typeid(Type), node, generator_type); if (!it->second.node().info().has_primary_key()) { return; diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index b4d5b47..badead0 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -122,6 +122,7 @@ add_library(matador-core STATIC utils/version.cpp ../../include/matador/object/collection_utils.hpp object/collection_utils.cpp + ../../include/matador/object/pk_field_locator.hpp ) target_link_libraries(matador-core ${CMAKE_DL_LIBS}) diff --git a/source/orm/CMakeLists.txt b/source/orm/CMakeLists.txt index f749105..f01d7ff 100644 --- a/source/orm/CMakeLists.txt +++ b/source/orm/CMakeLists.txt @@ -189,7 +189,8 @@ add_library(matador-orm STATIC sql/statement.cpp sql/statement_cache.cpp ../../include/matador/query/insert_query_builder.hpp -) + query/abstract_pk_generator.hpp + ) target_include_directories(matador-orm PUBLIC diff --git a/source/orm/query/abstract_pk_generator.hpp b/source/orm/query/abstract_pk_generator.hpp new file mode 100644 index 0000000..9241872 --- /dev/null +++ b/source/orm/query/abstract_pk_generator.hpp @@ -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 \ No newline at end of file diff --git a/source/orm/query/basic_schema.cpp b/source/orm/query/basic_schema.cpp index 9d43b04..108e373 100644 --- a/source/orm/query/basic_schema.cpp +++ b/source/orm/query/basic_schema.cpp @@ -3,8 +3,9 @@ #include "matador/sql/executor.hpp" 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)) +, generator_type_(generator_type) , node_(std::move(node)) { } @@ -16,6 +17,10 @@ const table &schema_node::table() const { return table_; } +utils::generator_type schema_node::generator_type() const { + return generator_type_; +} + const object::repository_node& schema_node::node() const { return node_; } diff --git a/source/orm/query/schema.cpp b/source/orm/query/schema.cpp index 55e5404..925c93a 100644 --- a/source/orm/query/schema.cpp +++ b/source/orm/query/schema.cpp @@ -165,11 +165,11 @@ sql::query_context schema::build_drop_constraint_context(const object::repositor .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 columns; for (const auto &attr: node.info().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