From 9e76203bc9992a24c5a68ef358e5df762ad1363a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Tue, 11 Nov 2025 16:19:58 +0100 Subject: [PATCH] column, placeholder and column_value generator progress --- include/matador/query/generator.hpp | 142 +++++++++++++++--- .../query/internal/column_value_pair.hpp | 7 +- .../matador/query/internal/query_parts.hpp | 2 +- include/matador/sql/column_generator.hpp | 13 -- source/orm/orm/session_query_builder.cpp | 2 +- source/orm/query/generator.cpp | 25 +++ .../orm/query/internal/column_value_pair.cpp | 20 ++- source/orm/query/internal/query_parts.cpp | 2 +- source/orm/query/query_compiler.cpp | 16 +- test/orm/CMakeLists.txt | 1 + test/orm/query/GeneratorTests.cpp | 51 +++++++ 11 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 test/orm/query/GeneratorTests.cpp diff --git a/include/matador/query/generator.hpp b/include/matador/query/generator.hpp index e4a0796..2bd388e 100644 --- a/include/matador/query/generator.hpp +++ b/include/matador/query/generator.hpp @@ -6,28 +6,69 @@ #include #include +#include "matador/object/repository.hpp" + #include "matador/query/fk_value_extractor.hpp" #include "matador/query/internal/column_value_pair.hpp" +#include "matador/sql/table.hpp" + #include namespace matador::query::generator { -class column_generator final { +enum class column_generator_options { + None = 0, + GenerateAlias = 1 << 0, + ForceLazy = 1 << 1 +}; + +template +class enum_flags { public: - template < class V > + explicit enum_flags(EnumType value) : value_(value) {} +private: + EnumType value_; +}; + +inline column_generator_options operator|(column_generator_options a, column_generator_options b) { return static_cast(static_cast(a) | static_cast(b)); } +inline column_generator_options operator&(column_generator_options a, column_generator_options b) { return static_cast(static_cast(a) & static_cast(b)); } +inline column_generator_options& operator|= (column_generator_options& a, column_generator_options b) { return reinterpret_cast(reinterpret_cast(a) |= static_cast(b)); } +inline column_generator_options& operator&= (column_generator_options& a, column_generator_options b) { return reinterpret_cast(reinterpret_cast(a) &= static_cast(b)); } + +inline bool is_column_generator_option_set(const column_generator_options source, const column_generator_options needle) { return static_cast(source & needle) > 0; } + +constexpr auto default_column_generator_options = column_generator_options::ForceLazy; + +class column_generator2 { +public: + explicit column_generator2(const std::string &table_name = "", + column_generator_options options = default_column_generator_options); + explicit column_generator2(const object::repository &repo, + const std::string &table_name = "", + column_generator_options options = default_column_generator_options); + + template< class Type > + std::vector generate() { + Type obj; + return generate(obj); + } + + template< class Type > + std::vector generate(const Type &obj) { + result_.clear(); + access::process(*this, obj); + + return result_; + } + + template < class V > void on_primary_key(const char *id, V &, const utils::primary_key_attribute& /*attr*/ = utils::default_pk_attributes) { - if (has_many_to_many_) { - return; - } push(id); } void on_revision(const char *id, uint64_t &/*rev*/); template void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) { - if (has_many_to_many_) { - return; - } push(id); } @@ -42,32 +83,29 @@ public: template void on_has_many(const char * /*id*/, ContainerType &, const char *, const utils::foreign_attributes &attr) { - if (has_many_to_many_) { + if (attr.fetch() == utils::fetch_type::LAZY || is_column_generator_option_set(options_, column_generator_options::ForceLazy)) { return; } - if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { + if (!repo_) { return; } - const auto info = table_schema_.info(); + const auto info = repo_->get().info(); if (!info) { return; } if (seen_tables.count(info->get().name()) == 0) { auto it = seen_tables.insert(info->get().name()).first; - table_name_stack_.push(info.value().get().name()); + table_stack_.push(std::make_shared(info.value().get().name())); typename ContainerType::value_type::value_type obj; access::process(*this, obj); - table_name_stack_.pop(); + table_stack_.pop(); seen_tables.erase(it); } } template void on_has_many_to_many(const char * /*id*/, ContainerType & /*cont*/, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) { - if (!has_many_to_many_) { - return; - } push(join_column); push(inverse_join_column); } @@ -75,7 +113,41 @@ public: template static void on_has_many_to_many(const char * /*id*/, ContainerType & /*cont*/, const utils::foreign_attributes &/*attr*/) {} +private: + template + void on_foreign_key(const char *id, Pointer &, const utils::foreign_attributes &attr) { + if (attr.fetch() == utils::fetch_type::LAZY || is_column_generator_option_set(options_, column_generator_options::ForceLazy)) { + push(id); + } else { + if (!repo_) { + return; + } + const auto info = repo_->get().info(); + if (!info) { + return; + } + if (seen_tables.count(info->get().name()) == 0) { + auto it = seen_tables.insert(info->get().name()).first; + table_stack_.push(std::make_shared(info.value().get().name())); + typename Pointer::value_type obj; + access::process(*this, obj); + table_stack_.pop(); + seen_tables.erase(it); + } + } + } + + void push(const std::string &column_name); + +private: + std::optional> repo_; + std::vector result_; + std::stack> table_stack_; + std::unordered_set seen_tables; + int column_index{0}; + column_generator_options options_{false}; }; + class placeholder_generator final { public: template< class Type > @@ -174,17 +246,43 @@ std::vector placeholders(const Type &obj) { return generator.generate(obj); } -template -std::vector column_value_pairs() { - column_value_generator generator; - return generator.generate(); -} - template std::vector column_value_pairs(const Type &obj) { column_value_generator generator; return generator.generate(obj); } +template +std::vector columns(const object::repository &repo, + const std::string &table_name = "", + const column_generator_options options = default_column_generator_options) { + column_generator2 generator(repo, table_name, options); + return generator.generate(); +} + +template +std::vector columns(const Type &obj, + const object::repository &repo, + const std::string &table_name = "", + const column_generator_options options = default_column_generator_options) { + column_generator2 generator(repo, table_name, options); + return generator.generate(obj); +} + +template +std::vector columns(const std::string &table_name = "", + const column_generator_options options = default_column_generator_options) { + column_generator2 generator(table_name, options); + return generator.generate(); +} + +template +std::vector columns(const Type &obj, + const std::string &table_name = "", + const column_generator_options options = default_column_generator_options) { + column_generator2 generator(table_name, options); + return generator.generate(obj); +} + } #endif //MATADOR_GENERATOR_HPP \ No newline at end of file diff --git a/include/matador/query/internal/column_value_pair.hpp b/include/matador/query/internal/column_value_pair.hpp index 2fcfa48..f17d48e 100644 --- a/include/matador/query/internal/column_value_pair.hpp +++ b/include/matador/query/internal/column_value_pair.hpp @@ -15,11 +15,14 @@ public: column_value_pair(const char *name, utils::database_type value); column_value_pair(const char *name, utils::placeholder p); - [[nodiscard]] const std::string& name() const; + friend bool operator==(const column_value_pair &lhs, const column_value_pair &rhs); + friend bool operator!=(const column_value_pair &lhs, const column_value_pair &rhs); + + [[nodiscard]] const sql::column& col() const; [[nodiscard]] const std::variant& value() const; private: - std::string name_; + sql::column column_; std::variant value_; }; diff --git a/include/matador/query/internal/query_parts.hpp b/include/matador/query/internal/query_parts.hpp index 84e219f..6e0b49b 100644 --- a/include/matador/query/internal/query_parts.hpp +++ b/include/matador/query/internal/query_parts.hpp @@ -320,7 +320,7 @@ class query_set_part final : public query_part public: explicit query_set_part(const std::vector& key_value_pairs); - [[nodiscard]] const std::vector& key_values() const; + [[nodiscard]] const std::vector& column_values() const; private: void accept(query_part_visitor &visitor) override; diff --git a/include/matador/sql/column_generator.hpp b/include/matador/sql/column_generator.hpp index c4f92ab..dd5ab37 100644 --- a/include/matador/sql/column_generator.hpp +++ b/include/matador/sql/column_generator.hpp @@ -40,19 +40,6 @@ public: static std::vector generate(const object::repository &scm, const std::string &name, bool force_lazy = false); - template < class Type > - static std::vector generate_has_many_to_many(const object::repository &scm, const bool force_lazy = false) { - const auto info = scm.info(); - if (!info) { - return {}; - } - std::vector columns; - column_generator gen(columns, scm, info.value().get().name(), force_lazy); - Type obj; - access::process(gen, obj); - return columns; - } - template < class V > void on_primary_key(const char *id, V &, const utils::primary_key_attribute& /*attr*/ = utils::default_pk_attributes) { push(id); diff --git a/source/orm/orm/session_query_builder.cpp b/source/orm/orm/session_query_builder.cpp index 1807c70..63954e7 100644 --- a/source/orm/orm/session_query_builder.cpp +++ b/source/orm/orm/session_query_builder.cpp @@ -74,7 +74,7 @@ void session_query_builder::append_join(const sql::column &left, const sql::colu using namespace matador::query; entity_query_data_.joins.push_back({ {right.table_}, - left == right + std::make_unique(left, binary_operator::EQUALS, right) }); } } diff --git a/source/orm/query/generator.cpp b/source/orm/query/generator.cpp index 0024e7d..718ac90 100644 --- a/source/orm/query/generator.cpp +++ b/source/orm/query/generator.cpp @@ -1,6 +1,31 @@ #include "matador/query/generator.hpp" namespace matador::query::generator { +column_generator2::column_generator2( const std::string& table_name, const column_generator_options options) +: options_(options) { + table_stack_.push(table_name.empty() ? std::make_shared() : std::make_shared(table_name)); +} + +column_generator2::column_generator2(const object::repository& repo, const std::string& table_name, const column_generator_options options) +: repo_(std::cref(repo)) +, options_(options) { + table_stack_.push(table_name.empty() ? std::make_shared() : std::make_shared(table_name)); +} + +void column_generator2::on_revision( const char* id, uint64_t& ) { + push(id); +} + +void column_generator2::push( const std::string& column_name ) { + if (is_column_generator_option_set(options_, column_generator_options::GenerateAlias)) { + char str[4]; + snprintf(str, 4, "c%02d", ++column_index); + result_.emplace_back(table_stack_.top(), column_name, str); + } else { + result_.emplace_back(table_stack_.top(), column_name); + } +} + void placeholder_generator::on_revision(const char* /*id*/, uint64_t&) { result_.emplace_back(utils::_); } diff --git a/source/orm/query/internal/column_value_pair.cpp b/source/orm/query/internal/column_value_pair.cpp index 35ce1e5..45826ff 100644 --- a/source/orm/query/internal/column_value_pair.cpp +++ b/source/orm/query/internal/column_value_pair.cpp @@ -5,29 +5,37 @@ namespace matador::query::internal { column_value_pair::column_value_pair(std::string name, utils::database_type value) -: name_(std::move(name)) +: column_(std::move(name)) , value_(std::move(value)) { } column_value_pair::column_value_pair(const sql::column &col, utils::database_type value) -: name_(col.name) +: column_(col) , value_(std::move(value)) { } column_value_pair::column_value_pair(const char *name, utils::database_type value) -: name_(name) +: column_(name) , value_(std::move(value)) { } column_value_pair::column_value_pair( const char* name, utils::placeholder p ) -: name_(name) +: column_(name) , value_(p) {} -const std::string &column_value_pair::name() const { - return name_; +const sql::column &column_value_pair::col() const { + return column_; } const std::variant& column_value_pair::value() const { return value_; } + +bool operator==( const column_value_pair& lhs, const column_value_pair& rhs ) { + return lhs.column_.equals(rhs.column_) && lhs.value_ == rhs.value_; +} + +bool operator!=( const column_value_pair& lhs, const column_value_pair& rhs ) { + return !( lhs == rhs ); +} } \ No newline at end of file diff --git a/source/orm/query/internal/query_parts.cpp b/source/orm/query/internal/query_parts.cpp index b64413f..b87dfeb 100644 --- a/source/orm/query/internal/query_parts.cpp +++ b/source/orm/query/internal/query_parts.cpp @@ -304,7 +304,7 @@ query_set_part::query_set_part(const std::vector& key_value_p : query_part(sql::dialect_token::Set) , key_value_pairs_(key_value_pairs) {} -const std::vector &query_set_part::key_values() const +const std::vector &query_set_part::column_values() const { return key_value_pairs_; } diff --git a/source/orm/query/query_compiler.cpp b/source/orm/query/query_compiler.cpp index d7711ef..f3f524d 100644 --- a/source/orm/query/query_compiler.cpp +++ b/source/orm/query/query_compiler.cpp @@ -363,18 +363,18 @@ void query_compiler::visit(internal::query_set_part &part) { attribute_string_writer writer(*dialect_, connection_); std::string result; - value_visitor visitor(writer, query_); if (part.key_values().size() < 2) { - for (const auto &col: part.key_values()) { - result.append(dialect_->prepare_identifier_string(col.name()) + "="); - result.append(determine_value(visitor, col.value())); + value_visitor visitor(writer, query_); if (part.column_values().size() < 2) { + for (const auto &column_value: part.column_values()) { + result.append(dialect_->prepare_identifier_string(column_value.col().name) + "="); + result.append(determine_value(visitor, column_value.value())); } } else { - auto it = part.key_values().begin(); - result.append(dialect_->prepare_identifier_string(it->name()) + "="); + auto it = part.column_values().begin(); + result.append(dialect_->prepare_identifier_string(it->col().name) + "="); result.append(determine_value(visitor, (it++)->value())); - for (; it != part.key_values().end(); ++it) { + for (; it != part.column_values().end(); ++it) { result.append(", "); - result.append(dialect_->prepare_identifier_string(it->name()) + "="); + result.append(dialect_->prepare_identifier_string(it->col().name) + "="); result.append(determine_value(visitor, it->value())); } } diff --git a/test/orm/CMakeLists.txt b/test/orm/CMakeLists.txt index ef2f2bf..bea6a93 100644 --- a/test/orm/CMakeLists.txt +++ b/test/orm/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(OrmTests sql/StatementCacheTest.cpp utils/auto_reset_event.cpp utils/auto_reset_event.hpp + query/GeneratorTests.cpp ) target_link_libraries(OrmTests matador-orm matador-core Catch2::Catch2WithMain) diff --git a/test/orm/query/GeneratorTests.cpp b/test/orm/query/GeneratorTests.cpp new file mode 100644 index 0000000..315b158 --- /dev/null +++ b/test/orm/query/GeneratorTests.cpp @@ -0,0 +1,51 @@ +#include + +#include "matador/query/generator.hpp" + +#include "matador/sql/column.hpp" + +#include "../../models/airplane.hpp" +#include "../../models/flight.hpp" + +using namespace matador::utils; +using namespace matador::query; +using namespace matador::sql; +using namespace matador::test; + +TEST_CASE("Test placeholder generator", "[generator][placeholder]") { + REQUIRE(generator::placeholders() == std::vector{_, _, _}); +} + +TEST_CASE("Test column value generator", "[generator][column_value]") { + SECTION("Test column value generator for simple table") { + const airplane a380{1, "Airbus", "A380"}; + const auto pairs = generator::column_value_pairs(a380); + const std::vector expected_result{ + { "id", 1U }, + { "brand", std::string{"Airbus"} }, + { "model", std::string{"A380"} } + }; + REQUIRE(pairs.size() == 3); + REQUIRE(pairs == expected_result); + } + + SECTION("Test column value generator for table with foreign key") { + + } +} + +TEST_CASE("Test column generator", "[generator][column]") { + SECTION("Test column generator for simple table") { + const std::vector expected_columns { + column("id"), + column("brand"), + column("model"), + }; + const auto result = generator::columns(); + REQUIRE(result.size() == expected_columns.size()); + auto it = result.begin(); + for (const auto& c : expected_columns) { + REQUIRE(c.equals( *it++)); + } + } +} \ No newline at end of file