diff --git a/include/matador/object/object_ptr.hpp b/include/matador/object/object_ptr.hpp index a4668c2..b7830c6 100644 --- a/include/matador/object/object_ptr.hpp +++ b/include/matador/object/object_ptr.hpp @@ -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_) { diff --git a/include/matador/orm/error_code.hpp b/include/matador/orm/error_code.hpp index 7bfc679..215bc04 100644 --- a/include/matador/orm/error_code.hpp +++ b/include/matador/orm/error_code.hpp @@ -13,6 +13,7 @@ enum class error_code : uint8_t { NoPrimaryKey, FailedToBuildQuery, FailedToFindObject, + FailedToInsertObject, Failed }; diff --git a/include/matador/orm/session.hpp b/include/matador/orm/session.hpp index eff0995..46222bf 100644 --- a/include/matador/orm/session.hpp +++ b/include/matador/orm/session.hpp @@ -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, 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 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 - // // _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(*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.")); - // } - // } - // } + 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, 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(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; - // } - // - // 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); diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 36e5770..274b00e 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -1,7 +1,7 @@ #ifndef MATADOR_INSERT_QUERY_BUILDER_HPP #define MATADOR_INSERT_QUERY_BUILDER_HPP -#include +#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 apply_returning{}; + std::function apply_primary_key{}; std::function bind_object{}; }; @@ -110,21 +111,24 @@ public: on_foreign_object(obj, attr); } template - void on_has_many(const char * /*id*/, CollectionType &objects, const char *join_column, const utils::foreign_attributes &attr, std::enable_if_t::value> * = nullptr) { + void on_has_many(const char * /*id*/, object::collection> &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 linker(ptr_, join_column); + has_many_linker 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 - void on_has_many(const char * /*id*/, CollectionType &/*con*/, const char *, const utils::foreign_attributes &/*attr*/, std::enable_if_t::value> * = nullptr) {} + static void on_has_many(const char * /*id*/, object::collection &/*con*/, const char *, const utils::foreign_attributes &/*attr*/) {} template 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)); } diff --git a/include/matador/utils/identifier.hpp b/include/matador/utils/identifier.hpp index b7f04f9..8df2594 100644 --- a/include/matador/utils/identifier.hpp +++ b/include/matador/utils/identifier.hpp @@ -198,16 +198,16 @@ private: template bool in_range(Source value) { if constexpr (std::is_signed_v == std::is_signed_v) { - return value >= static_cast(std::numeric_limits::min()) && - value <= static_cast(std::numeric_limits::max()); + return value >= static_cast((std::numeric_limits::min)()) && + value <= static_cast((std::numeric_limits::max)()); } else if constexpr (std::is_signed_v && !std::is_signed_v) { if (value < 0) { return false; } using UnsignedSource = std::make_unsigned_t; - return static_cast(value) <= std::numeric_limits::max(); + return static_cast(value) <= (std::numeric_limits::max)(); } else { - return value <= static_cast>(std::numeric_limits::max()); + return value <= static_cast>((std::numeric_limits::max)()); } } diff --git a/source/orm/orm/error_code.cpp b/source/orm/orm/error_code.cpp index 26a911b..b3e17ed 100644 --- a/source/orm/orm/error_code.cpp +++ b/source/orm/orm/error_code.cpp @@ -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: diff --git a/test/orm/query/InsertQueryBuilderTest.cpp b/test/orm/query/InsertQueryBuilderTest.cpp index fe14667..44aeafa 100644 --- a/test/orm/query/InsertQueryBuilderTest.cpp +++ b/test/orm/query/InsertQueryBuilderTest.cpp @@ -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 to_contexts_by_name(const schema& scm, const dialect& d) { + std::unordered_map 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()); @@ -32,7 +74,8 @@ TEST_CASE("insert query builder test", "[query][insert_query_builder]") { .and_then( [&scm] { return scm.attach("flights"); } ); REQUIRE(result); - insert_query_builder iqb(scm); + const auto contexts_by_type = to_contexts_by_name(scm, db.dialect()); + insert_query_builder iqb(scm, contexts_by_type); const auto a380 = make_object(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(step.query)); - const auto sql = std::get(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("authors"); } ); REQUIRE(result.is_ok()); - // auto s_king = make_object(1, "Steven", "King", "21.9.1947", 1956, false); - // - // s_king->books.push_back(make_object(2, "Carrie", object_ptr{}, 1974)); - // s_king->books.push_back(make_object(3, "The Shining", object_ptr{}, 1977)); - // s_king->books.push_back(make_object(4, "It", object_ptr{}, 1986)); - // s_king->books.push_back(make_object(5, "Misery", object_ptr{}, 1987)); - // s_king->books.push_back(make_object(6, "The Dark Tower: The Gunslinger", object_ptr{}, 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(1, "Steven", "King", "21.9.1947", 1956, false); + + s_king->books.push_back(make_object(2, "Carrie", object_ptr{}, 1974)); + s_king->books.push_back(make_object(3, "The Shining", object_ptr{}, 1977)); + s_king->books.push_back(make_object(4, "It", object_ptr{}, 1986)); + s_king->books.push_back(make_object(5, "Misery", object_ptr{}, 1987)); + s_king->books.push_back(make_object(6, "The Dark Tower: The Gunslinger", object_ptr{}, 1982)); + + const auto contexts_by_type = to_contexts_by_name(scm, db.dialect()); + insert_query_builder 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); } \ No newline at end of file