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(); } void reset() { proxy_.reset(); }
operator bool() const { return valid(); } 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]] 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(); }
void primary_key(const utils::identifier &pk) { proxy_->primary_key(pk); } void primary_key(const utils::identifier &pk) { proxy_->primary_key(pk); }
[[nodiscard]] bool is_persistent() const { return proxy_->is_persistent(); } [[nodiscard]] bool is_persistent() const { return proxy_->is_persistent(); }
[[nodiscard]] bool is_transient() const { return !proxy_->is_transient(); } [[nodiscard]] bool is_transient() const { return proxy_->is_transient(); }
[[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) { void change_state(object_state s) {
if (proxy_) { if (proxy_) {

View File

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

View File

@ -14,6 +14,7 @@
#include "matador/sql/connection.hpp" #include "matador/sql/connection.hpp"
#include "matador/sql/connection_pool.hpp" #include "matador/sql/connection_pool.hpp"
#include "matador/sql/executor.hpp" #include "matador/sql/executor.hpp"
#include "matador/sql/record.hpp"
#include "matador/sql/resolver_service.hpp" #include "matador/sql/resolver_service.hpp"
#include "matador/sql/statement.hpp" #include "matador/sql/statement.hpp"
#include "matador/sql/statement_cache.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) // 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); auto steps = iqb.build(obj);
if (!steps.is_ok()) { if (!steps.is_ok()) {
return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build insert dependency queries.")); 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 // Execute all steps; for Identity steps read RETURNING and write pk back into the object
for (auto &step : *steps) { for (query::insert_step &step : *steps) {
// if (step.pk_is_unset && step.set_pk) { auto stmt = cache_.acquire(step.ctx);
// if (step.pk_generator == utils::generator_type::Manual) { if (!stmt.is_ok()) {
// if (step.pk_is_unset()) { return utils::failure(stmt.err());
// return utils::failure(make_error(error_code::NoPrimaryKey, "Manual primary key is required but unset.")); }
// } if (step.pk_generator == utils::generator_type::Identity) {
// } else if (step.pk_generator == utils::generator_type::Sequence) { // insert and read RETURNING
// if (step.pk_is_unset()) { auto record = stmt->fetch_one();
// // hard-coded naming as you specified earlier if (!record.is_ok()) {
// // <table_name>_seq (table name known in schema meta; if you prefer, store it in step too) return utils::failure(record.err());
// // 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"; if (!record.value().has_value()) {
// return utils::failure(make_error(error_code::FailedToFindObject, "Failed to insert object and retrieve identity."));
// auto id_res = query::select().nextval(seq_name).fetch_value<std::uint64_t>(*this); }
// if (!id_res.is_ok() || !id_res.value().has_value()) { step.apply_returning(*record.value());
// return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to obtain next sequence 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);
// step.set_pk(*id_res.value()); if (!result.is_ok()) {
// } return utils::failure(result.err());
// } else if (step.pk_generator == utils::generator_type::Table) { }
// if (step.pk_is_unset()) { step.apply_primary_key(utils::identifier{*result});
// // 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."));
// }
// }
// }
auto result = step.acquire_and_bind(cache_); auto result = step.acquire_and_bind(cache_);
if (!result.is_ok()) { 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()) { if (const auto exec_result = result->execute(); !exec_result.is_ok()) {
return utils::failure(exec_result.err()); 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); obj.change_state(object::object_state::Persistent);

View File

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

View File

@ -198,16 +198,16 @@ private:
template <typename Target, typename Source> template <typename Target, typename Source>
bool in_range(Source value) { bool in_range(Source value) {
if constexpr (std::is_signed_v<Source> == std::is_signed_v<Target>) { if constexpr (std::is_signed_v<Source> == std::is_signed_v<Target>) {
return value >= static_cast<Target>(std::numeric_limits<Source>::min()) && return value >= static_cast<Target>((std::numeric_limits<Source>::min)()) &&
value <= static_cast<Target>(std::numeric_limits<Source>::max()); value <= static_cast<Target>((std::numeric_limits<Source>::max)());
} else if constexpr (std::is_signed_v<Source> && !std::is_signed_v<Target>) { } else if constexpr (std::is_signed_v<Source> && !std::is_signed_v<Target>) {
if (value < 0) { if (value < 0) {
return false; return false;
} }
using UnsignedSource = std::make_unsigned_t<Source>; 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 { } 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"; return "Failed to build query";
case error_code::FailedToFindObject: case error_code::FailedToFindObject:
return "Failed to find object"; return "Failed to find object";
case error_code::FailedToInsertObject:
return "Failed to insert object";
case error_code::Failed: case error_code::Failed:
return "Failed"; return "Failed";
default: default:

View File

@ -6,6 +6,8 @@
#include "matador/sql/connection.hpp" #include "matador/sql/connection.hpp"
#include "matador/sql/interface/connection_impl.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/schema.hpp"
#include "matador/query/insert_query_builder.hpp" #include "matador/query/insert_query_builder.hpp"
@ -22,6 +24,46 @@ using namespace matador::sql;
using namespace matador::query; using namespace matador::query;
using namespace matador::utils; 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]") { TEST_CASE("insert query builder test", "[query][insert_query_builder]") {
using namespace matador::test; using namespace matador::test;
backend_provider::instance().register_backend("noop", std::make_unique<orm::test_backend_service>()); 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"); } ); .and_then( [&scm] { return scm.attach<flight>("flights"); } );
REQUIRE(result); 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" ); const auto a380 = make_object<airplane>(1, "Boeing", "A380" );
auto build_result = iqb.build(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; const auto& stmts = *build_result;
REQUIRE(stmts.size() == 1); 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]") { 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"); } ); .and_then( [&scm] { return scm.attach<author>("authors"); } );
REQUIRE(result.is_ok()); REQUIRE(result.is_ok());
// auto s_king = make_object<author>(1, "Steven", "King", "21.9.1947", 1956, false); 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>(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>(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>(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>(5, "Misery", object_ptr<author>{}, 1987));
// s_king->books.push_back(make_object<book>(6, "The Dark Tower: The Gunslinger", object_ptr<author>{}, 1982)); s_king->books.push_back(make_object<book>(6, "The Dark Tower: The Gunslinger", object_ptr<author>{}, 1982));
//
// insert_query_builder iqb(scm); 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()); auto build_result = iqb.build(s_king);
// REQUIRE(build_result.is_ok());
// const auto& stmts = *build_result;
// REQUIRE(stmts.size() == 1); const auto& stmts = *build_result;
REQUIRE_FALSE(stmts.empty());
REQUIRE(stmts.size() == 6);
} }