insert_query_builder progress

This commit is contained in:
sascha 2026-02-18 15:32:27 +01:00
parent 0876535b44
commit e5072741f0
5 changed files with 142 additions and 119 deletions

View File

@ -33,7 +33,8 @@ public:
object_proxy(std::weak_ptr<object_resolver<Type>> resolver, std::shared_ptr<Type> obj) object_proxy(std::weak_ptr<object_resolver<Type>> resolver, std::shared_ptr<Type> obj)
: obj_(obj) : obj_(obj)
, resolver_(resolver) , resolver_(resolver)
, pk_(primary_key_resolver::resolve_object(*obj).pk) { , pk_(primary_key_resolver::resolve_object(*obj).pk)
, state_(object_state::Persistent){
} }
// Transient // Transient
@ -60,8 +61,9 @@ public:
bool is_transient() const { return state_ == object_state::Transient; } bool is_transient() const { return state_ == object_state::Transient; }
bool is_detached() const { return state_ == object_state::Detached; } bool is_detached() const { return state_ == object_state::Detached; }
bool is_removed() const { return state_ == object_state::Removed; } bool is_removed() const { return state_ == object_state::Removed; }
void change_state(const object_state state) { void change_state(const object_state state) {
state_ = state; state_.store(state, std::memory_order_release);
} }
private: private:
Type* resolve() const { Type* resolve() const {
@ -86,7 +88,7 @@ private:
std::shared_ptr<Type> obj_{}; std::shared_ptr<Type> obj_{};
std::weak_ptr<object_resolver<Type>> resolver_{}; std::weak_ptr<object_resolver<Type>> resolver_{};
utils::identifier pk_{}; utils::identifier pk_{};
object_state state_{object_state::Transient}; std::atomic<object_state> state_{object_state::Transient};
mutable std::mutex mutex_{}; mutable std::mutex mutex_{};
}; };
} }

View File

@ -41,8 +41,8 @@ public:
void reset() { proxy_.reset(); } void reset() { proxy_.reset(); }
operator bool() { return valid(); } operator bool() const { return valid(); }
bool valid() { return proxy_ != nullptr; } [[nodiscard]] bool valid() const { return proxy_ != nullptr; }
[[nodiscard]] bool has_primary_key() const { return proxy_->has_primary_key(); } [[nodiscard]] bool has_primary_key() const { return proxy_->has_primary_key(); }
[[nodiscard]] const utils::identifier &primary_key() const { return proxy_->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_detached() const { return !proxy_->is_detached(); }
[[nodiscard]] bool is_removed() const { return !proxy_->is_removed(); } [[nodiscard]] bool is_removed() const { return !proxy_->is_removed(); }
void change_state(object_state s) {
if (proxy_) {
proxy_->change_state(s);
}
}
private: private:
std::shared_ptr<object_proxy<Type> > proxy_{}; std::shared_ptr<object_proxy<Type> > proxy_{};
}; };

View File

@ -97,52 +97,6 @@ private:
const std::unordered_map<std::string, sql::statement> &statements_per_column_; const std::unordered_map<std::string, sql::statement> &statements_per_column_;
}; };
/*
template<typename Type>
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);
} */
class session final : public sql::executor { class session final : public sql::executor {
public: public:
session(session_context &&ctx, const query::schema &scm); session(session_context &&ctx, const query::schema &scm);
@ -202,6 +156,10 @@ 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) {
if (obj.is_persistent()) {
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."));
@ -216,57 +174,78 @@ utils::result<object::object_ptr<Type>, utils::error> session::insert(object::ob
// Execute all steps; for Identity steps read RETURNING and write pk back into the object // Execute all steps; for Identity steps read RETURNING and write pk back into the object
for (auto &step : *steps) { for (auto &step : *steps) {
if (!step.has_returning) { if (step.pk_is_unset && step.set_pk) {
const auto exec_res = step.query.execute(*this); 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
// <table_name>_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<std::uint64_t>(*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 <table>_tbl_seq ... RETURNING ...
return utils::failure(make_error(error_code::Failed, "Table primary key generator not implemented yet."));
}
}
}
// --- Execute step ---
if (std::holds_alternative<query::executable_query>(step.query)) {
const auto &q = std::get<query::executable_query>(step.query);
const auto exec_res = q.execute(*this);
if (!exec_res.is_ok()) { if (!exec_res.is_ok()) {
return utils::failure(exec_res.err()); return utils::failure(exec_res.err());
} }
continue; continue;
} }
auto stmt_res = step.query.prepare(*this); const auto &q = std::get<query::fetchable_query>(step.query);
if (!stmt_res.is_ok()) { auto rec_res = q.fetch_one(*this);
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()) { if (!rec_res.is_ok()) {
return utils::failure(rec_res.err()); return utils::failure(rec_res.err());
} }
if (!rec_res.value().has_value()) { if (!rec_res.value().has_value()) {
return utils::failure(make_error(error_code::FailedToFindObject, "INSERT ... RETURNING did not return a row.")); return utils::failure(make_error(error_code::FailedToFindObject, "INSERT ... RETURNING did not return a row."));
} }
if (step.apply_returning) { if (step.apply_returning) {
step.apply_returning(*rec_res.value()); step.apply_returning(*rec_res.value());
} }
} }
obj.change_state(object::object_state::Persistent);
return utils::ok(obj); 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."));
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<Type>(schema_))
// .values(query::generator::placeholders<Type>())
auto res = query::query::insert() // .prepare(*this);
.into(it->second.name(), query::generator::columns<Type>(schema_)) // if (!res) {
.values(query::generator::placeholders<Type>()) // return utils::failure(res.err());
.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());
// }
if (const auto insert_result = res->bind(*obj).execute(); !insert_result.is_ok()) { // return utils::ok(obj);
return utils::failure(insert_result.err());
}
return utils::ok(obj);
} }
class pk_object_binder final { class pk_object_binder final {

View File

@ -33,11 +33,47 @@ struct pk_setter {
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} 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<class V>
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<std::uint64_t>(pk) == 0ULL);
}
}
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 & ) {}
};
class insert_query_builder { class insert_query_builder {
public: public:
using step_query_t = std::variant<executable_query, fetchable_query>;
struct insert_step { struct insert_step {
executable_query query; step_query_t query;
bool has_returning{false};
// 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<bool()> pk_is_unset{};
std::function<void(std::uint64_t)> set_pk{};
// Identity post-insert
std::function<void(const sql::record &)> apply_returning{}; std::function<void(const sql::record &)> apply_returning{};
}; };
@ -68,13 +104,19 @@ public:
template<class Pointer> template<class Pointer>
void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr) { 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<class Pointer> template<class Pointer>
void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr) { 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<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: private:
template<class EntityType> template<class EntityType>
@ -112,35 +154,36 @@ private:
access::process(*this, *ptr); access::process(*this, *ptr);
// 2) Build INSERT for this object // 2) Build INSERT for this object
// 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() && it->second.generator_type() == utils::generator_type::Identity) { insert_step step{};
const std::string pk_name = info.primary_key_attribute()->name(); if (info.has_primary_key()) {
const table_column pk_col(&it->second.table(), pk_name); step.pk_name = info.primary_key_attribute()->name();
insert_step step { step.pk_generator = it->second.generator_type();
query::query::insert()
.into(it->second.table()) step.pk_is_unset = [ptr, name = step.pk_name]() {
.values(*ptr) pk_unset_checker chk{name};
.returning(pk_col), access::process(chk, *ptr);
true, return chk.unset;
[ptr, pk_name](const sql::record &rec) { };
// record.at<T>(name) returns optional<T>
// We assume integer-like PKs for now (as in your examples). step.set_pk = [ptr, name = step.pk_name](std::uint64_t v) {
if (auto v = rec.at<std::uint64_t>(pk_name); v.has_value()) { pk_setter setter{name, v};
pk_setter setter{pk_name, *v}; access::process(setter, *ptr);
access::process(setter, *ptr);
}
}
}; };
steps_.push_back(std::move(step));
return;
} }
insert_step step{ if (info.has_primary_key() && step.pk_generator == utils::generator_type::Identity) {
query::query::insert().into(it->second.table()).values(*ptr), const table_column pk_col(&it->second.table(), step.pk_name);
false, 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<std::uint64_t>(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)); steps_.push_back(std::move(step));
} }

View File

@ -25,7 +25,6 @@ utils::result<void, utils::error> schema::create(const sql::connection &conn) co
.columns(node.info().attributes()) .columns(node.info().attributes())
.compile(conn); .compile(conn);
std::cout << ctx.sql << std::endl;
if (auto result = conn.execute(ctx); !result) { if (auto result = conn.execute(ctx); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }
@ -39,7 +38,6 @@ utils::result<void, utils::error> schema::create(const sql::connection &conn) co
} }
auto ctx = build_add_constraint_context(node, cons, conn); auto ctx = build_add_constraint_context(node, cons, conn);
std::cout << ctx.sql << std::endl;
if (auto result = conn.execute(ctx); !result) { if (auto result = conn.execute(ctx); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }
@ -53,7 +51,6 @@ utils::result<void, utils::error> schema::create(const sql::connection &conn) co
} }
auto ctx = build_add_constraint_context(node, cons, conn); auto ctx = build_add_constraint_context(node, cons, conn);
std::cout << ctx.sql << std::endl;
if (auto result = conn.execute(ctx); !result) { if (auto result = conn.execute(ctx); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }
@ -74,7 +71,6 @@ utils::result<void, utils::error> schema::drop(const sql::connection &conn) cons
.drop_constraint(cons) .drop_constraint(cons)
.compile(conn); .compile(conn);
std::cout << ctx.sql << std::endl;
if (auto result = conn.execute(ctx); !result) { if (auto result = conn.execute(ctx); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }
@ -92,7 +88,6 @@ utils::result<void, utils::error> schema::drop(const sql::connection &conn) cons
.drop_constraint(cons) .drop_constraint(cons)
.compile(conn); .compile(conn);
std::cout << ctx.sql << std::endl;
if (auto result = conn.execute(ctx); !result) { if (auto result = conn.execute(ctx); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }
@ -105,7 +100,6 @@ utils::result<void, utils::error> schema::drop(const sql::connection &conn) cons
.table(node.name()) .table(node.name())
.compile(conn); .compile(conn);
std::cout << ctx.sql << std::endl;
if (auto result = conn.execute(ctx); !result) { if (auto result = conn.execute(ctx); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }