session and insert query builder progress
This commit is contained in:
parent
0a0e3752ea
commit
d734d647ed
|
|
@ -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_) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ enum class error_code : uint8_t {
|
|||
NoPrimaryKey,
|
||||
FailedToBuildQuery,
|
||||
FailedToFindObject,
|
||||
FailedToInsertObject,
|
||||
Failed
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue