From 38dc42ade8f167796c421d07f7ae6720785c6230 Mon Sep 17 00:00:00 2001 From: sascha Date: Tue, 17 Feb 2026 16:19:14 +0100 Subject: [PATCH] insert_query_builder progress --- include/matador/orm/session.hpp | 46 +++++++ .../matador/query/insert_query_builder.hpp | 130 +++++++++++++++++- .../matador/query/query_builder_exception.hpp | 1 + 3 files changed, 173 insertions(+), 4 deletions(-) diff --git a/include/matador/orm/session.hpp b/include/matador/orm/session.hpp index 3d48062..f2bc823 100644 --- a/include/matador/orm/session.hpp +++ b/include/matador/orm/session.hpp @@ -96,6 +96,52 @@ private: const std::unordered_map &statements_per_column_; }; + /* + 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); +} */ class session final : public sql::executor { public: session(session_context &&ctx, const query::schema &scm); diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 48cc194..701e5b6 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -7,19 +7,56 @@ #include "matador/query/query_builder_exception.hpp" namespace matador::query { + +struct pk_setter { + const std::string &name; + std::uint64_t value; + + template + 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(value); + } + } + 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 & ) {} +}; + class insert_query_builder { +public: + struct insert_step { + executable_query query; + bool has_returning{false}; + std::function apply_returning{}; + }; + public: explicit insert_query_builder(const basic_schema &schema); template - utils::result, query_build_error> build(const object::object_ptr &ptr) { - const auto it = schema_.find(typeid(EntityType)); + utils::result, query_build_error> build(const object::object_ptr &ptr) { + auto it = schema_.find(typeid(EntityType)); if (it == schema_.end()) { return utils::failure(query_build_error::UnknownType); } - executable_query q = query::query::insert().into(it->second.table()).values(*ptr); - return utils::ok(std::vector{q}); + steps_.clear(); + visited_.clear(); + + build_for(ptr); + + return utils::ok(steps_); } template < class V > @@ -39,8 +76,93 @@ public: on_foreign_object(id, obj, attr); } +private: + template + static std::pair make_visit_key(const object::object_ptr &ptr) { + return {std::type_index(typeid(EntityType)), static_cast(&(*ptr))}; + } + + struct visit_key_hash { + size_t operator()(const std::pair &p) const noexcept { + // combine hashes (simple + sufficient here) + const size_t h1 = p.first.hash_code(); + const size_t h2 = std::hash{}(p.second); + return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); + } + }; + + template + void build_for(const object::object_ptr &ptr) { + if (!ptr) { + return; + } + + const auto key = make_visit_key(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 + // 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) { + const std::string pk_name = info.primary_key_attribute()->name(); + const table_column pk_col(&it->second.table(), pk_name); + insert_step step { + query::query::insert() + .into(it->second.table()) + .values(*ptr) + .returning(pk_col), + true, + [ptr, pk_name](const sql::record &rec) { + // record.at(name) returns optional + // We assume integer-like PKs for now (as in your examples). + if (auto v = rec.at(pk_name); v.has_value()) { + pk_setter setter{pk_name, *v}; + access::process(setter, *ptr); + } + } + }; + steps_.push_back(std::move(step)); + return; + } + + insert_step step{ + query::query::insert().into(it->second.table()).values(*ptr), + false, + {} + }; + steps_.push_back(std::move(step)); + } + + template + 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; + build_for(obj); + } private: const basic_schema &schema_; + + std::vector steps_; + std::unordered_set, visit_key_hash> visited_; }; } #endif //MATADOR_INSERT_QUERY_BUILDER_HPP \ No newline at end of file diff --git a/include/matador/query/query_builder_exception.hpp b/include/matador/query/query_builder_exception.hpp index 586f19b..499d37f 100644 --- a/include/matador/query/query_builder_exception.hpp +++ b/include/matador/query/query_builder_exception.hpp @@ -11,6 +11,7 @@ enum class query_build_error : std::uint8_t { Ok = 0, UnknownType, MissingPrimaryKey, + MissingObject, UnexpectedError, QueryError };