Compare commits

..

3 Commits

Author SHA1 Message Date
sascha 905ee019d7 startet pk_accessor class 2026-03-19 16:46:44 +01:00
sascha 907bcf90a6 some code formatting 2026-03-19 11:54:50 +01:00
sascha 6b2db17f58 added test for table sequence 2026-03-19 11:52:34 +01:00
14 changed files with 356 additions and 135 deletions

View File

@ -37,6 +37,9 @@ set(TEST_SOURCES
../../../test/backends/SequenceFixture.hpp ../../../test/backends/SequenceFixture.hpp
../../../test/backends/SequenceFixture.cpp ../../../test/backends/SequenceFixture.cpp
../../../test/backends/SequenceTest.cpp ../../../test/backends/SequenceTest.cpp
../../../test/backends/TableSequenceFixture.hpp
../../../test/backends/TableSequenceFixture.cpp
../../../test/backends/TableSequenceTest.cpp
) )
set(LIBRARY_TEST_TARGET PostgresTests) set(LIBRARY_TEST_TARGET PostgresTests)

View File

@ -52,6 +52,7 @@ struct LoginHistory : core::Model {
template<typename Operator> template<typename Operator>
void process( Operator& op ) { void process( Operator& op ) {
namespace field = matador::access; namespace field = matador::access;
field::process_base<Model>( op, *this );
field::process( op, *matador::base_class<Model>( this ) ); field::process( op, *matador::base_class<Model>( this ) );
field::belongs_to( op, "client", client, matador::utils::CascadeAllFetchLazy ); field::belongs_to( op, "client", client, matador::utils::CascadeAllFetchLazy );
field::belongs_to( op, "scenario", scenario, matador::utils::CascadeAllFetchLazy ); field::belongs_to( op, "scenario", scenario, matador::utils::CascadeAllFetchLazy );

View File

@ -13,6 +13,7 @@
namespace matador::query { namespace matador::query {
class table; class table;
class column_expression;
// ReSharper disable CppNonExplicitConvertingConstructor // ReSharper disable CppNonExplicitConvertingConstructor
class table_column { class table_column {
@ -35,6 +36,7 @@ public:
table_column(const std::shared_ptr<abstract_column_expression>& expression) noexcept; table_column(const std::shared_ptr<abstract_column_expression>& expression) noexcept;
table_column(column_expression&& expression) noexcept;
table_column& operator=(const table_column& other); table_column& operator=(const table_column& other);
table_column(const table_column& other) = default; table_column(const table_column& other) = default;
table_column(table_column&& other) noexcept = default; table_column(table_column&& other) noexcept = default;

View File

@ -25,6 +25,20 @@ void process(Operator &op, const Type &object) {
process(op, const_cast<Type &>(object)); process(op, const_cast<Type &>(object));
} }
template<class Base, class Derived, class Operator>
void process_base(Operator &op, const Derived &object) {
static_assert(!std::is_same_v<Base, Derived>, "class Base must not be of same type as class Derived");
static_assert(std::is_base_of_v<Base, Derived>, "class Base must be base of class Derived");
process(op, static_cast<const Base&>(object));
}
template<class Base, class Derived, class Operator>
void process_base(Operator &op, Derived &object) {
static_assert(!std::is_same_v<Base, Derived>, "class Base must not be of same type as class Derived");
static_assert(std::is_base_of_v<Base, Derived>, "class Base must be base of class Derived");
process(op, static_cast<Base&>(object));
}
template< class Operator, class Type > template< class Operator, class Type >
void primary_key(Operator &op, const char *id, Type &value, const utils::primary_key_attribute &attr) { void primary_key(Operator &op, const char *id, Type &value, const utils::primary_key_attribute &attr) {
op.on_primary_key(id, value, attr); op.on_primary_key(id, value, attr);

View File

@ -8,33 +8,31 @@ namespace matador {
/** /**
* @brief Safely casts a given derived class to its base class * @brief Safely casts a given derived class to its base class
* *
* @tparam B The base class type * @tparam Base The base class type
* @tparam D The class type of the derived class * @tparam Derived The class type of the derived class
* @param derived The derived object * @param derived The derived object
* @return The cast object * @return The cast object
*/ */
template < class B, class D> template < class Base, class Derived>
const B* base_class(const D *derived) const Base* base_class(const Derived *derived) {
{ static_assert(!std::is_same_v<Base, Derived>, "class Base must not be of same type as class Derived");
static_assert(!std::is_same_v<B, D>, "class B must not be of same type as class D"); static_assert(std::is_base_of_v<Base, Derived>, "class Base must be base of class Derived");
static_assert(std::is_base_of_v<B, D>, "class B must be base of class D"); return static_cast<const Base*>(derived);
return static_cast<const B*>(derived);
} }
/** /**
* @brief Safely casts a given derived class to its base class * @brief Safely casts a given derived class to its base class
* *
* @tparam B The base class type * @tparam Base The base class type
* @tparam D The class type of the derived class * @tparam Derived The class type of the derived class
* @param derived The derived object * @param derived The derived object
* @return The cast object * @return The cast object
*/ */
template < class B, class D> template < class Base, class Derived>
B* base_class(D *derived) Base* base_class(Derived *derived) {
{ static_assert(!std::is_same_v<Base, Derived>, "class Base must not be of same type as class Derived");
static_assert(!std::is_same_v<B, D>, "class B must not be of same type as class D"); static_assert(std::is_base_of_v<Base, Derived>, "class Base must be base of class Derived");
static_assert(std::is_base_of_v<B, D>, "class B must be base of class D"); return static_cast<Base*>(derived);
return static_cast<B*>(derived);
} }
} }

View File

@ -0,0 +1,105 @@
#ifndef MATADOR_PRIMARY_KEY_ACCESSOR_HPP
#define MATADOR_PRIMARY_KEY_ACCESSOR_HPP
#include "matador/utils/field_attributes.hpp"
#include "matador/utils/primary_key_attribute.hpp"
#include <cstdint>
#include <string>
namespace matador::utils {
class foreign_attributes;
namespace detail {
template < typename PrimaryKeyType >
class primary_key_setter {
public:
explicit primary_key_setter(const std::string &name, const PrimaryKeyType& value)
: name_(name)
, value_(value){}
void on_primary_key(const char *id, PrimaryKeyType &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
if (id != nullptr && name_ == id) {
pk = static_cast<PrimaryKeyType>(value_);
}
}
template<typename ValueType>
static void on_primary_key(const char * /*id*/, ValueType & /*pk*/, const utils::primary_key_attribute & = utils::default_pk_attributes) {}
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
template<typename T>
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
template<class P>
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
template<class P>
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
private:
const std::string &name_;
PrimaryKeyType value_{};
};
struct pk_unset_checker {
const std::string &name;
bool unset{true};
template<class V>
void on_primary_key(const char *id, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
if (id != nullptr && name == id) {
// Your convention: 0 means unset for integer PKs
unset = (static_cast<std::uint64_t>(pk) == 0ULL);
}
}
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
template<typename T>
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
template<class P>
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
template<class P>
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
};
struct pk_value_extractor {
std::uint64_t value{0};
template<class V>
void on_primary_key(const char * /*id*/, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
value = static_cast<std::uint64_t>(pk);
}
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
template<typename T>
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
template<class P>
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
template<class P>
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
template<class C>
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
};
}
template<typename PrimaryKeyType>
class primary_key_accessor {
public:
PrimaryKeyType get() const;
PrimaryKeyType
};
}
#endif //MATADOR_PRIMARY_KEY_ACCESSOR_HPP

View File

@ -124,6 +124,7 @@ add_library(matador-core STATIC
object/collection_utils.cpp object/collection_utils.cpp
../../include/matador/object/pk_field_locator.hpp ../../include/matador/object/pk_field_locator.hpp
../../include/matador/object/object_cache.hpp ../../include/matador/object/object_cache.hpp
../../include/matador/utils/primary_key_accessor.hpp
) )
target_link_libraries(matador-core ${CMAKE_DL_LIBS}) target_link_libraries(matador-core ${CMAKE_DL_LIBS})

View File

@ -17,11 +17,10 @@
#include "matador/sql/dialect.hpp" #include "matador/sql/dialect.hpp"
namespace matador::query { namespace matador::query {
sql::query_context query_builder::compile(const query_data& data, sql::query_context query_builder::compile(const query_data& data,
const sql::dialect& d, const sql::dialect& d,
const std::optional<std::reference_wrapper<const sql::connection_impl>> conn) const std::optional<std::reference_wrapper<const sql::connection_impl>>
{ conn) {
data_ = &data; data_ = &data;
dialect_ = &d; dialect_ = &d;
connection_ = conn; connection_ = conn;
@ -208,23 +207,28 @@ void query_builder::visit(internal::query_into_part &part) {
query_.sql += ")"/* + result*/; query_.sql += ")"/* + result*/;
} }
struct value_visitor { struct value_visitor
{
value_visitor(attribute_string_writer& w, sql::query_context& ctx) value_visitor(attribute_string_writer& w, sql::query_context& ctx)
: value_to_string_visitor(w, ctx) {} : value_to_string_visitor(w, ctx) {
}
void operator()(const utils::database_type& val) { void operator()(const utils::database_type& val) {
std::visit(value_to_string_visitor, val); std::visit(value_to_string_visitor, val);
} }
void operator()(const utils::placeholder&/*val*/) { void operator()(const utils::placeholder&/*val*/) {
value_to_string_visitor.query.bind_vars.emplace_back(std::string("value_") + std::to_string(value_to_string_visitor.query.bind_vars.size() + 1)); value_to_string_visitor.query.bind_vars.emplace_back(
value_to_string_visitor.result = value_to_string_visitor.writer->dialect().next_placeholder(value_to_string_visitor.query.bind_vars); std::string("value_") + std::to_string(value_to_string_visitor.query.bind_vars.size() + 1));
value_to_string_visitor.result = value_to_string_visitor.writer->dialect().next_placeholder(
value_to_string_visitor.query.bind_vars);
} }
internal::basic_type_to_string_visitor value_to_string_visitor; internal::basic_type_to_string_visitor value_to_string_visitor;
}; };
std::string query_builder::determine_value(const sql::dialect &d, sql::query_context& ctx, const abstract_column_expression &exp) { std::string query_builder::determine_value(const sql::dialect& d, sql::query_context& ctx,
const abstract_column_expression& exp) {
attribute_string_writer writer(d, {}); attribute_string_writer writer(d, {});
expression_evaluator v(d, ctx); expression_evaluator v(d, ctx);
exp.accept(v); exp.accept(v);
@ -232,7 +236,8 @@ std::string query_builder::determine_value(const sql::dialect &d, sql::query_con
return v.result(); return v.result();
} }
std::string query_builder::determine_value(value_visitor &visitor, const std::variant<utils::placeholder, utils::database_type> &val) { std::string query_builder::determine_value(value_visitor& visitor,
const std::variant<utils::placeholder, utils::database_type>& val) {
std::visit(visitor, val); std::visit(visitor, val);
return visitor.value_to_string_visitor.result; return visitor.value_to_string_visitor.result;
} }
@ -281,14 +286,12 @@ void query_builder::visit(internal::query_delete_part &/*delete_part*/) {
query_.sql = dialect_->remove(); query_.sql = dialect_->remove();
} }
void query_builder::visit(internal::query_delete_from_part &part) void query_builder::visit(internal::query_delete_from_part& part) {
{
query_.table_name = part.table().name(); query_.table_name = part.table().name();
query_.sql += " " + build_table_name(part.token(), *dialect_, query_.table_name); query_.sql += " " + build_table_name(part.token(), *dialect_, query_.table_name);
} }
void query_builder::visit(internal::query_create_part &/*create_part*/) void query_builder::visit(internal::query_create_part&/*create_part*/) {
{
query_.command = sql::sql_command::CreateTable; query_.command = sql::sql_command::CreateTable;
query_.sql = dialect_->create(); query_.sql = dialect_->create();
} }
@ -296,8 +299,7 @@ void query_builder::visit(internal::query_create_part &/*create_part*/)
void build_create_column(std::string& out, const table_column& col, const sql::dialect& d); void build_create_column(std::string& out, const table_column& col, const sql::dialect& d);
std::string build_constraint(const table_constraint& cons, const sql::dialect& d); std::string build_constraint(const table_constraint& cons, const sql::dialect& d);
void query_builder::visit(internal::query_create_table_part &part) void query_builder::visit(internal::query_create_table_part& part) {
{
query_.sql += " " + dialect_->table() + " " + dialect_->prepare_identifier_string(part.table().name()) + " ("; query_.sql += " " + dialect_->table() + " " + dialect_->prepare_identifier_string(part.table().name()) + " (";
query_.table_name = part.table().name(); query_.table_name = part.table().name();
@ -496,7 +498,9 @@ std::string query_builder::build_add_constraint_string(const table_constraint &c
if (c.is_primary_key_constraint()) { if (c.is_primary_key_constraint()) {
result.append(dialect_->primary_key()).append(" (").append(c.column_name()).append(")"); result.append(dialect_->primary_key()).append(" (").append(c.column_name()).append(")");
} else if (c.is_foreign_key_constraint()) { } else if (c.is_foreign_key_constraint()) {
result.append(dialect_->foreign_key()).append(" (").append(c.column_name()).append(") ").append(dialect_->references()).append(" ").append(c.referenced_table()).append(" (").append(c.referenced_column()).append(")"); result.append(dialect_->foreign_key()).append(" (").append(c.column_name()).append(") ").
append(dialect_->references()).append(" ").append(c.referenced_table()).append(" (").append(
c.referenced_column()).append(")");
} else if (c.is_unique_constraint()) { } else if (c.is_unique_constraint()) {
result.append(dialect_->unique()).append(" (").append(c.column_name()).append(")"); result.append(dialect_->unique()).append(" (").append(c.column_name()).append(")");
} }

View File

@ -15,6 +15,7 @@ void prepare_column(sql::query_context& ctx, const sql::dialect& d, const table_
attribute_string_writer writer(d, {}); attribute_string_writer writer(d, {});
expression_evaluator v(d, ctx); expression_evaluator v(d, ctx);
col.expression()->accept(v); col.expression()->accept(v);
ctx.sql += v.result();
if (col.has_alias()) { if (col.has_alias()) {
ctx.sql.append(" ").append(d.as()).append(" ").append(col.alias()); ctx.sql.append(" ").append(d.as()).append(" ").append(col.alias());

View File

@ -5,6 +5,8 @@
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
#include "matador/query/expression/column_expression.hpp"
namespace matador::query { namespace matador::query {
table_column operator ""_col(const char *name, const size_t len) { table_column operator ""_col(const char *name, const size_t len) {
@ -67,6 +69,10 @@ table_column::table_column(const class table* tab,
table_column::table_column(const std::shared_ptr<abstract_column_expression>& expression) noexcept table_column::table_column(const std::shared_ptr<abstract_column_expression>& expression) noexcept
: table_column(nullptr, "", "", utils::basic_type::Unknown, {}, sql::sql_function_t::None, expression) {} : table_column(nullptr, "", "", utils::basic_type::Unknown, {}, sql::sql_function_t::None, expression) {}
table_column::table_column(column_expression&& expression) noexcept
: table_column(std::shared_ptr(expression.release())) {
}
table_column & table_column::operator=(const table_column &other) { table_column & table_column::operator=(const table_column &other) {
if (this == &other) { if (this == &other) {
return *this; return *this;
@ -95,7 +101,7 @@ bool table_column::equals(const table_column &x) const {
} }
table_column table_column::as(const std::string& alias) const { table_column table_column::as(const std::string& alias) const {
return {table_, column_name_, alias, type_, attributes_, function_}; return {table_, column_name_, alias, type_, attributes_, function_, expression_};
} }
const std::string& table_column::name() const { const std::string& table_column::name() const {

View File

@ -1,8 +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/sql/connection.hpp" #include "matador/sql/connection.hpp"
#include <stack> #include <stack>

View File

@ -0,0 +1,30 @@
#include "TableSequenceFixture.hpp"
#include "matador/query/query.hpp"
#include "matador/query/builder.hpp"
#include "connection.hpp"
#include "catch2/catch_test_macros.hpp"
namespace matador::test {
TableSequenceFixture::TableSequenceFixture()
: db(connection::dns) {
REQUIRE(db.open());
REQUIRE(query::query::create()
.table(sequence_table_name)
.columns({
query::column("name", utils::basic_type::Varchar, 255),
query::column("next_id", utils::basic_type::Int64)
})
.execute(db));
}
TableSequenceFixture::~TableSequenceFixture() {
REQUIRE(query::query::drop()
.table(sequence_table_name)
.execute(db));
REQUIRE(db.close());
}
}

View File

@ -0,0 +1,18 @@
#ifndef MATADOR_TABLE_SEQUENCE_FIXTURE_HPP
#define MATADOR_TABLE_SEQUENCE_FIXTURE_HPP
#include "matador/sql/connection.hpp"
namespace matador::test {
class TableSequenceFixture {
public:
TableSequenceFixture();
~TableSequenceFixture();
protected:
sql::connection db;
std::string sequence_table_name{"test_seq_table"};
};
}
#endif //MATADOR_TABLE_SEQUENCE_FIXTURE_HPP

View File

@ -0,0 +1,40 @@
#include "catch2/catch_test_macros.hpp"
#include "matador/query/criteria.hpp"
#include "matador/query/expression/expression_operators.hpp"
#include "matador/query/query.hpp"
#include "matador/query/table_column.hpp"
#include "TableSequenceFixture.hpp"
using namespace matador::query;
using namespace matador::test;
TEST_CASE_METHOD(TableSequenceFixture, "test create and drop table sequence", "[table_sequence][create][drop]") {
const auto next_id_col = "next_id"_col;
auto result = query::query::insert()
.into(sequence_table_name, { "name", next_id_col})
.values({ "test_seq", 1 })
.execute(db);
REQUIRE(result);
REQUIRE(result->affected_rows == 1);
table_column exp(next_id_col - 1);
auto id_result = query::query::update(sequence_table_name)
.set(next_id_col, next_id_col + 1)
.where("name"_col == "test_seq")
.returning((next_id_col - 1).as("id"))
.fetch_value<int64_t>(db);
REQUIRE(id_result);
REQUIRE(id_result->has_value());
REQUIRE(id_result->value() == 1);
id_result = query::query::select({next_id_col})
.from(sequence_table_name)
.where("name"_col == "test_seq")
.fetch_value<int64_t>(db);
REQUIRE(id_result);
REQUIRE(id_result->has_value());
REQUIRE(id_result->value() == 2);
}