session and insert query builder progress

This commit is contained in:
sascha 2026-04-16 16:53:06 +02:00
parent 0a0e3752ea
commit d734d647ed
7 changed files with 110 additions and 88 deletions

View File

@ -42,16 +42,16 @@ public:
void reset() { proxy_.reset(); }
operator bool() const { return valid(); }
[[nodiscard]] bool valid() const { return proxy_ != nullptr; }
[[nodiscard]] bool valid() const { return proxy_ != nullptr && !proxy_->empty(); }
[[nodiscard]] bool has_primary_key() const { return proxy_->has_primary_key(); }
[[nodiscard]] const utils::identifier &primary_key() const { return proxy_->primary_key(); }
void primary_key(const utils::identifier &pk) { proxy_->primary_key(pk); }
[[nodiscard]] bool is_persistent() const { return proxy_->is_persistent(); }
[[nodiscard]] bool is_transient() const { return !proxy_->is_transient(); }
[[nodiscard]] bool is_detached() const { return !proxy_->is_detached(); }
[[nodiscard]] bool is_removed() const { return !proxy_->is_removed(); }
[[nodiscard]] bool is_transient() const { return proxy_->is_transient(); }
[[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_) {

View File

@ -13,6 +13,7 @@ enum class error_code : uint8_t {
NoPrimaryKey,
FailedToBuildQuery,
FailedToFindObject,
FailedToInsertObject,
Failed
};

View File

@ -14,6 +14,7 @@
#include "matador/sql/connection.hpp"
#include "matador/sql/connection_pool.hpp"
#include "matador/sql/executor.hpp"
#include "matador/sql/record.hpp"
#include "matador/sql/resolver_service.hpp"
#include "matador/sql/statement.hpp"
#include "matador/sql/statement_cache.hpp"
@ -105,40 +106,35 @@ utils::result<object::object_ptr<Type>, utils::error> session::insert(object::ob
}
// Build dependency-ordered insert steps (deps first, root last)
query::insert_query_builder iqb(schema_, contexts_by_type_);
query::insert_query_builder<Type> iqb(schema_, contexts_by_type_);
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.pk_is_unset && step.set_pk) {
// if (step.pk_generator == utils::generator_type::Manual) {
// 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::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."));
// }
// }
// }
for (query::insert_step &step : *steps) {
auto stmt = cache_.acquire(step.ctx);
if (!stmt.is_ok()) {
return utils::failure(stmt.err());
}
if (step.pk_generator == utils::generator_type::Identity) {
// insert and read RETURNING
auto record = stmt->fetch_one();
if (!record.is_ok()) {
return utils::failure(record.err());
}
if (!record.value().has_value()) {
return utils::failure(make_error(error_code::FailedToFindObject, "Failed to insert object and retrieve identity."));
}
step.apply_returning(*record.value());
} else if (step.pk_generator == utils::generator_type::Sequence || step.pk_generator == utils::generator_type::Table) {
auto result = it->second.pk_generator().next_id(*this);
if (!result.is_ok()) {
return utils::failure(result.err());
}
step.apply_primary_key(utils::identifier{*result});
}
auto result = step.acquire_and_bind(cache_);
if (!result.is_ok()) {
@ -148,28 +144,6 @@ utils::result<object::object_ptr<Type>, utils::error> session::insert(object::ob
if (const auto exec_result = result->execute(); !exec_result.is_ok()) {
return utils::failure(exec_result.err());
}
// --- 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()) {
// return utils::failure(exec_res.err());
// }
// continue;
// }
//
// const auto &q = std::get<query::fetchable_query>(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);

View File

@ -1,7 +1,7 @@
#ifndef MATADOR_INSERT_QUERY_BUILDER_HPP
#define MATADOR_INSERT_QUERY_BUILDER_HPP
#include <utility>
#include "matador/object/collection.hpp"
#include "matador/query/basic_schema.hpp"
#include "matador/query/intermediates/executable_query.hpp"
@ -62,6 +62,7 @@ struct insert_step {
// Identity post-insert
std::function<void(const sql::record &)> apply_returning{};
std::function<void(const utils::identifier &)> apply_primary_key{};
std::function<void(sql::statement &)> bind_object{};
};
@ -110,21 +111,24 @@ public:
on_foreign_object(obj, attr);
}
template<class CollectionType>
void on_has_many(const char * /*id*/, CollectionType &objects, const char *join_column, const utils::foreign_attributes &attr, std::enable_if_t<object::is_object_ptr<typename CollectionType::value_type>::value> * = nullptr) {
void on_has_many(const char * /*id*/, object::collection<object::object_ptr<CollectionType>> &objects, const char *join_column, const utils::foreign_attributes &attr) {
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
return;
}
has_many_linker<typename CollectionType::value_type> linker(ptr_, join_column);
has_many_linker<ObjectType> linker(ptr_, join_column);
for (auto &obj : objects) {
obj.is_persistent() ? build_for(obj) : on_foreign_object(obj, attr);
if (obj.is_transient()) {
build_for(obj);
}
// obj.is_persistent() ? build_for(obj) : on_foreign_object(obj, attr);
access::process(linker, obj);
access::process(linker, *obj);
}
}
template<class CollectionType>
void on_has_many(const char * /*id*/, CollectionType &/*con*/, const char *, const utils::foreign_attributes &/*attr*/, std::enable_if_t<!object::is_object_ptr<typename CollectionType::value_type>::value> * = nullptr) {}
static void on_has_many(const char * /*id*/, object::collection<CollectionType> &/*con*/, const char *, const utils::foreign_attributes &/*attr*/) {}
template<class Collection>
void on_has_many_to_many(const char *id, Collection &container, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes & ) {
if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) {
@ -230,8 +234,10 @@ private:
id.assign(f.value());
step.pk_accessor.set(*ptr, id);
};
} else {
// step.query = executable_query{insert().into(it->second.table()).values(*ptr)};
} else if (info.has_primary_key() && step.pk_generator == utils::generator_type::Sequence || step.pk_generator == utils::generator_type::Table) {
step.apply_primary_key = [ptr, &step](const utils::identifier &id) {
step.pk_accessor.set(*ptr, id);
};
}
steps_.push_back(std::move(step));
}

View File

@ -198,16 +198,16 @@ private:
template <typename Target, typename Source>
bool in_range(Source value) {
if constexpr (std::is_signed_v<Source> == std::is_signed_v<Target>) {
return value >= static_cast<Target>(std::numeric_limits<Source>::min()) &&
value <= static_cast<Target>(std::numeric_limits<Source>::max());
return value >= static_cast<Target>((std::numeric_limits<Source>::min)()) &&
value <= static_cast<Target>((std::numeric_limits<Source>::max)());
} else if constexpr (std::is_signed_v<Source> && !std::is_signed_v<Target>) {
if (value < 0) {
return false;
}
using UnsignedSource = std::make_unsigned_t<Source>;
return static_cast<UnsignedSource>(value) <= std::numeric_limits<Target>::max();
return static_cast<UnsignedSource>(value) <= (std::numeric_limits<Target>::max)();
} else {
return value <= static_cast<std::make_unsigned_t<Target>>(std::numeric_limits<Target>::max());
return value <= static_cast<std::make_unsigned_t<Target>>((std::numeric_limits<Target>::max)());
}
}

View File

@ -20,6 +20,8 @@ std::string orm_category_impl::message(const int ev) const {
return "Failed to build query";
case error_code::FailedToFindObject:
return "Failed to find object";
case error_code::FailedToInsertObject:
return "Failed to insert object";
case error_code::Failed:
return "Failed";
default:

View File

@ -6,6 +6,8 @@
#include "matador/sql/connection.hpp"
#include "matador/sql/interface/connection_impl.hpp"
#include "matador/query/query_contexts.hpp"
#include "matador/query/query.hpp"
#include "matador/query/schema.hpp"
#include "matador/query/insert_query_builder.hpp"
@ -22,6 +24,46 @@ using namespace matador::sql;
using namespace matador::query;
using namespace matador::utils;
std::unordered_map<std::type_index, query_contexts> to_contexts_by_name(const schema& scm, const dialect& d) {
std::unordered_map<std::type_index, query_contexts> contexts_by_type;
for (const auto &[type, node] : scm) {
query_contexts queries;
// SELECT all
queries.select_all = select(node.table())
.from(node.name())
.compile(d);
if (node.table().has_primary_key()) {
// SELECT one
queries.select_one = select(node.table())
.from(node.name())
.where(*node.table().primary_key_column().value() == _)
.compile(d);
// UPDATE one
auto update_set = update(node.table());
for (const auto &col: node.table().columns()) {
update_set.set(col, _);
}
queries.update_one = update_set.where(*node.table().primary_key_column().value() == _)
.compile(d);
// DELETE one
queries.delete_one = remove()
.from(node.name())
.where(*node.table().primary_key_column().value() == _)
.compile(d);
}
// INSERT one
queries.insert = insert()
.into(node.name(), node.table())
.values(generator::placeholders(node.table().columns().size()))
.compile(d);
contexts_by_type[node.node().type_index()] = queries;
}
return contexts_by_type;
}
TEST_CASE("insert query builder test", "[query][insert_query_builder]") {
using namespace matador::test;
backend_provider::instance().register_backend("noop", std::make_unique<orm::test_backend_service>());
@ -32,7 +74,8 @@ TEST_CASE("insert query builder test", "[query][insert_query_builder]") {
.and_then( [&scm] { return scm.attach<flight>("flights"); } );
REQUIRE(result);
insert_query_builder iqb(scm);
const auto contexts_by_type = to_contexts_by_name(scm, db.dialect());
insert_query_builder<airplane> iqb(scm, contexts_by_type);
const auto a380 = make_object<airplane>(1, "Boeing", "A380" );
auto build_result = iqb.build(a380);
@ -40,12 +83,6 @@ TEST_CASE("insert query builder test", "[query][insert_query_builder]") {
const auto& stmts = *build_result;
REQUIRE(stmts.size() == 1);
const auto step = stmts.front();
REQUIRE(std::holds_alternative<executable_query>(step.query));
const auto sql = std::get<executable_query>(step.query).str(db);
REQUIRE(sql == R"(INSERT INTO "airplanes" ("id", "brand", "model") VALUES (1, 'Boeing', 'A380'))");
}
TEST_CASE("Test insert builder has many", "[query][insert_query_builder][has_many]") {
@ -58,19 +95,21 @@ TEST_CASE("Test insert builder has many", "[query][insert_query_builder][has_man
.and_then( [&scm] { return scm.attach<author>("authors"); } );
REQUIRE(result.is_ok());
// auto s_king = make_object<author>(1, "Steven", "King", "21.9.1947", 1956, false);
//
// s_king->books.push_back(make_object<book>(2, "Carrie", object_ptr<author>{}, 1974));
// s_king->books.push_back(make_object<book>(3, "The Shining", object_ptr<author>{}, 1977));
// s_king->books.push_back(make_object<book>(4, "It", object_ptr<author>{}, 1986));
// s_king->books.push_back(make_object<book>(5, "Misery", object_ptr<author>{}, 1987));
// s_king->books.push_back(make_object<book>(6, "The Dark Tower: The Gunslinger", object_ptr<author>{}, 1982));
//
// insert_query_builder iqb(scm);
//
// auto build_result = iqb.build(s_king);
// REQUIRE(build_result.is_ok());
//
// const auto& stmts = *build_result;
// REQUIRE(stmts.size() == 1);
auto s_king = make_object<author>(1, "Steven", "King", "21.9.1947", 1956, false);
s_king->books.push_back(make_object<book>(2, "Carrie", object_ptr<author>{}, 1974));
s_king->books.push_back(make_object<book>(3, "The Shining", object_ptr<author>{}, 1977));
s_king->books.push_back(make_object<book>(4, "It", object_ptr<author>{}, 1986));
s_king->books.push_back(make_object<book>(5, "Misery", object_ptr<author>{}, 1987));
s_king->books.push_back(make_object<book>(6, "The Dark Tower: The Gunslinger", object_ptr<author>{}, 1982));
const auto contexts_by_type = to_contexts_by_name(scm, db.dialect());
insert_query_builder<author> iqb(scm, contexts_by_type);
auto build_result = iqb.build(s_king);
REQUIRE(build_result.is_ok());
const auto& stmts = *build_result;
REQUIRE_FALSE(stmts.empty());
REQUIRE(stmts.size() == 6);
}