diff --git a/backends/postgres/include/postgres_connection.hpp b/backends/postgres/include/postgres_connection.hpp index 52d9a6c..32fac42 100644 --- a/backends/postgres/include/postgres_connection.hpp +++ b/backends/postgres/include/postgres_connection.hpp @@ -45,6 +45,8 @@ public: private: [[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ; + utils::result execute(const std::string &stmt) const; + private: PGconn *conn_{nullptr}; diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index 6427374..4e004b9 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -129,6 +129,20 @@ std::string postgres_connection::generate_statement_name(const sql::query_contex return name.str(); } +utils::result postgres_connection::execute(const std::string& stmt) const { + PGresult *res = PQexec(conn_, stmt.c_str()); + + if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + return utils::failure(make_error(sql::error_code::FAILURE, res, conn_, "Failed to execute", stmt)); + } + + const size_t affected_rows = utils::to(PQcmdTuples(res)); + + PQclear(res); + + return utils::ok(sql::execute_result{affected_rows}); +} + utils::result, utils::error> postgres_connection::prepare(const sql::query_context &context) { auto statement_name = generate_statement_name(context); @@ -143,9 +157,7 @@ utils::result, utils::error> postgres_conne } utils::result postgres_connection::execute(const sql::query_context &context) { - if (context.command == sql::sql_command::Insert) { - // handle insert command with the primary key generator strategy - } + return execute(context.sql); PGresult *res = PQexec(conn_, context.sql.c_str()); if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { @@ -299,7 +311,17 @@ utils::result postgres_connection::exists(const std::string } utils::result postgres_connection::sequence_exists(const std::string& schema_name, const std::string& sequence_name) { - return utils::ok(false); + const std::string sql{"SELECT to_regclass('" + schema_name + "." + sequence_name + "')"}; + PGresult* res = PQexec(conn_, sql.c_str()); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + return utils::failure(make_error(sql::error_code::SEQUENCE_EXISTS_FAILED, res, conn_, "Failed check if sequence exists", sql)); + } + + const bool exists = PQgetisnull(res, 0, 0) ? false : true; + PQclear(res); + + return utils::ok(exists); } std::string postgres_connection::to_escaped_string(const utils::blob_type_t &value) const { diff --git a/backends/postgres/test/CMakeLists.txt b/backends/postgres/test/CMakeLists.txt index 63e53d6..ade1ec1 100644 --- a/backends/postgres/test/CMakeLists.txt +++ b/backends/postgres/test/CMakeLists.txt @@ -34,8 +34,8 @@ set(TEST_SOURCES ../../../test/utils/record_printer.hpp ../../../test/utils/record_printer.cpp ../../../test/models/model_metas.hpp - ../../../test/backends/SequenceFixture.hpp - ../../../test/backends/SequenceFixture.cpp + ../../../test/backends/SequenceFixture.hpp + ../../../test/backends/SequenceFixture.cpp ../../../test/backends/SequenceTest.cpp ) diff --git a/include/matador/query/abstract_pk_generator.hpp b/include/matador/query/abstract_pk_generator.hpp index 6c6943b..1d64846 100644 --- a/include/matador/query/abstract_pk_generator.hpp +++ b/include/matador/query/abstract_pk_generator.hpp @@ -15,6 +15,7 @@ class abstract_pk_generator { public: virtual ~abstract_pk_generator() = default; virtual utils::result next_id(const sql::executor& exec) = 0; + virtual utils::result current_id(const sql::executor& exec) = 0; [[nodiscard]] utils::generator_type type() const; diff --git a/include/matador/query/intermediates/query_select_intermediate.hpp b/include/matador/query/intermediates/query_select_intermediate.hpp index 0fd739d..3fc730a 100644 --- a/include/matador/query/intermediates/query_select_intermediate.hpp +++ b/include/matador/query/intermediates/query_select_intermediate.hpp @@ -18,6 +18,7 @@ public: explicit query_select_intermediate(const std::vector& columns); fetchable_query nextval(const std::string& sequence_name); + fetchable_query currval(const std::string& sequence_name); template query_from_intermediate from(const Tables&... tables) { diff --git a/include/matador/query/internal/query_parts.hpp b/include/matador/query/internal/query_parts.hpp index b105c87..c669fad 100644 --- a/include/matador/query/internal/query_parts.hpp +++ b/include/matador/query/internal/query_parts.hpp @@ -139,6 +139,19 @@ private: std::string sequence_name_; }; +class query_select_currval_part final : public query_part { +public: + explicit query_select_currval_part(std::string sequence_name); + + [[nodiscard]] const std::string& sequence_name() const; + +private: + void accept(query_part_visitor &visitor) override; + +private: + std::string sequence_name_; +}; + /** * Represents the SQL FROM part diff --git a/include/matador/query/manual_pk_generator.hpp b/include/matador/query/manual_pk_generator.hpp index df36240..360482c 100644 --- a/include/matador/query/manual_pk_generator.hpp +++ b/include/matador/query/manual_pk_generator.hpp @@ -8,6 +8,7 @@ class manual_pk_generator : public abstract_pk_generator { public: manual_pk_generator(); [[nodiscard]] utils::result next_id(const sql::executor& exec) override; + [[nodiscard]] utils::result current_id(const sql::executor& exec) override; }; } #endif //MATADOR_MANUAL_PK_GENERATOR_HPP \ No newline at end of file diff --git a/include/matador/query/query_builder.hpp b/include/matador/query/query_builder.hpp index 6e63d0a..56e69aa 100644 --- a/include/matador/query/query_builder.hpp +++ b/include/matador/query/query_builder.hpp @@ -46,6 +46,7 @@ protected: void visit(internal::query_select_part &part) override; void visit(internal::query_select_nextval_part &part) override; + void visit(internal::query_select_currval_part &part) override; void visit(internal::query_from_part &part) override; void visit(internal::query_join_table_part &part) override; void visit(internal::query_join_query_part &part) override; diff --git a/include/matador/query/query_part_visitor.hpp b/include/matador/query/query_part_visitor.hpp index bb5738b..48b418b 100644 --- a/include/matador/query/query_part_visitor.hpp +++ b/include/matador/query/query_part_visitor.hpp @@ -18,6 +18,7 @@ class query_add_primary_key_constraint_part; // Select class query_select_part; class query_select_nextval_part; +class query_select_currval_part; class query_from_part; class query_join_table_part; class query_join_query_part; @@ -76,6 +77,7 @@ public: virtual void visit(internal::query_select_part &part) = 0; virtual void visit(internal::query_select_nextval_part &part) = 0; + virtual void visit(internal::query_select_currval_part &part) = 0; virtual void visit(internal::query_from_part &part) = 0; virtual void visit(internal::query_join_table_part &part) = 0; virtual void visit(internal::query_join_query_part &part) = 0; diff --git a/include/matador/query/sequence_pk_generator.hpp b/include/matador/query/sequence_pk_generator.hpp index 1cea571..dbd5b5d 100644 --- a/include/matador/query/sequence_pk_generator.hpp +++ b/include/matador/query/sequence_pk_generator.hpp @@ -12,9 +12,11 @@ public: explicit sequence_pk_generator(const std::string& sequence_name); utils::result next_id(const sql::executor& exec) override; + utils::result current_id(const sql::executor& exec) override; private: - fetchable_query query_; + fetchable_query next_id_query_; + fetchable_query current_id_query_; }; } #endif //MATADOR_SEQUENCE_PK_GENERATOR_H \ No newline at end of file diff --git a/include/matador/query/table_pk_generator.hpp b/include/matador/query/table_pk_generator.hpp index 2a0d1be..7579b7c 100644 --- a/include/matador/query/table_pk_generator.hpp +++ b/include/matador/query/table_pk_generator.hpp @@ -2,15 +2,18 @@ #define MATADOR_TABLE_PK_GENERATOR_HPP #include "matador/query/abstract_pk_generator.hpp" +#include "matador/query/intermediates/fetchable_query.hpp" namespace matador::query { class table_pk_generator : public abstract_pk_generator { public: table_pk_generator(const std::string& table_name, const std::string& sequence_name); [[nodiscard]] utils::result next_id(const sql::executor& exec) override; + [[nodiscard]] utils::result current_id(const sql::executor& exec) override; private: - std::string table_name; + fetchable_query next_id_query_; + fetchable_query current_id_query_; }; } #endif //MATADOR_TABLE_PK_GENERATOR_HPP \ No newline at end of file diff --git a/include/matador/sql/dialect.hpp b/include/matador/sql/dialect.hpp index 7ac0c11..c3aeb5a 100644 --- a/include/matador/sql/dialect.hpp +++ b/include/matador/sql/dialect.hpp @@ -138,6 +138,7 @@ public: [[nodiscard]] const std::string& commit() const; [[nodiscard]] const std::string& constraint() const; [[nodiscard]] const std::string& create() const; + [[nodiscard]] const std::string& currval() const; [[nodiscard]] const std::string& desc() const; [[nodiscard]] const std::string& distinct() const; [[nodiscard]] const std::string& drop() const; @@ -212,6 +213,7 @@ private: {dialect_token::Commit, "COMMIT TRANSACTION"}, {dialect_token::Constraint, "CONSTRAINT"}, {dialect_token::Create, "CREATE"}, + {dialect_token::CurrVal, "CURRVAL"}, {dialect_token::Desc, "DESC"}, {dialect_token::Distinct, "DISTINCT"}, {dialect_token::Drop, "DROP"}, diff --git a/include/matador/sql/dialect_token.hpp b/include/matador/sql/dialect_token.hpp index 053fd00..d3e9030 100644 --- a/include/matador/sql/dialect_token.hpp +++ b/include/matador/sql/dialect_token.hpp @@ -21,6 +21,7 @@ enum class dialect_token : uint8_t { Commit, Constraint, Create, + CurrVal, Database, Desc, Distinct, diff --git a/include/matador/sql/error_code.hpp b/include/matador/sql/error_code.hpp index f493681..6705e31 100644 --- a/include/matador/sql/error_code.hpp +++ b/include/matador/sql/error_code.hpp @@ -18,6 +18,7 @@ enum class error_code : uint8_t { DESCRIBE_FAILED, TABLE_EXISTS_FAILED, RETRIEVE_DATA_FAILED, + SEQUENCE_EXISTS_FAILED, RESET_FAILED, OPEN_ERROR, CLOSE_ERROR, diff --git a/source/orm/query/intermediates/query_select_intermediate.cpp b/source/orm/query/intermediates/query_select_intermediate.cpp index d9f7ca7..729fb1f 100644 --- a/source/orm/query/intermediates/query_select_intermediate.cpp +++ b/source/orm/query/intermediates/query_select_intermediate.cpp @@ -17,6 +17,12 @@ fetchable_query query_select_intermediate::nextval(const std::string& sequence_n return {context_}; } +fetchable_query query_select_intermediate::currval(const std::string& sequence_name) { + context_->parts.pop_back(); + context_->parts.push_back(std::make_unique(sequence_name)); + return {context_}; +} + query_from_intermediate query_select_intermediate::from(const std::vector& tables) { context_->parts.push_back(std::make_unique(tables)); for (const auto& tab : tables) { diff --git a/source/orm/query/internal/query_parts.cpp b/source/orm/query/internal/query_parts.cpp index cc11c8f..3567f82 100644 --- a/source/orm/query/internal/query_parts.cpp +++ b/source/orm/query/internal/query_parts.cpp @@ -147,6 +147,19 @@ void query_select_nextval_part::accept(query_part_visitor& visitor) { visitor.visit(*this); } +query_select_currval_part::query_select_currval_part(std::string sequence_name) +: query_part(sql::dialect_token::NextVal) +, sequence_name_(std::move(sequence_name)){ +} + +const std::string& query_select_currval_part::sequence_name() const { + return sequence_name_; +} + +void query_select_currval_part::accept(query_part_visitor& visitor) { + visitor.visit(*this); +} + query_from_part::query_from_part(std::vector
tables) : query_part(sql::dialect_token::From) , tables_(std::move(tables)) { diff --git a/source/orm/query/manual_pk_generator.cpp b/source/orm/query/manual_pk_generator.cpp index 520b049..936a9f6 100644 --- a/source/orm/query/manual_pk_generator.cpp +++ b/source/orm/query/manual_pk_generator.cpp @@ -10,4 +10,8 @@ manual_pk_generator::manual_pk_generator() utils::result manual_pk_generator::next_id(const sql::executor &/*exec*/) { return utils::failure(utils::error(sql::error_code::FAILURE, "Manual PK generator not implemented")); } + +utils::result manual_pk_generator::current_id(const sql::executor& exec) { + return utils::failure(utils::error(sql::error_code::FAILURE, "Manual PK generator not implemented")); +} } diff --git a/source/orm/query/query_builder.cpp b/source/orm/query/query_builder.cpp index 605b4ca..80f3c9c 100644 --- a/source/orm/query/query_builder.cpp +++ b/source/orm/query/query_builder.cpp @@ -104,6 +104,11 @@ void query_builder::visit(internal::query_select_nextval_part& part) { prepare_prototype(query_.prototype, part.sequence_name()); } +void query_builder::visit(internal::query_select_currval_part& part) { + query_.sql += dialect_->select() + " " + dialect_->currval() + "('" + part.sequence_name() + "')"; + prepare_prototype(query_.prototype, part.sequence_name()); +} + void query_builder::visit(internal::query_from_part &part) { query_.table_name = part.tables().front().name(); query_.sql += " " + dialect_->from() + " "; diff --git a/source/orm/query/sequence_pk_generator.cpp b/source/orm/query/sequence_pk_generator.cpp index 949e356..66261eb 100644 --- a/source/orm/query/sequence_pk_generator.cpp +++ b/source/orm/query/sequence_pk_generator.cpp @@ -6,11 +6,21 @@ namespace matador::query { sequence_pk_generator::sequence_pk_generator(const std::string& sequence_name) : abstract_pk_generator(utils::generator_type::Sequence) -, query_(query::select().nextval(sequence_name)) { +, next_id_query_(query::select().nextval(sequence_name)) +, current_id_query_(query::select().currval(sequence_name)) { } utils::result sequence_pk_generator::next_id(const sql::executor& exec) { - return query_.fetch_value(exec).and_then([](const std::optional id) -> utils::result { + return next_id_query_.fetch_value(exec).and_then([](const std::optional id) -> utils::result { + if (!id) { + return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value")); + } + return utils::ok(*id); + }); +} + +utils::result sequence_pk_generator::current_id(const sql::executor& exec) { + return current_id_query_.fetch_value(exec).and_then([](const std::optional id) -> utils::result { if (!id) { return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value")); } diff --git a/source/orm/query/table_pk_generator.cpp b/source/orm/query/table_pk_generator.cpp index 24864a7..b091510 100644 --- a/source/orm/query/table_pk_generator.cpp +++ b/source/orm/query/table_pk_generator.cpp @@ -4,23 +4,35 @@ #include "matador/query/error_code.hpp" #include "matador/query/expression/expression_operators.hpp" +#include "matador/sql/error_code.hpp" + namespace matador::query { table_pk_generator::table_pk_generator(const std::string& table_name, const std::string &sequence_name) -: abstract_pk_generator(utils::generator_type::Table) { - - query::update(table_name) - .set("next_id"_col, "next_id"_col + 1) - .where("name"_col == sequence_name) - .returning(("next_id"_col - 1).as("id")); - /* - *UPDATE id_table - SET next_id = next_id + 1 - WHERE name = 'users' - RETURNING next_id - 1 AS id; - */ -} +: abstract_pk_generator(utils::generator_type::Table) +, next_id_query_(query::update(table_name) + .set("next_id"_col, "next_id"_col + 1) + .where("name"_col == sequence_name) + .returning(("next_id"_col - 1).as("id"))) +, current_id_query_(query::select({"next_id"_col}) + .from(table_name) + .where("name"_col == sequence_name) +) {} utils::result table_pk_generator::next_id(const sql::executor &exec) { - return utils::failure(utils::error{error_code::Failure}); + return next_id_query_.fetch_value(exec).and_then([](const std::optional id) -> utils::result { + if (!id) { + return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value")); + } + return utils::ok(*id); + }); +} + +utils::result table_pk_generator::current_id(const sql::executor& exec) { + return current_id_query_.fetch_value(exec).and_then([](const std::optional id) -> utils::result { + if (!id) { + return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value")); + } + return utils::ok(*id); + }); } } diff --git a/source/orm/sql/dialect.cpp b/source/orm/sql/dialect.cpp index e5d42f3..84ab278 100644 --- a/source/orm/sql/dialect.cpp +++ b/source/orm/sql/dialect.cpp @@ -243,6 +243,10 @@ const std::string& dialect::nextval() const { return token_at(dialect_token::NextVal); } +const std::string& dialect::currval() const { + return token_at(dialect_token::CurrVal); +} + const std::string &dialect::not_() const { return token_at(dialect_token::Not); } diff --git a/source/orm/sql/error_code.cpp b/source/orm/sql/error_code.cpp index 2884256..8b4d96b 100644 --- a/source/orm/sql/error_code.cpp +++ b/source/orm/sql/error_code.cpp @@ -30,6 +30,8 @@ std::string sql_category_impl::message(const int ev) const { return "Table exists failed"; case error_code::RETRIEVE_DATA_FAILED: return "Retrieve data failed"; + case error_code::SEQUENCE_EXISTS_FAILED: + return "Sequence exists failed"; case error_code::RESET_FAILED: return "Reset failed"; case error_code::OPEN_ERROR: diff --git a/test/backends/SequenceFixture.cpp b/test/backends/SequenceFixture.cpp index 8bf3ba3..f9570a7 100644 --- a/test/backends/SequenceFixture.cpp +++ b/test/backends/SequenceFixture.cpp @@ -10,8 +10,7 @@ namespace matador::test { SequenceFixture::SequenceFixture() -: db(connection::dns) -, repo(db.dialect().default_schema_name()) { +: db(connection::dns) { REQUIRE(db.open()); } @@ -20,7 +19,6 @@ SequenceFixture::~SequenceFixture() { drop_sequence_if_exists(sequences_to_drop.top()); sequences_to_drop.pop(); } - REQUIRE(repo.drop(db)); REQUIRE(db.close()); } @@ -37,5 +35,16 @@ void SequenceFixture::check_sequence_not_exists(const std::string& sequence_name } void SequenceFixture::drop_sequence_if_exists(const std::string& sequence_name) const { + const auto result = db.sequence_exists(sequence_name).and_then([&sequence_name, this](const bool exists) { + if (exists) { + auto res = query::query::drop() + .sequence(sequence_name) + .execute(db); + REQUIRE(res); + this->check_sequence_not_exists(sequence_name); + } + return utils::ok(true); + }); + REQUIRE(result); } } diff --git a/test/backends/SequenceFixture.hpp b/test/backends/SequenceFixture.hpp index 4a698d0..3be0fea 100644 --- a/test/backends/SequenceFixture.hpp +++ b/test/backends/SequenceFixture.hpp @@ -1,7 +1,6 @@ #ifndef MATADOR_SEQUENCE_FIXTURE_HPP #define MATADOR_SEQUENCE_FIXTURE_HPP - #include "matador/query/schema.hpp" #include "matador/sql/connection.hpp" @@ -20,7 +19,6 @@ public: protected: sql::connection db; std::stack sequences_to_drop; - query::schema repo; private: void drop_sequence_if_exists(const std::string &sequence_name) const; diff --git a/test/backends/SequenceTest.cpp b/test/backends/SequenceTest.cpp index 4da08b7..e455d72 100644 --- a/test/backends/SequenceTest.cpp +++ b/test/backends/SequenceTest.cpp @@ -7,7 +7,7 @@ using namespace matador::query; using namespace matador::test; -TEST_CASE_METHOD(SequenceFixture, "", "[sequence]") { +TEST_CASE_METHOD(SequenceFixture, "test create and drop sequence", "[sequence][create][drop]") { auto result = query::create() .sequence("person_seq") .execute(db); @@ -22,6 +22,13 @@ TEST_CASE_METHOD(SequenceFixture, "", "[sequence]") { REQUIRE(next_id.is_ok()); REQUIRE(*next_id == 1); + auto curr_id = query::select() + .currval("person_seq") + .fetch_value(db); + + REQUIRE(curr_id.is_ok()); + REQUIRE(*curr_id == 1); + result = query::drop() .sequence("person_seq") .execute(db);