Compare commits

..

2 Commits

Author SHA1 Message Date
sascha 6423bd505b pk generator progress and sequence test progress 2026-03-18 15:54:08 +01:00
sascha 7996608f62 added missing nodiscard 2026-03-16 13:37:33 +01:00
26 changed files with 154 additions and 32 deletions

View File

@ -45,6 +45,8 @@ public:
private: private:
[[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ; [[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ;
utils::result<sql::execute_result, utils::error> execute(const std::string &stmt) const;
private: private:
PGconn *conn_{nullptr}; PGconn *conn_{nullptr};

View File

@ -129,6 +129,20 @@ std::string postgres_connection::generate_statement_name(const sql::query_contex
return name.str(); return name.str();
} }
utils::result<sql::execute_result, utils::error> 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<size_t>(PQcmdTuples(res));
PQclear(res);
return utils::ok(sql::execute_result{affected_rows});
}
utils::result<std::unique_ptr<sql::statement_impl>, utils::error> postgres_connection::prepare(const sql::query_context &context) { utils::result<std::unique_ptr<sql::statement_impl>, utils::error> postgres_connection::prepare(const sql::query_context &context) {
auto statement_name = generate_statement_name(context); auto statement_name = generate_statement_name(context);
@ -143,9 +157,7 @@ utils::result<std::unique_ptr<sql::statement_impl>, utils::error> postgres_conne
} }
utils::result<sql::execute_result, utils::error> postgres_connection::execute(const sql::query_context &context) { utils::result<sql::execute_result, utils::error> postgres_connection::execute(const sql::query_context &context) {
if (context.command == sql::sql_command::Insert) { return execute(context.sql);
// handle insert command with the primary key generator strategy
}
PGresult *res = PQexec(conn_, context.sql.c_str()); PGresult *res = PQexec(conn_, context.sql.c_str());
if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
@ -299,7 +311,17 @@ utils::result<bool, utils::error> postgres_connection::exists(const std::string
} }
utils::result<bool, utils::error> postgres_connection::sequence_exists(const std::string& schema_name, const std::string& sequence_name) { utils::result<bool, utils::error> 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 { std::string postgres_connection::to_escaped_string(const utils::blob_type_t &value) const {

View File

@ -15,6 +15,7 @@ class abstract_pk_generator {
public: public:
virtual ~abstract_pk_generator() = default; virtual ~abstract_pk_generator() = default;
virtual utils::result<int64_t, utils::error> next_id(const sql::executor& exec) = 0; virtual utils::result<int64_t, utils::error> next_id(const sql::executor& exec) = 0;
virtual utils::result<int64_t, utils::error> current_id(const sql::executor& exec) = 0;
[[nodiscard]] utils::generator_type type() const; [[nodiscard]] utils::generator_type type() const;

View File

@ -18,6 +18,7 @@ public:
explicit query_select_intermediate(const std::vector<table_column>& columns); explicit query_select_intermediate(const std::vector<table_column>& columns);
fetchable_query nextval(const std::string& sequence_name); fetchable_query nextval(const std::string& sequence_name);
fetchable_query currval(const std::string& sequence_name);
template<typename... Tables> template<typename... Tables>
query_from_intermediate from(const Tables&... tables) { query_from_intermediate from(const Tables&... tables) {

View File

@ -139,6 +139,19 @@ private:
std::string sequence_name_; 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 * Represents the SQL FROM part

View File

@ -8,6 +8,7 @@ class manual_pk_generator : public abstract_pk_generator {
public: public:
manual_pk_generator(); manual_pk_generator();
[[nodiscard]] utils::result<int64_t, utils::error> next_id(const sql::executor& exec) override; [[nodiscard]] utils::result<int64_t, utils::error> next_id(const sql::executor& exec) override;
[[nodiscard]] utils::result<int64_t, utils::error> current_id(const sql::executor& exec) override;
}; };
} }
#endif //MATADOR_MANUAL_PK_GENERATOR_HPP #endif //MATADOR_MANUAL_PK_GENERATOR_HPP

View File

@ -46,6 +46,7 @@ protected:
void visit(internal::query_select_part &part) override; void visit(internal::query_select_part &part) override;
void visit(internal::query_select_nextval_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_from_part &part) override;
void visit(internal::query_join_table_part &part) override; void visit(internal::query_join_table_part &part) override;
void visit(internal::query_join_query_part &part) override; void visit(internal::query_join_query_part &part) override;

View File

@ -18,6 +18,7 @@ class query_add_primary_key_constraint_part;
// Select // Select
class query_select_part; class query_select_part;
class query_select_nextval_part; class query_select_nextval_part;
class query_select_currval_part;
class query_from_part; class query_from_part;
class query_join_table_part; class query_join_table_part;
class query_join_query_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_part &part) = 0;
virtual void visit(internal::query_select_nextval_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_from_part &part) = 0;
virtual void visit(internal::query_join_table_part &part) = 0; virtual void visit(internal::query_join_table_part &part) = 0;
virtual void visit(internal::query_join_query_part &part) = 0; virtual void visit(internal::query_join_query_part &part) = 0;

View File

@ -243,14 +243,14 @@ public:
iterator find() { return basic_schema::find(typeid(Type)); } iterator find() { return basic_schema::find(typeid(Type)); }
template<typename Type> template<typename Type>
const_iterator find() const { return basic_schema::find(typeid(Type)); } [[nodiscard]] const_iterator find() const { return basic_schema::find(typeid(Type)); }
template < typename Type > template < typename Type >
[[nodiscard]] bool contains() const { [[nodiscard]] bool contains() const {
return basic_schema::contains(std::type_index(typeid(Type))); return basic_schema::contains(std::type_index(typeid(Type)));
} }
const object::repository &repo() const { return repo_; } [[nodiscard]] const object::repository &repo() const { return repo_; }
object::repository &repo() { return repo_; } object::repository &repo() { return repo_; }
void dump(std::ostream &os) const; void dump(std::ostream &os) const;

View File

@ -12,9 +12,11 @@ public:
explicit sequence_pk_generator(const std::string& sequence_name); explicit sequence_pk_generator(const std::string& sequence_name);
utils::result<int64_t, utils::error> next_id(const sql::executor& exec) override; utils::result<int64_t, utils::error> next_id(const sql::executor& exec) override;
utils::result<int64_t, utils::error> current_id(const sql::executor& exec) override;
private: private:
fetchable_query query_; fetchable_query next_id_query_;
fetchable_query current_id_query_;
}; };
} }
#endif //MATADOR_SEQUENCE_PK_GENERATOR_H #endif //MATADOR_SEQUENCE_PK_GENERATOR_H

View File

@ -2,15 +2,18 @@
#define MATADOR_TABLE_PK_GENERATOR_HPP #define MATADOR_TABLE_PK_GENERATOR_HPP
#include "matador/query/abstract_pk_generator.hpp" #include "matador/query/abstract_pk_generator.hpp"
#include "matador/query/intermediates/fetchable_query.hpp"
namespace matador::query { namespace matador::query {
class table_pk_generator : public abstract_pk_generator { class table_pk_generator : public abstract_pk_generator {
public: public:
table_pk_generator(const std::string& table_name, const std::string& sequence_name); table_pk_generator(const std::string& table_name, const std::string& sequence_name);
[[nodiscard]] utils::result<int64_t, utils::error> next_id(const sql::executor& exec) override; [[nodiscard]] utils::result<int64_t, utils::error> next_id(const sql::executor& exec) override;
[[nodiscard]] utils::result<int64_t, utils::error> current_id(const sql::executor& exec) override;
private: private:
std::string table_name; fetchable_query next_id_query_;
fetchable_query current_id_query_;
}; };
} }
#endif //MATADOR_TABLE_PK_GENERATOR_HPP #endif //MATADOR_TABLE_PK_GENERATOR_HPP

View File

@ -138,6 +138,7 @@ public:
[[nodiscard]] const std::string& commit() const; [[nodiscard]] const std::string& commit() const;
[[nodiscard]] const std::string& constraint() const; [[nodiscard]] const std::string& constraint() const;
[[nodiscard]] const std::string& create() const; [[nodiscard]] const std::string& create() const;
[[nodiscard]] const std::string& currval() const;
[[nodiscard]] const std::string& desc() const; [[nodiscard]] const std::string& desc() const;
[[nodiscard]] const std::string& distinct() const; [[nodiscard]] const std::string& distinct() const;
[[nodiscard]] const std::string& drop() const; [[nodiscard]] const std::string& drop() const;
@ -212,6 +213,7 @@ private:
{dialect_token::Commit, "COMMIT TRANSACTION"}, {dialect_token::Commit, "COMMIT TRANSACTION"},
{dialect_token::Constraint, "CONSTRAINT"}, {dialect_token::Constraint, "CONSTRAINT"},
{dialect_token::Create, "CREATE"}, {dialect_token::Create, "CREATE"},
{dialect_token::CurrVal, "CURRVAL"},
{dialect_token::Desc, "DESC"}, {dialect_token::Desc, "DESC"},
{dialect_token::Distinct, "DISTINCT"}, {dialect_token::Distinct, "DISTINCT"},
{dialect_token::Drop, "DROP"}, {dialect_token::Drop, "DROP"},

View File

@ -21,6 +21,7 @@ enum class dialect_token : uint8_t {
Commit, Commit,
Constraint, Constraint,
Create, Create,
CurrVal,
Database, Database,
Desc, Desc,
Distinct, Distinct,

View File

@ -18,6 +18,7 @@ enum class error_code : uint8_t {
DESCRIBE_FAILED, DESCRIBE_FAILED,
TABLE_EXISTS_FAILED, TABLE_EXISTS_FAILED,
RETRIEVE_DATA_FAILED, RETRIEVE_DATA_FAILED,
SEQUENCE_EXISTS_FAILED,
RESET_FAILED, RESET_FAILED,
OPEN_ERROR, OPEN_ERROR,
CLOSE_ERROR, CLOSE_ERROR,

View File

@ -17,6 +17,12 @@ fetchable_query query_select_intermediate::nextval(const std::string& sequence_n
return {context_}; return {context_};
} }
fetchable_query query_select_intermediate::currval(const std::string& sequence_name) {
context_->parts.pop_back();
context_->parts.push_back(std::make_unique<internal::query_select_currval_part>(sequence_name));
return {context_};
}
query_from_intermediate query_select_intermediate::from(const std::vector<table>& tables) { query_from_intermediate query_select_intermediate::from(const std::vector<table>& tables) {
context_->parts.push_back(std::make_unique<internal::query_from_part>(tables)); context_->parts.push_back(std::make_unique<internal::query_from_part>(tables));
for (const auto& tab : tables) { for (const auto& tab : tables) {

View File

@ -147,6 +147,19 @@ void query_select_nextval_part::accept(query_part_visitor& visitor) {
visitor.visit(*this); 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<table> tables) query_from_part::query_from_part(std::vector<table> tables)
: query_part(sql::dialect_token::From) : query_part(sql::dialect_token::From)
, tables_(std::move(tables)) { , tables_(std::move(tables)) {

View File

@ -10,4 +10,8 @@ manual_pk_generator::manual_pk_generator()
utils::result<int64_t, utils::error> manual_pk_generator::next_id(const sql::executor &/*exec*/) { utils::result<int64_t, utils::error> manual_pk_generator::next_id(const sql::executor &/*exec*/) {
return utils::failure(utils::error(sql::error_code::FAILURE, "Manual PK generator not implemented")); return utils::failure(utils::error(sql::error_code::FAILURE, "Manual PK generator not implemented"));
} }
utils::result<int64_t, utils::error> manual_pk_generator::current_id(const sql::executor& exec) {
return utils::failure(utils::error(sql::error_code::FAILURE, "Manual PK generator not implemented"));
}
} }

View File

@ -104,6 +104,11 @@ void query_builder::visit(internal::query_select_nextval_part& part) {
prepare_prototype(query_.prototype, part.sequence_name()); 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) { void query_builder::visit(internal::query_from_part &part) {
query_.table_name = part.tables().front().name(); query_.table_name = part.tables().front().name();
query_.sql += " " + dialect_->from() + " "; query_.sql += " " + dialect_->from() + " ";

View File

@ -6,11 +6,21 @@
namespace matador::query { namespace matador::query {
sequence_pk_generator::sequence_pk_generator(const std::string& sequence_name) sequence_pk_generator::sequence_pk_generator(const std::string& sequence_name)
: abstract_pk_generator(utils::generator_type::Sequence) : 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<int64_t, utils::error> sequence_pk_generator::next_id(const sql::executor& exec) { utils::result<int64_t, utils::error> sequence_pk_generator::next_id(const sql::executor& exec) {
return query_.fetch_value<int64_t>(exec).and_then([](const std::optional<int64_t> id) -> utils::result<int64_t, utils::error> { return next_id_query_.fetch_value<int64_t>(exec).and_then([](const std::optional<int64_t> id) -> utils::result<int64_t, utils::error> {
if (!id) {
return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value"));
}
return utils::ok(*id);
});
}
utils::result<int64_t, utils::error> sequence_pk_generator::current_id(const sql::executor& exec) {
return current_id_query_.fetch_value<int64_t>(exec).and_then([](const std::optional<int64_t> id) -> utils::result<int64_t, utils::error> {
if (!id) { if (!id) {
return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value")); return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value"));
} }

View File

@ -4,23 +4,35 @@
#include "matador/query/error_code.hpp" #include "matador/query/error_code.hpp"
#include "matador/query/expression/expression_operators.hpp" #include "matador/query/expression/expression_operators.hpp"
#include "matador/sql/error_code.hpp"
namespace matador::query { namespace matador::query {
table_pk_generator::table_pk_generator(const std::string& table_name, const std::string &sequence_name) table_pk_generator::table_pk_generator(const std::string& table_name, const std::string &sequence_name)
: abstract_pk_generator(utils::generator_type::Table) { : abstract_pk_generator(utils::generator_type::Table)
, next_id_query_(query::update(table_name)
query::update(table_name)
.set("next_id"_col, "next_id"_col + 1) .set("next_id"_col, "next_id"_col + 1)
.where("name"_col == sequence_name) .where("name"_col == sequence_name)
.returning(("next_id"_col - 1).as("id")); .returning(("next_id"_col - 1).as("id")))
/* , current_id_query_(query::select({"next_id"_col})
*UPDATE id_table .from(table_name)
SET next_id = next_id + 1 .where("name"_col == sequence_name)
WHERE name = 'users' ) {}
RETURNING next_id - 1 AS id;
*/
}
utils::result<int64_t, utils::error> table_pk_generator::next_id(const sql::executor &exec) { utils::result<int64_t, utils::error> table_pk_generator::next_id(const sql::executor &exec) {
return utils::failure(utils::error{error_code::Failure}); return next_id_query_.fetch_value<int64_t>(exec).and_then([](const std::optional<int64_t> id) -> utils::result<int64_t, utils::error> {
if (!id) {
return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value"));
}
return utils::ok(*id);
});
}
utils::result<int64_t, utils::error> table_pk_generator::current_id(const sql::executor& exec) {
return current_id_query_.fetch_value<int64_t>(exec).and_then([](const std::optional<int64_t> id) -> utils::result<int64_t, utils::error> {
if (!id) {
return utils::failure(utils::error(sql::error_code::RETRIEVE_DATA_FAILED, "Sequence returned no value"));
}
return utils::ok(*id);
});
} }
} }

View File

@ -243,6 +243,10 @@ const std::string& dialect::nextval() const {
return token_at(dialect_token::NextVal); return token_at(dialect_token::NextVal);
} }
const std::string& dialect::currval() const {
return token_at(dialect_token::CurrVal);
}
const std::string &dialect::not_() const { const std::string &dialect::not_() const {
return token_at(dialect_token::Not); return token_at(dialect_token::Not);
} }

View File

@ -30,6 +30,8 @@ std::string sql_category_impl::message(const int ev) const {
return "Table exists failed"; return "Table exists failed";
case error_code::RETRIEVE_DATA_FAILED: case error_code::RETRIEVE_DATA_FAILED:
return "Retrieve data failed"; return "Retrieve data failed";
case error_code::SEQUENCE_EXISTS_FAILED:
return "Sequence exists failed";
case error_code::RESET_FAILED: case error_code::RESET_FAILED:
return "Reset failed"; return "Reset failed";
case error_code::OPEN_ERROR: case error_code::OPEN_ERROR:

View File

@ -10,8 +10,7 @@
namespace matador::test { namespace matador::test {
SequenceFixture::SequenceFixture() SequenceFixture::SequenceFixture()
: db(connection::dns) : db(connection::dns) {
, repo(db.dialect().default_schema_name()) {
REQUIRE(db.open()); REQUIRE(db.open());
} }
@ -20,7 +19,6 @@ SequenceFixture::~SequenceFixture() {
drop_sequence_if_exists(sequences_to_drop.top()); drop_sequence_if_exists(sequences_to_drop.top());
sequences_to_drop.pop(); sequences_to_drop.pop();
} }
REQUIRE(repo.drop(db));
REQUIRE(db.close()); 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 { 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);
} }
} }

View File

@ -1,7 +1,6 @@
#ifndef MATADOR_SEQUENCE_FIXTURE_HPP #ifndef MATADOR_SEQUENCE_FIXTURE_HPP
#define MATADOR_SEQUENCE_FIXTURE_HPP #define MATADOR_SEQUENCE_FIXTURE_HPP
#include "matador/query/schema.hpp" #include "matador/query/schema.hpp"
#include "matador/sql/connection.hpp" #include "matador/sql/connection.hpp"
@ -20,7 +19,6 @@ public:
protected: protected:
sql::connection db; sql::connection db;
std::stack <std::string> sequences_to_drop; std::stack <std::string> sequences_to_drop;
query::schema repo;
private: private:
void drop_sequence_if_exists(const std::string &sequence_name) const; void drop_sequence_if_exists(const std::string &sequence_name) const;

View File

@ -7,7 +7,7 @@
using namespace matador::query; using namespace matador::query;
using namespace matador::test; 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() auto result = query::create()
.sequence("person_seq") .sequence("person_seq")
.execute(db); .execute(db);
@ -22,6 +22,13 @@ TEST_CASE_METHOD(SequenceFixture, "", "[sequence]") {
REQUIRE(next_id.is_ok()); REQUIRE(next_id.is_ok());
REQUIRE(*next_id == 1); REQUIRE(*next_id == 1);
auto curr_id = query::select()
.currval("person_seq")
.fetch_value<uint32_t>(db);
REQUIRE(curr_id.is_ok());
REQUIRE(*curr_id == 1);
result = query::drop() result = query::drop()
.sequence("person_seq") .sequence("person_seq")
.execute(db); .execute(db);