From e5072741f0b7540846aa5e978a30bd9fb33ae97b Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 18 Feb 2026 15:32:27 +0100 Subject: [PATCH] insert_query_builder progress --- include/matador/object/object_proxy.hpp | 8 +- include/matador/object/object_ptr.hpp | 9 +- include/matador/orm/session.hpp | 137 ++++++++---------- .../matador/query/insert_query_builder.hpp | 101 +++++++++---- source/orm/query/schema.cpp | 6 - 5 files changed, 142 insertions(+), 119 deletions(-) diff --git a/include/matador/object/object_proxy.hpp b/include/matador/object/object_proxy.hpp index c317fe4..d9ca1c5 100644 --- a/include/matador/object/object_proxy.hpp +++ b/include/matador/object/object_proxy.hpp @@ -33,7 +33,8 @@ public: object_proxy(std::weak_ptr> resolver, std::shared_ptr obj) : obj_(obj) , resolver_(resolver) - , pk_(primary_key_resolver::resolve_object(*obj).pk) { + , pk_(primary_key_resolver::resolve_object(*obj).pk) + , state_(object_state::Persistent){ } // Transient @@ -60,8 +61,9 @@ public: bool is_transient() const { return state_ == object_state::Transient; } bool is_detached() const { return state_ == object_state::Detached; } bool is_removed() const { return state_ == object_state::Removed; } + void change_state(const object_state state) { - state_ = state; + state_.store(state, std::memory_order_release); } private: Type* resolve() const { @@ -86,7 +88,7 @@ private: std::shared_ptr obj_{}; std::weak_ptr> resolver_{}; utils::identifier pk_{}; - object_state state_{object_state::Transient}; + std::atomic state_{object_state::Transient}; mutable std::mutex mutex_{}; }; } diff --git a/include/matador/object/object_ptr.hpp b/include/matador/object/object_ptr.hpp index f96df71..a4668c2 100644 --- a/include/matador/object/object_ptr.hpp +++ b/include/matador/object/object_ptr.hpp @@ -41,8 +41,8 @@ public: void reset() { proxy_.reset(); } - operator bool() { return valid(); } - bool valid() { return proxy_ != nullptr; } + operator bool() const { return valid(); } + [[nodiscard]] bool valid() const { return proxy_ != nullptr; } [[nodiscard]] bool has_primary_key() const { return proxy_->has_primary_key(); } [[nodiscard]] const utils::identifier &primary_key() const { return proxy_->primary_key(); } @@ -53,6 +53,11 @@ public: [[nodiscard]] bool is_detached() const { return !proxy_->is_detached(); } [[nodiscard]] bool is_removed() const { return !proxy_->is_removed(); } + void change_state(object_state s) { + if (proxy_) { + proxy_->change_state(s); + } + } private: std::shared_ptr > proxy_{}; }; diff --git a/include/matador/orm/session.hpp b/include/matador/orm/session.hpp index c5f5359..a93f054 100644 --- a/include/matador/orm/session.hpp +++ b/include/matador/orm/session.hpp @@ -97,52 +97,6 @@ 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); @@ -202,6 +156,10 @@ private: template utils::result, utils::error> session::insert(object::object_ptr obj) { + if (obj.is_persistent()) { + 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.")); @@ -216,57 +174,78 @@ utils::result, utils::error> session::insert(object::ob // 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 (step.pk_is_unset && step.set_pk) { + if (step.pk_generator == utils::generator_type::Manually) { + if (step.pk_is_unset()) { + return utils::failure(make_error(error_code::NoPrimaryKey, "Manual primary key is required but unset.")); + } + } else if (step.pk_generator == utils::generator_type::Sequence) { + if (step.pk_is_unset()) { + // hard-coded naming as you specified earlier + // _seq (table name known in schema meta; if you prefer, store it in step too) + // For now, we derive from the schema type used for the root insert: simplistic but works if table name == entity name. + const std::string seq_name = it->second.name() + "_seq"; + + auto id_res = query::query::select().nextval(seq_name).fetch_value(*this); + if (!id_res.is_ok() || !id_res.value().has_value()) { + return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to obtain next sequence value.")); + } + step.set_pk(*id_res.value()); + } + } else if (step.pk_generator == utils::generator_type::Table) { + if (step.pk_is_unset()) { + // TODO: implement table id generation; same idea as above: + // UPDATE _tbl_seq ... RETURNING ... + return utils::failure(make_error(error_code::Failed, "Table primary key generator not implemented yet.")); + } + } + } + + // --- Execute step --- + if (std::holds_alternative(step.query)) { + const auto &q = std::get(step.query); + const auto exec_res = q.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 + const auto &q = std::get(step.query); + auto rec_res = q.fetch_one(*this); 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()); } } + obj.change_state(object::object_state::Persistent); 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.")); - } - - auto res = query::query::insert() - .into(it->second.name(), query::generator::columns(schema_)) - .values(query::generator::placeholders()) - .prepare(*this); - if (!res) { - return utils::failure(res.err()); - } - - if (const auto insert_result = res->bind(*obj).execute(); !insert_result.is_ok()) { - return utils::failure(insert_result.err()); - } - 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.")); + // } + // + // auto res = query::query::insert() + // .into(it->second.name(), query::generator::columns(schema_)) + // .values(query::generator::placeholders()) + // .prepare(*this); + // if (!res) { + // return utils::failure(res.err()); + // } + // + // if (const auto insert_result = res->bind(*obj).execute(); !insert_result.is_ok()) { + // return utils::failure(insert_result.err()); + // } + // return utils::ok(obj); } class pk_object_binder final { diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 07269db..46c126d 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -33,11 +33,47 @@ struct pk_setter { static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} }; +struct pk_unset_checker { + const std::string &name; + bool unset{true}; + + template + void on_primary_key(const char *id, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) { + if (id != nullptr && name == id) { + // Your convention: 0 means unset for integer PKs + unset = (static_cast(pk) == 0ULL); + } + } + 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: + using step_query_t = std::variant; + struct insert_step { - executable_query query; - bool has_returning{false}; + step_query_t query; + + // Session uses these to handle manual/sequence/table pre-insert PKs + utils::generator_type pk_generator{utils::generator_type::Manually}; + std::string pk_name; + + std::function pk_is_unset{}; + std::function set_pk{}; + + // Identity post-insert std::function apply_returning{}; }; @@ -68,13 +104,19 @@ public: template void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr) { - on_foreign_object(id, obj, attr); + on_foreign_object(obj, attr); } template void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr) { - on_foreign_object(id, obj, attr); + on_foreign_object(obj, attr); } + 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: template @@ -112,35 +154,36 @@ private: 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() && 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 { - 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); - } - } + insert_step step{}; + if (info.has_primary_key()) { + step.pk_name = info.primary_key_attribute()->name(); + step.pk_generator = it->second.generator_type(); + + step.pk_is_unset = [ptr, name = step.pk_name]() { + pk_unset_checker chk{name}; + access::process(chk, *ptr); + return chk.unset; + }; + + step.set_pk = [ptr, name = step.pk_name](std::uint64_t v) { + pk_setter setter{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, - {} - }; + if (info.has_primary_key() && step.pk_generator == utils::generator_type::Identity) { + const table_column pk_col(&it->second.table(), step.pk_name); + step.query = fetchable_query{query::query::insert().into(it->second.table()).values(*ptr).returning(pk_col)}; + step.apply_returning = [ptr, pk_name = step.pk_name](const sql::record &rec) { + if (auto v = rec.at(pk_name); v.has_value()) { + pk_setter setter{pk_name, *v}; + access::process(setter, *ptr); + } + }; + } else { + step.query = executable_query{query::query::insert().into(it->second.table()).values(*ptr)}; + } steps_.push_back(std::move(step)); } diff --git a/source/orm/query/schema.cpp b/source/orm/query/schema.cpp index 925c93a..b650527 100644 --- a/source/orm/query/schema.cpp +++ b/source/orm/query/schema.cpp @@ -25,7 +25,6 @@ utils::result schema::create(const sql::connection &conn) co .columns(node.info().attributes()) .compile(conn); - std::cout << ctx.sql << std::endl; if (auto result = conn.execute(ctx); !result) { return utils::failure(result.err()); } @@ -39,7 +38,6 @@ utils::result schema::create(const sql::connection &conn) co } auto ctx = build_add_constraint_context(node, cons, conn); - std::cout << ctx.sql << std::endl; if (auto result = conn.execute(ctx); !result) { return utils::failure(result.err()); } @@ -53,7 +51,6 @@ utils::result schema::create(const sql::connection &conn) co } auto ctx = build_add_constraint_context(node, cons, conn); - std::cout << ctx.sql << std::endl; if (auto result = conn.execute(ctx); !result) { return utils::failure(result.err()); } @@ -74,7 +71,6 @@ utils::result schema::drop(const sql::connection &conn) cons .drop_constraint(cons) .compile(conn); - std::cout << ctx.sql << std::endl; if (auto result = conn.execute(ctx); !result) { return utils::failure(result.err()); } @@ -92,7 +88,6 @@ utils::result schema::drop(const sql::connection &conn) cons .drop_constraint(cons) .compile(conn); - std::cout << ctx.sql << std::endl; if (auto result = conn.execute(ctx); !result) { return utils::failure(result.err()); } @@ -105,7 +100,6 @@ utils::result schema::drop(const sql::connection &conn) cons .table(node.name()) .compile(conn); - std::cout << ctx.sql << std::endl; if (auto result = conn.execute(ctx); !result) { return utils::failure(result.err()); }