From bfbbd4c58990dc6df300324603bac6d13f54f06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Tue, 2 Dec 2025 16:19:13 +0100 Subject: [PATCH] query builder progress --- backends/postgres/src/postgres_connection.cpp | 4 +- include/matador/object/attribute.hpp | 17 ++-- .../matador/object/attribute_generator.hpp | 8 +- include/matador/object/basic_object_info.hpp | 2 + include/matador/object/constraint.hpp | 12 ++- include/matador/object/object_generator.hpp | 8 +- include/matador/orm/session_query_builder.hpp | 4 +- include/matador/query/builder.hpp | 35 ++++++-- .../query_create_intermediate.hpp | 22 ++++- .../matador/query/internal/query_parts.hpp | 31 ++++++- include/matador/query/query_compiler.hpp | 5 ++ include/matador/query/query_part_visitor.hpp | 4 + include/matador/sql/dialect.hpp | 2 + include/matador/sql/dialect_token.hpp | 1 + include/matador/utils/basic_types.hpp | 3 +- include/matador/utils/data_type_traits.hpp | 6 +- source/core/object/attribute.cpp | 25 ++---- source/core/object/basic_object_info.cpp | 4 + source/core/object/constraint.cpp | 47 +++++++++- source/core/object/repository_node.cpp | 2 +- source/orm/orm/schema.cpp | 4 +- source/orm/orm/session.cpp | 4 +- .../query/intermediates/fetchable_query.cpp | 3 +- .../query_create_intermediate.cpp | 28 ++++-- source/orm/query/internal/query_parts.cpp | 30 +++++-- source/orm/query/query_compiler.cpp | 87 +++++++++++-------- source/orm/sql/dialect.cpp | 4 + source/orm/sql/query_result.cpp | 3 +- test/core/object/AttributeGeneratorTest.cpp | 24 ++--- test/orm/query/QueryBuilderTest.cpp | 47 ++++++---- test/orm/sql/ColumnTest.cpp | 36 ++++---- 31 files changed, 348 insertions(+), 164 deletions(-) diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index b627161..d782b96 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -240,9 +240,9 @@ utils::result, utils::error> postgres_connection: // Todo: extract size auto type = (string2type(reader.column(2))); end = nullptr; - object::null_option_type null_opt{object::null_option_type::NULLABLE}; + object::null_option_type null_opt{object::null_option_type::Nullable}; if (strtoul(reader.column(4), &end, 10) == 0) { - null_opt = object::null_option_type::NOT_NULL; + null_opt = object::null_option_type::NotNull; } // f.default_value(res->column(4)); prototype.emplace_back(name, type, utils::null_attributes, null_opt, index); diff --git a/include/matador/object/attribute.hpp b/include/matador/object/attribute.hpp index 5353bf6..b15ee5d 100644 --- a/include/matador/object/attribute.hpp +++ b/include/matador/object/attribute.hpp @@ -10,14 +10,9 @@ namespace matador::object { enum class null_option_type : uint8_t { - NULLABLE, NOT_NULL + Nullable, NotNull }; -struct attribute_options { - utils::field_attributes attributes; - null_option_type null_option{null_option_type::NOT_NULL}; - int index{-1}; -}; class object; class attribute_generator; @@ -33,20 +28,17 @@ public: attribute() = default; attribute(std::string name, utils::basic_type type, - const utils::field_attributes&, - null_option_type null_opt); + const utils::field_attributes &attr = utils::null_attributes, + null_option_type null_opt = null_option_type::NotNull); [[nodiscard]] const std::string& name() const; void name(const std::string& n); [[nodiscard]] std::string full_name() const; - [[nodiscard]] int index() const; [[nodiscard]] const utils::field_attributes& attributes() const; [[nodiscard]] utils::field_attributes& attributes(); [[nodiscard]] bool is_nullable() const; [[nodiscard]] utils::basic_type type() const; [[nodiscard]] object* owner() const; - // [[nodiscard]] const std::string& table_name() const; - // void table_name(const std::string& name); void change_type(utils::basic_type type, const utils::field_attributes &attr = utils::null_attributes); template < typename Type > void change_type(const utils::field_attributes &attr = utils::null_attributes) { @@ -75,8 +67,9 @@ private: std::string name_; object *owner_{nullptr}; - attribute_options options_; utils::basic_type type_{utils::basic_type::type_null}; + utils::field_attributes options_{}; + null_option_type null_option_{null_option_type::NotNull}; }; diff --git a/include/matador/object/attribute_generator.hpp b/include/matador/object/attribute_generator.hpp index c269d18..2e0875a 100644 --- a/include/matador/object/attribute_generator.hpp +++ b/include/matador/object/attribute_generator.hpp @@ -27,7 +27,7 @@ public: template attribute generate(const char *id, Type &x) { access::process(*this, x); - return attribute{id, type_, {utils::constraints::ForeignKey }, null_option_type::NOT_NULL}; + return attribute{id, type_, {utils::constraints::ForeignKey }, null_option_type::NotNull}; } template @@ -139,18 +139,18 @@ private: template void attribute_generator::on_primary_key(const char *id, ValueType &x, const utils::primary_key_attribute& attr) { - auto &ref = emplace_attribute(id, { attr.size(), utils::constraints::PrimaryKey }, null_option_type::NOT_NULL); + auto &ref = emplace_attribute(id, { attr.size(), utils::constraints::PrimaryKey }, null_option_type::NotNull); prepare_primary_key(ref, utils::identifier(x)); } template void attribute_generator::on_attribute(const char *id, Type &/*x*/, const utils::field_attributes &attr) { - std::ignore = emplace_attribute(id, attr, null_option_type::NOT_NULL); + std::ignore = emplace_attribute(id, attr, null_option_type::NotNull); } template void attribute_generator::on_attribute(const char *id, std::optional & /*x*/, const utils::field_attributes &attr) { - std::ignore = emplace_attribute(id, attr, null_option_type::NULLABLE); + std::ignore = emplace_attribute(id, attr, null_option_type::Nullable); } } diff --git a/include/matador/object/basic_object_info.hpp b/include/matador/object/basic_object_info.hpp index e088e2d..8aace6d 100644 --- a/include/matador/object/basic_object_info.hpp +++ b/include/matador/object/basic_object_info.hpp @@ -2,6 +2,7 @@ #define BASIC_PROTOTYPE_INFO_HPP #include "matador/object/attribute.hpp" +#include "matador/object/constraint.hpp" #include "matador/object/relation_endpoint.hpp" #include "matador/utils/identifier.hpp" @@ -26,6 +27,7 @@ public: [[nodiscard]] std::type_index type_index() const; [[nodiscard]] std::string name() const; [[nodiscard]] const std::list& attributes() const; + [[nodiscard]] const std::list& constraints() const; [[nodiscard]] bool has_primary_key() const; [[nodiscard]] const utils::identifier& primary_key() const; diff --git a/include/matador/object/constraint.hpp b/include/matador/object/constraint.hpp index bf66968..430d938 100644 --- a/include/matador/object/constraint.hpp +++ b/include/matador/object/constraint.hpp @@ -4,6 +4,7 @@ #include "matador/utils/constraints.hpp" #include +#include namespace matador::object { @@ -18,6 +19,14 @@ public: explicit constraint(std::string name); [[nodiscard]] const std::string& name() const; + [[nodiscard]] const class attribute* attribute() const; + [[nodiscard]] std::string column_name() const; + [[nodiscard]] const object* owner() const; + [[nodiscard]] bool is_primary_key_constraint() const; + [[nodiscard]] bool is_foreign_key_constraint() const; + [[nodiscard]] bool is_unique_constraint() const; + [[nodiscard]] const std::string& ref_table_name() const; + [[nodiscard]] const std::string& ref_column_name() const; private: friend class constraint_builder; @@ -26,7 +35,8 @@ private: friend class object; std::string name_; - attribute* attr_{nullptr}; + std::variant attr_; + // class attribute* attr_{nullptr}; object *owner_{nullptr}; utils::constraints options_{utils::constraints::None}; std::string ref_table_name_{}; diff --git a/include/matador/object/object_generator.hpp b/include/matador/object/object_generator.hpp index 4d5ea1f..805c653 100644 --- a/include/matador/object/object_generator.hpp +++ b/include/matador/object/object_generator.hpp @@ -88,7 +88,7 @@ public: template void on_foreign_key(const char *id, Pointer &/*x*/) { const auto type = pk_type_determinator::determine(); - auto &ref = object_->attributes_.emplace_back(id, type, utils::constraints::ForeignKey, null_option_type::NOT_NULL); + auto &ref = object_->attributes_.emplace_back(id, type, utils::constraints::ForeignKey, null_option_type::NotNull); ref.owner_ = object_.get(); } template @@ -121,19 +121,19 @@ private: template void object_generator::on_primary_key(const char *id, ValueType &x, const utils::primary_key_attribute& attr) { - auto &ref = emplace_attribute(id, { attr.size(), utils::constraints::PrimaryKey }, null_option_type::NOT_NULL); + auto &ref = emplace_attribute(id, { attr.size(), utils::constraints::PrimaryKey }, null_option_type::NotNull); prepare_primary_key(ref, utils::identifier(x)); create_pk_constraint(id); } template void object_generator::on_attribute(const char *id, Type &/*x*/, const utils::field_attributes &attr) { - std::ignore = emplace_attribute(id, attr, null_option_type::NOT_NULL); + std::ignore = emplace_attribute(id, attr, null_option_type::NotNull); } template void object_generator::on_attribute(const char *id, std::optional & /*x*/, const utils::field_attributes &attr) { - std::ignore = emplace_attribute(id, attr, null_option_type::NULLABLE); + std::ignore = emplace_attribute(id, attr, null_option_type::Nullable); } } diff --git a/include/matador/orm/session_query_builder.hpp b/include/matador/orm/session_query_builder.hpp index fd79131..aad174e 100644 --- a/include/matador/orm/session_query_builder.hpp +++ b/include/matador/orm/session_query_builder.hpp @@ -272,7 +272,7 @@ void session_query_builder::on_foreign_object(const char *id, Pointer &, const u append_join( query::column{table_info_stack_.top().table, id}, - query::column{next->second, info->get().reference_column()->name()} + query::column{next->second, info->get().primary_key_attribute()->name()} ); } else { push(id); @@ -281,7 +281,7 @@ void session_query_builder::on_foreign_object(const char *id, Pointer &, const u // create select query auto result = matador::query::query::select(generator::columns(schema_, generator::column_generator_options::ForceLazy)) .from(*foreign_table) - .where(column(foreign_table, info->get().reference_column()->name(), "") == _) + .where(column(foreign_table, info->get().primary_key_attribute()->name(), "") == _) .prepare(executor_); if (!result) { throw query_builder_exception(query_build_error::QueryError, result.release_error()); diff --git a/include/matador/query/builder.hpp b/include/matador/query/builder.hpp index e93ff1c..ad906d1 100644 --- a/include/matador/query/builder.hpp +++ b/include/matador/query/builder.hpp @@ -26,10 +26,8 @@ private: class data_type { public: - template - data_type() : type_(utils::data_type_traits()) {} - template - explicit data_type(const size_t size) : type_(utils::data_type_traits()), size_(size) {} + explicit data_type(const utils::basic_type type, const size_t size = 0) + : type_(type), size_(size) {} [[nodiscard]] const utils::basic_type& type() const { return type_; } [[nodiscard]] size_t size() const { return size_; } @@ -39,10 +37,28 @@ private: size_t size_{0}; }; -// using BigInt = data_type(); -// using Real = data_type; -// using double_ = data_type; -// using string = data_type; +template +class typed_data_type final : public data_type { +public: + typed_data_type() + : data_type(utils::data_type_traits::type()) {} +}; + +template +class sized_typed_data_type final : public data_type { +public: + explicit sized_typed_data_type(size_t size) + : data_type(utils::data_type_traits::type(size), size) {} +}; + +using BigInt = typed_data_type; +using Integer = typed_data_type; +using Real = typed_data_type; +using Double_ = typed_data_type; +using String = typed_data_type; +using Boolean = typed_data_type; +using Varchar = sized_typed_data_type; + class constraint { public: @@ -50,7 +66,7 @@ public: : name_(std::move(name)) {} [[nodiscard]] const std::string& name() const; - [[nodiscard]] const std::string& column_name() const; + [[nodiscard]] std::string column_name() const; [[nodiscard]] const utils::constraints& type() const; [[nodiscard]] bool is_primary_key_constraint() const; [[nodiscard]] bool is_foreign_key_constraint() const; @@ -64,6 +80,7 @@ private: utils::constraints type_{}; std::string referenced_table_; std::string referenced_column_; + data_type dt = Varchar{255}; }; } namespace matador::query { diff --git a/include/matador/query/intermediates/query_create_intermediate.hpp b/include/matador/query/intermediates/query_create_intermediate.hpp index f4ab9ed..0187287 100644 --- a/include/matador/query/intermediates/query_create_intermediate.hpp +++ b/include/matador/query/intermediates/query_create_intermediate.hpp @@ -5,16 +5,32 @@ #include "matador/query/intermediates/executable_query.hpp" -#include "matador/object/attribute_generator.hpp" +#include "matador/object/attribute.hpp" +#include "matador/object/constraint.hpp" namespace matador::query { +class query_create_table_columns_intermediate : public executable_query { +public: + using executable_query::executable_query; + + executable_query constraints(std::initializer_list constraints); + executable_query constraints(const std::list &constraints); +}; + +class query_create_table_intermediate : public query_intermediate { +public: + using query_intermediate::query_intermediate; + + query_create_table_columns_intermediate columns(std::initializer_list columns); + query_create_table_columns_intermediate columns(const std::list &columns); +}; + class query_create_intermediate : public query_intermediate { public: query_create_intermediate(); - executable_query table(const table &tab, std::initializer_list columns); - executable_query table(const query::table &tab, const std::vector &columns); + query_create_table_intermediate table(const table &tab); executable_query schema(const std::string &schema_name); }; diff --git a/include/matador/query/internal/query_parts.hpp b/include/matador/query/internal/query_parts.hpp index 500bf6d..4e18620 100644 --- a/include/matador/query/internal/query_parts.hpp +++ b/include/matador/query/internal/query_parts.hpp @@ -10,6 +10,7 @@ #include "matador/query/table.hpp" #include "matador/object/attribute.hpp" +#include "matador/object/constraint.hpp" #include "matador/utils/placeholder.hpp" @@ -364,17 +365,41 @@ private: class query_create_table_part final : public query_part { public: - query_create_table_part(class table tab, std::vector columns); + explicit query_create_table_part(class table tab); [[nodiscard]] const class table& table() const; - [[nodiscard]] const std::vector& columns() const; private: void accept(query_part_visitor &visitor) override; private: class table table_; - std::vector columns_; +}; + +class query_create_table_columns_part final : public query_part { +public: + explicit query_create_table_columns_part(const std::list &columns); + + [[nodiscard]] const std::list& columns() const; + +private: + void accept(query_part_visitor &visitor) override; + +private: + std::list columns_; +}; + +class query_create_table_constraints_part final : public query_part { +public: + explicit query_create_table_constraints_part(const std::list &constraints); + + [[nodiscard]] const std::list& constraints() const; + +private: + void accept(query_part_visitor &visitor) override; + +private: + std::list constraints_; }; class query_create_schema_part final : public query_part { diff --git a/include/matador/query/query_compiler.hpp b/include/matador/query/query_compiler.hpp index b7f629f..5df35e1 100644 --- a/include/matador/query/query_compiler.hpp +++ b/include/matador/query/query_compiler.hpp @@ -8,6 +8,7 @@ #include "matador/utils/placeholder.hpp" +#include #include namespace matador::sql { @@ -64,6 +65,8 @@ protected: void visit(internal::query_create_part &part) override; void visit(internal::query_create_table_part &part) override; + void visit(internal::query_create_table_columns_part& part) override; + void visit(internal::query_create_table_constraints_part& part) override; void visit(internal::query_create_schema_part& part) override; void visit(internal::query_drop_part &part) override; @@ -79,6 +82,8 @@ protected: size_t table_index{0}; const sql::dialect *dialect_{nullptr}; std::optional> connection_{}; + + std::function finisher_ = [](sql::query_context&) {}; }; } diff --git a/include/matador/query/query_part_visitor.hpp b/include/matador/query/query_part_visitor.hpp index e246669..be9e192 100644 --- a/include/matador/query/query_part_visitor.hpp +++ b/include/matador/query/query_part_visitor.hpp @@ -32,6 +32,8 @@ class query_delete_part; class query_delete_from_part; class query_create_part; class query_create_table_part; +class query_create_table_columns_part; +class query_create_table_constraints_part; class query_create_schema_part; class query_drop_part; class query_drop_table_part; @@ -75,6 +77,8 @@ public: virtual void visit(internal::query_create_part &part) = 0; virtual void visit(internal::query_create_table_part &part) = 0; + virtual void visit(internal::query_create_table_columns_part &part) = 0; + virtual void visit(internal::query_create_table_constraints_part &part) = 0; virtual void visit(internal::query_create_schema_part &part) = 0; virtual void visit(internal::query_drop_part &part) = 0; diff --git a/include/matador/sql/dialect.hpp b/include/matador/sql/dialect.hpp index e1de8f0..7f20150 100644 --- a/include/matador/sql/dialect.hpp +++ b/include/matador/sql/dialect.hpp @@ -137,6 +137,7 @@ public: [[nodiscard]] const std::string& column() const; [[nodiscard]] const std::string& columns() const; [[nodiscard]] const std::string& commit() const; + [[nodiscard]] const std::string& constraint() const; [[nodiscard]] const std::string& create() const; [[nodiscard]] const std::string& desc() const; [[nodiscard]] const std::string& distinct() const; @@ -201,6 +202,7 @@ private: {dialect_token::Column, "COLUMN"}, {dialect_token::Columns, "COLUMNS"}, {dialect_token::Commit, "COMMIT TRANSACTION"}, + {dialect_token::Constraint, "CONSTRAINT"}, {dialect_token::Create, "CREATE"}, {dialect_token::Desc, "DESC"}, {dialect_token::Distinct, "DISTINCT"}, diff --git a/include/matador/sql/dialect_token.hpp b/include/matador/sql/dialect_token.hpp index eb663c5..bc4a46f 100644 --- a/include/matador/sql/dialect_token.hpp +++ b/include/matador/sql/dialect_token.hpp @@ -19,6 +19,7 @@ enum class dialect_token : uint8_t { Column, Columns, Commit, + Constraint, Create, Database, Desc, diff --git a/include/matador/utils/basic_types.hpp b/include/matador/utils/basic_types.hpp index ea873c8..1884283 100644 --- a/include/matador/utils/basic_types.hpp +++ b/include/matador/utils/basic_types.hpp @@ -25,7 +25,8 @@ enum class basic_type : uint8_t { type_date, /*!< Data type date */ type_time, /*!< Data type time */ type_blob, /*!< Data type blob */ - type_null /*!< Data type null */ + type_null, /*!< Data type null */ + type_unknown /*!< Data type unknown */ }; } diff --git a/include/matador/utils/data_type_traits.hpp b/include/matador/utils/data_type_traits.hpp index ffb00fb..4b11835 100644 --- a/include/matador/utils/data_type_traits.hpp +++ b/include/matador/utils/data_type_traits.hpp @@ -19,7 +19,11 @@ class attribute_writer; * for a data type */ template < class Type, class Enable = void > -struct data_type_traits; +struct data_type_traits { + static basic_type type(std::size_t /*size*/) { return basic_type::type_unknown; } + static void read_value(attribute_reader &/*reader*/, const char *id, size_t index, nullptr_t &/*value*/, size_t /*size*/ = 0) {} + static void bind_value(attribute_writer &/*binder*/, size_t index, nullptr_t &/*value*/, size_t /*size*/ = 0) {} +}; } #endif //BASIC_TYPE_TRAITS_HPP diff --git a/source/core/object/attribute.cpp b/source/core/object/attribute.cpp index 85867d6..eab3474 100644 --- a/source/core/object/attribute.cpp +++ b/source/core/object/attribute.cpp @@ -6,7 +6,7 @@ namespace matador::object { attribute::attribute(std::string name) -: attribute(std::move(name), utils::basic_type::type_null, {}, null_option_type::NOT_NULL) { +: attribute(std::move(name), utils::basic_type::type_null, {}, null_option_type::NotNull) { } attribute::attribute(std::string name, @@ -14,8 +14,9 @@ attribute::attribute(std::string name, const utils::field_attributes &attr, const null_option_type null_opt) : name_(std::move(name)) -, options_{attr, null_opt} , type_(type) +, options_(attr) +, null_option_(null_opt) {} const std::string &attribute::name() const { @@ -30,20 +31,16 @@ std::string attribute::full_name() const { return owner_ ? owner_->name() + "." + name_ : name_; } -int attribute::index() const { - return options_.index; -} - const utils::field_attributes &attribute::attributes() const { - return options_.attributes; + return options_; } utils::field_attributes& attribute::attributes() { - return options_.attributes; + return options_; } bool attribute::is_nullable() const { - return options_.null_option == null_option_type::NULLABLE; + return null_option_ == null_option_type::Nullable; } utils::basic_type attribute::type() const { @@ -54,16 +51,8 @@ object* attribute::owner() const { return owner_; } -// const std::string& attribute::table_name() const { -// return owner_ ? owner_->name() : ""; -// } -// -// void attribute::table_name( const std::string& name ) { -// table_name_ = name; -// } - void attribute::change_type(const utils::basic_type type, const utils::field_attributes& attr) { - options_.attributes = attr; + options_ = attr; type_ = type; } diff --git a/source/core/object/basic_object_info.cpp b/source/core/object/basic_object_info.cpp index 7e3b132..ffaa0e7 100644 --- a/source/core/object/basic_object_info.cpp +++ b/source/core/object/basic_object_info.cpp @@ -36,6 +36,10 @@ const std::list& basic_object_info::attributes() const { return object_->attributes(); } +const std::list& basic_object_info::constraints() const { + return object_->constraints(); +} + bool basic_object_info::has_primary_key() const { return object_->has_primary_key(); } diff --git a/source/core/object/constraint.cpp b/source/core/object/constraint.cpp index 92917d3..4ad10f6 100644 --- a/source/core/object/constraint.cpp +++ b/source/core/object/constraint.cpp @@ -1,4 +1,5 @@ #include "matador/object/constraint.hpp" +#include "matador/object/attribute.hpp" namespace matador::object { constraint::constraint(std::string name) @@ -8,6 +9,49 @@ const std::string & constraint::name() const { return name_; } +const class attribute* constraint::attribute() const { + if (std::holds_alternative(attr_)) { + return std::get(attr_); + } + + return nullptr; +} + +std::string constraint::column_name() const { + if (std::holds_alternative(attr_)) { + return std::get(attr_)->name(); + } + if (std::holds_alternative(attr_)) { + return std::get(attr_); + } + + return ""; +} + +const object* constraint::owner() const { + return owner_; +} + +bool constraint::is_primary_key_constraint() const { + return utils::is_constraint_set(options_, utils::constraints::PrimaryKey); +} + +bool constraint::is_foreign_key_constraint() const { + return utils::is_constraint_set(options_, utils::constraints::ForeignKey); +} + +bool constraint::is_unique_constraint() const { + return utils::is_constraint_set(options_, utils::constraints::Unique); +} + +const std::string& constraint::ref_table_name() const { + return ref_table_name_; +} + +const std::string& constraint::ref_column_name() const { + return ref_column_name_; +} + constraint_builder & constraint_builder::constraint(std::string name) { constraint_name = std::move(name); return *this; @@ -35,10 +79,11 @@ constraint_builder & constraint_builder::references(std::string table, std::stri constraint_builder::operator class constraint() const { class constraint c; c.name_ = constraint_name; + c.attr_ = column_name; c.options_ = options_; c.ref_column_name_ = ref_column_name; c.ref_table_name_ = ref_table_name; - return {}; + return c; } constraint_builder constraint(std::string name) { diff --git a/source/core/object/repository_node.cpp b/source/core/object/repository_node.cpp index 3c2d332..3e252d0 100644 --- a/source/core/object/repository_node.cpp +++ b/source/core/object/repository_node.cpp @@ -110,7 +110,7 @@ attribute* repository_node::determine_reference_column(const std::type_index& ti repository& repo) { const auto it = repo.missing_references_.find(ti); if (it == repo.missing_references_.end()) { - return new attribute(pk_info.pk_column_name, pk_info.type, {utils::constraints::ForeignKey}, null_option_type::NOT_NULL); + return new attribute(pk_info.pk_column_name, pk_info.type, {utils::constraints::ForeignKey}, null_option_type::NotNull); } auto ref_column = it->second; diff --git a/source/orm/orm/schema.cpp b/source/orm/orm/schema.cpp index d319d02..51506b5 100644 --- a/source/orm/orm/schema.cpp +++ b/source/orm/orm/schema.cpp @@ -57,7 +57,9 @@ matador::utils::result matador::orm::schema::create // std::cout << result.sql << std::endl; for (const auto &node: repo_) { auto ctx = query::query::create() - .table(node->name(), node->info().attributes()) + .table(node->name()) + .columns(node->info().attributes()) + .constraints(node->info().constraints()) .compile(*c); for ( const auto& [sql, command] : ctx.additional_commands ) { diff --git a/source/orm/orm/session.cpp b/source/orm/orm/session.cpp index cfd983b..4f436cf 100644 --- a/source/orm/orm/session.cpp +++ b/source/orm/orm/session.cpp @@ -52,7 +52,9 @@ utils::result session::create_schema() const { auto c = cache_.pool().acquire(); for (const auto &node: *schema_) { auto ctx = query::query::create() - .table(node->name(), node->info().attributes()) + .table(node->name()) + .columns(node->info().attributes()) + .constraints(node->info().constraints()) .compile(*c); for ( const auto& [sql, command] : ctx.additional_commands ) { diff --git a/source/orm/query/intermediates/fetchable_query.cpp b/source/orm/query/intermediates/fetchable_query.cpp index ac9e696..420c486 100644 --- a/source/orm/query/intermediates/fetchable_query.cpp +++ b/source/orm/query/intermediates/fetchable_query.cpp @@ -9,13 +9,14 @@ namespace matador::query { namespace detail { sql::record *create_prototype(const std::vector &prototype) { auto result = std::make_unique(); + int index{0}; for (const auto &col: prototype) { result->append({ col.name(), col.type(), col.attributes().options(), col.attributes().size(), - col.index() + index++ }); } return result.release(); diff --git a/source/orm/query/intermediates/query_create_intermediate.cpp b/source/orm/query/intermediates/query_create_intermediate.cpp index 366723d..0001efc 100644 --- a/source/orm/query/intermediates/query_create_intermediate.cpp +++ b/source/orm/query/intermediates/query_create_intermediate.cpp @@ -8,12 +8,8 @@ query_create_intermediate::query_create_intermediate() { context_->parts.push_back(std::make_unique()); } -executable_query query_create_intermediate::table(const class table &tab, const std::initializer_list columns) { - return this->table(tab, std::vector{columns}); -} - -executable_query query_create_intermediate::table(const class table &tab, const std::vector &columns) { - context_->parts.push_back(std::make_unique(tab, columns)); +query_create_table_intermediate query_create_intermediate::table(const class table &tab) { + context_->parts.push_back(std::make_unique(tab)); return {context_}; } @@ -21,4 +17,24 @@ executable_query query_create_intermediate::schema( const std::string& schema_na context_->parts.push_back(std::make_unique(schema_name)); return {context_}; } + +executable_query query_create_table_columns_intermediate::constraints( std::initializer_list constraints ) { + return this->constraints(std::list(constraints)); +} + +executable_query query_create_table_columns_intermediate::constraints( const std::list& constraints ) { + + context_->parts.push_back(std::make_unique(constraints)); + return {context_}; +} + +query_create_table_columns_intermediate query_create_table_intermediate::columns( std::initializer_list columns ) { + context_->parts.push_back(std::make_unique(columns)); + return {context_}; +} + +query_create_table_columns_intermediate query_create_table_intermediate::columns( const std::list& columns ) { + context_->parts.push_back(std::make_unique(columns)); + return {context_}; +} } diff --git a/source/orm/query/internal/query_parts.cpp b/source/orm/query/internal/query_parts.cpp index 5322698..28023c5 100644 --- a/source/orm/query/internal/query_parts.cpp +++ b/source/orm/query/internal/query_parts.cpp @@ -344,23 +344,41 @@ void query_create_part::accept(query_part_visitor &visitor) visitor.visit(*this); } -query_create_table_part::query_create_table_part(class table tab, std::vector columns) +query_create_table_part::query_create_table_part(class table tab) : query_part(sql::dialect_token::Table) -, table_(std::move(tab)) -, columns_(std::move(columns)) {} +, table_(std::move(tab)) {} const table &query_create_table_part::table() const { return table_; } -const std::vector &query_create_table_part::columns() const +void query_create_table_part::accept(query_part_visitor &visitor) { + visitor.visit(*this); +} + +query_create_table_columns_part::query_create_table_columns_part(const std::list& columns) +: query_part( sql::dialect_token::Columns ) +, columns_(columns){} + +const std::list& query_create_table_columns_part::columns() const { return columns_; } -void query_create_table_part::accept(query_part_visitor &visitor) -{ +void query_create_table_columns_part::accept(query_part_visitor& visitor) { + visitor.visit(*this); +} + +query_create_table_constraints_part::query_create_table_constraints_part(const std::list& constraints) +: query_part( sql::dialect_token::Constraint ) +, constraints_(constraints) {} + +const std::list& query_create_table_constraints_part::constraints() const { + return constraints_; +} + +void query_create_table_constraints_part::accept( query_part_visitor& visitor ) { visitor.visit(*this); } diff --git a/source/orm/query/query_compiler.cpp b/source/orm/query/query_compiler.cpp index abc89c3..7f576b2 100644 --- a/source/orm/query/query_compiler.cpp +++ b/source/orm/query/query_compiler.cpp @@ -27,6 +27,7 @@ sql::query_context query_compiler::compile(const query_data &data, for (const auto &part: data.parts) { part->accept(*this); } + finisher_(query_); connection_ = std::nullopt; dialect_ = nullptr; data_ = nullptr; @@ -289,55 +290,55 @@ void query_compiler::visit(internal::query_create_part &/*create_part*/) query_.sql = dialect_->token_at(sql::dialect_token::Create); } -struct fk_context { - std::string column; - std::shared_ptr reference_column; -}; - -struct column_context -{ - std::vector primary_keys; - std::vector foreign_contexts; -}; - -std::string build_create_column(const object::attribute &col, const sql::dialect &d, column_context &context); +std::string build_create_column(const object::attribute &col, const sql::dialect &d); +std::string build_constraint(const class object::constraint &cons, const sql::dialect &d); void query_compiler::visit(internal::query_create_table_part &part) { - query_.sql += " " + dialect_->token_at(sql::dialect_token::Table) + " " + dialect_->prepare_identifier_string(part.table().name()) + " "; + query_.sql += " " + dialect_->token_at(sql::dialect_token::Table) + " " + dialect_->prepare_identifier_string(part.table().name()) + " ("; query_.table_name = part.table().name(); - std::string result = "("; + // if (!context.primary_keys.empty()) { + // result.append(", CONSTRAINT PK_" + part.table().name() + " PRIMARY KEY (" + utils::join(context.primary_keys, ", ") + ")"); + // } + // for (const auto &[column, reference_column]: context.foreign_contexts) { + // // ALTER TABLE Orders ADD CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) REFERENCES Persons(PersonID); + // std::string fk_cmd = "ALTER TABLE " + dialect_->prepare_identifier_string(query_.table_name) + " ADD"; + // fk_cmd += " CONSTRAINT FK_" + query_.table_name; + // fk_cmd += "_" + column; + // fk_cmd += " FOREIGN KEY (" + dialect_->prepare_identifier_string(column) + ")"; + // fk_cmd += " REFERENCES " + reference_column->table_name() + "(" + reference_column->name() + ")"; + // query_.additional_commands.push_back({fk_cmd, sql::sql_command::SQL_ALTER_TABLE}); + // } - column_context context; + finisher_ = [](sql::query_context &ctx) { ctx.sql += ")"; }; +} + +void query_compiler::visit(internal::query_create_table_columns_part& part) { + std::string result = "("; if (part.columns().size() < 2) { for (const auto &col: part.columns()) { - result.append(build_create_column(col, *dialect_, context)); + result.append(build_create_column(col, *dialect_)); } } else { auto it = part.columns().begin(); - result.append(build_create_column(*it++, *dialect_, context)); + result.append(build_create_column(*it++, *dialect_)); for (; it != part.columns().end(); ++it) { result.append(", "); - result.append(build_create_column(*it, *dialect_, context)); + result.append(build_create_column(*it, *dialect_)); } } - if (!context.primary_keys.empty()) { - result.append(", CONSTRAINT PK_" + part.table().name() + " PRIMARY KEY (" + utils::join(context.primary_keys, ", ") + ")"); - } - for (const auto &[column, reference_column]: context.foreign_contexts) { - // ALTER TABLE Orders ADD CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) REFERENCES Persons(PersonID); - std::string fk_cmd = "ALTER TABLE " + dialect_->prepare_identifier_string(query_.table_name) + " ADD"; - fk_cmd += " CONSTRAINT FK_" + query_.table_name; - fk_cmd += "_" + column; - fk_cmd += " FOREIGN KEY (" + dialect_->prepare_identifier_string(column) + ")"; - fk_cmd += " REFERENCES " + reference_column->table_name() + "(" + reference_column->name() + ")"; - query_.additional_commands.push_back({fk_cmd, sql::sql_command::SQL_ALTER_TABLE}); - } + query_.sql += result; +} - result += ")"; +void query_compiler::visit(internal::query_create_table_constraints_part& part) { + std::string result; + for (const auto& c : part.constraints()) { + result.append(", "); + result.append(build_constraint(c, *dialect_)); + } query_.sql += result; } @@ -387,7 +388,7 @@ void query_compiler::visit(internal::query_drop_table_part &part) query_.sql += " " + build_table_name(part.token(), *dialect_, query_.table_name); } -std::string build_create_column(const object::attribute &col, const sql::dialect &d, column_context &context) +std::string build_create_column(const object::attribute &col, const sql::dialect &d) { std::string result = d.prepare_identifier_string(col.name()) + " " + d.data_type_at(col.type()); if (col.attributes().size() > 0) { @@ -399,16 +400,26 @@ std::string build_create_column(const object::attribute &col, const sql::dialect if (is_constraint_set(col.attributes().options(), utils::constraints::Unique)) { result.append(" UNIQUE"); } - if (is_constraint_set(col.attributes().options(), utils::constraints::PrimaryKey)) { - context.primary_keys.emplace_back(col.name()); - } - if (is_constraint_set(col.attributes().options(), utils::constraints::ForeignKey)) { - context.foreign_contexts.push_back({col.name(), col.reference_column()}); - } return result; } +std::string build_constraint(const class object::constraint& cons, const sql::dialect& d) { + std::string result; + if (!cons.name().empty()) { + result.append(d.constraint()).append(" ").append(d.prepare_identifier_string(cons.name())).append(" "); + } + if (cons.is_primary_key_constraint()) { + result.append(d.primary_key()); + } else if (cons.is_foreign_key_constraint()) { + result.append(d.foreign_key()); + } else { + // handle error + } + result.append("(").append(cons.attribute()->full_name()).append(")"); + return result; +} + std::string query_compiler::build_table_name(const sql::dialect_token token, const sql::dialect &d, const table& t) { return d.token_at(token) + " " + diff --git a/source/orm/sql/dialect.cpp b/source/orm/sql/dialect.cpp index 65dc728..970a890 100644 --- a/source/orm/sql/dialect.cpp +++ b/source/orm/sql/dialect.cpp @@ -172,6 +172,10 @@ const std::string& dialect::commit() const { return token_at(dialect_token::Commit); } +const std::string& dialect::constraint() const { + return token_at(dialect_token::Constraint); +} + const std::string& dialect::create() const { return token_at(dialect_token::Create); } diff --git a/source/orm/sql/query_result.cpp b/source/orm/sql/query_result.cpp index 122ff6e..8b8839f 100644 --- a/source/orm/sql/query_result.cpp +++ b/source/orm/sql/query_result.cpp @@ -8,13 +8,14 @@ namespace matador::sql::detail { template<> record *create_prototype(const std::vector &prototype) { auto result = std::make_unique(); + int index{0}; for (const auto &col: prototype) { result->append({ col.name(), col.type(), col.attributes().options(), col.attributes().size(), - col.index() + index++ }); } return result.release(); diff --git a/test/core/object/AttributeGeneratorTest.cpp b/test/core/object/AttributeGeneratorTest.cpp index a476faa..bb7ee38 100644 --- a/test/core/object/AttributeGeneratorTest.cpp +++ b/test/core/object/AttributeGeneratorTest.cpp @@ -22,15 +22,15 @@ TEST_CASE("Generate column definitions from object", "[column][definition][gener auto columns = attribute_generator::generate(repo, obj); const std::vector expected_columns = { - attribute{"product_name", basic_type::type_varchar, constraints::PrimaryKey, null_option_type::NOT_NULL }, - attribute{"supplier_id", basic_type::type_uint32, constraints::ForeignKey, null_option_type::NOT_NULL }, - attribute{"category_id", basic_type::type_uint32, constraints::ForeignKey, null_option_type::NOT_NULL }, - attribute{"quantity_per_unit", basic_type::type_varchar, null_attributes, null_option_type::NOT_NULL }, - attribute{"unit_price", basic_type::type_uint32, null_attributes, null_option_type::NOT_NULL }, - attribute{"units_in_stock", basic_type::type_uint32, null_attributes, null_option_type::NOT_NULL }, - attribute{"units_in_order", basic_type::type_uint32, null_attributes, null_option_type::NOT_NULL }, - attribute{"reorder_level", basic_type::type_uint32, null_attributes, null_option_type::NOT_NULL }, - attribute{"discontinued", basic_type::type_bool, null_attributes, null_option_type::NOT_NULL } + attribute{"product_name", basic_type::type_varchar, constraints::PrimaryKey, null_option_type::NotNull }, + attribute{"supplier_id", basic_type::type_uint32, constraints::ForeignKey, null_option_type::NotNull }, + attribute{"category_id", basic_type::type_uint32, constraints::ForeignKey, null_option_type::NotNull }, + attribute{"quantity_per_unit", basic_type::type_varchar, null_attributes, null_option_type::NotNull }, + attribute{"unit_price", basic_type::type_uint32, null_attributes, null_option_type::NotNull }, + attribute{"units_in_stock", basic_type::type_uint32, null_attributes, null_option_type::NotNull }, + attribute{"units_in_order", basic_type::type_uint32, null_attributes, null_option_type::NotNull }, + attribute{"reorder_level", basic_type::type_uint32, null_attributes, null_option_type::NotNull }, + attribute{"discontinued", basic_type::type_bool, null_attributes, null_option_type::NotNull } }; REQUIRE(!columns.empty()); REQUIRE(columns.size() == expected_columns.size()); @@ -49,9 +49,9 @@ TEST_CASE("Generate columns from object with nullable columns", "[column generat auto columns = attribute_generator::generate(repo, obj); const std::vector expected_columns = { - attribute{"id", basic_type::type_uint32, constraints::PrimaryKey, null_option_type::NOT_NULL }, - attribute{"name", basic_type::type_varchar, null_attributes, null_option_type::NOT_NULL }, - attribute{"age", basic_type::type_uint32, null_attributes, null_option_type::NOT_NULL } + attribute{"id", basic_type::type_uint32, constraints::PrimaryKey, null_option_type::NotNull }, + attribute{"name", basic_type::type_varchar, null_attributes, null_option_type::NotNull }, + attribute{"age", basic_type::type_uint32, null_attributes, null_option_type::NotNull } }; REQUIRE(!columns.empty()); REQUIRE(columns.size() == expected_columns.size()); diff --git a/test/orm/query/QueryBuilderTest.cpp b/test/orm/query/QueryBuilderTest.cpp index b8b2b7e..2034e50 100644 --- a/test/orm/query/QueryBuilderTest.cpp +++ b/test/orm/query/QueryBuilderTest.cpp @@ -6,8 +6,6 @@ #include #include "matador/query/table.hpp" -#include "matador/object/attribute_generator.hpp" - #include "matador/sql/connection.hpp" #include "matador/utils/placeholder.hpp" @@ -37,21 +35,32 @@ TEST_CASE_METHOD(QueryFixture, "Test alter table sql statement", "[query][alter] TEST_CASE_METHOD(QueryFixture, "Test create table sql statement string", "[query]") { auto result = query::create() - .table({"person"}, { - make_pk_column("id"), - make_column("name", 255), - make_column("age") - }).str(*db); + .table({"person"}) + .columns({ + attribute("id", basic_type::type_uint32), + attribute("name", basic_type::type_varchar, 255), + attribute("age", basic_type::type_uint16) + }) + .constraints({ + constraint("PK_person").primary_key({"id"}) + }) + .str(*db); REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); auto ctx = query::create() - .table("person", { - make_pk_column("id"), - make_column("name", {255, constraints::Unique}, null_option_type::NOT_NULL), - make_column("age"), - make_fk_column("address", "address", "id") - }).compile(*db); + .table("person") + .columns({ + attribute("id", basic_type::type_uint32), + attribute("name", basic_type::type_varchar, 255), + attribute("age", basic_type::type_uint16), + attribute("address", basic_type::type_uint32) + }) + .constraints({ + constraint("PK_person").primary_key({"id"}), + constraint("FK_person_address").foreign_key({"address"}).references("address", {"id"}) + }) + .compile(*db); REQUIRE(ctx.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); REQUIRE(ctx.additional_commands.size() == 1); @@ -210,10 +219,14 @@ TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with offset and TEST_CASE_METHOD(QueryFixture, "Test create, insert and select a blob column", "[query][blob]") { auto result = query::create() - .table("person", { - make_pk_column("id"), - make_column("name", 255), - make_column("data") + .table("person") + .columns({ + attribute("id", basic_type::type_uint32), + attribute("name", basic_type::type_varchar, 255), + attribute("data", basic_type::type_blob) + }) + .constraints({ + constraint("PK_person").primary_key({"id"}) }) .str(*db); diff --git a/test/orm/sql/ColumnTest.cpp b/test/orm/sql/ColumnTest.cpp index ecd9457..264e975 100644 --- a/test/orm/sql/ColumnTest.cpp +++ b/test/orm/sql/ColumnTest.cpp @@ -9,9 +9,7 @@ TEST_CASE("Test create empty column", "[column]") { attribute c("name"); REQUIRE(c.name() == "name"); - REQUIRE(c.index() == -1); REQUIRE(c.type() == basic_type::type_null); - REQUIRE(!c.reference_column()); c.change_type(255); REQUIRE(c.type() == basic_type::type_varchar); @@ -24,39 +22,39 @@ TEST_CASE("Test copy and move column", "[column]") { attribute c( "name", basic_type::type_varchar, - 2, - std::make_shared("author", basic_type::type_uint32, "books", attribute_options{constraints::ForeignKey}), + // 2, + // std::make_shared("author", basic_type::type_uint32, "books", attribute_options{constraints::ForeignKey}), {255, constraints::ForeignKey}, - null_option_type::NOT_NULL + null_option_type::NotNull ); REQUIRE(c.name() == "name"); - REQUIRE(c.index() == 2); - REQUIRE(c.reference_column()); - REQUIRE(c.reference_column()->name() == "author"); - REQUIRE(c.reference_column()->table_name() == "books"); + // REQUIRE(c.index() == 2); + // REQUIRE(c.reference_column()); + // REQUIRE(c.reference_column()->name() == "author"); + // REQUIRE(c.reference_column()->table_name() == "books"); REQUIRE(c.type() == basic_type::type_varchar); REQUIRE(c.attributes().size() == 255); auto c2 = c; REQUIRE(c2.name() == "name"); - REQUIRE(c2.index() == 2); - REQUIRE(c2.reference_column()); - REQUIRE(c2.reference_column()->name() == "author"); - REQUIRE(c2.reference_column()->table_name() == "books"); + // REQUIRE(c2.index() == 2); + // REQUIRE(c2.reference_column()); + // REQUIRE(c2.reference_column()->name() == "author"); + // REQUIRE(c2.reference_column()->table_name() == "books"); REQUIRE(c2.type() == basic_type::type_varchar); REQUIRE(c2.attributes().size() == 255); auto c3 = std::move(c2); REQUIRE(c3.name() == "name"); - REQUIRE(c3.index() == 2); - REQUIRE(c3.reference_column()); - REQUIRE(c3.reference_column()->name() == "author"); - REQUIRE(c3.reference_column()->table_name() == "books"); + // REQUIRE(c3.index() == 2); + // REQUIRE(c3.reference_column()); + // REQUIRE(c3.reference_column()->name() == "author"); + // REQUIRE(c3.reference_column()->table_name() == "books"); REQUIRE(c3.type() == basic_type::type_varchar); REQUIRE(c3.attributes().size() == 255); REQUIRE(c2.name().empty()); - REQUIRE(c2.index() == 2); - REQUIRE(!c2.reference_column()); + // REQUIRE(c2.index() == 2); + // REQUIRE(!c2.reference_column()); REQUIRE(c2.attributes().size() == 255); } \ No newline at end of file