diff --git a/backends/postgres/include/postgres_connection.hpp b/backends/postgres/include/postgres_connection.hpp index 43da962..f8c688c 100644 --- a/backends/postgres/include/postgres_connection.hpp +++ b/backends/postgres/include/postgres_connection.hpp @@ -35,7 +35,7 @@ public: utils::result, utils::error> prepare(const sql::query_context &context) override; utils::result, utils::error> fetch(const sql::query_context &context) override; - utils::result, utils::error> describe(const std::string& table) override; + utils::result, utils::error> describe(const std::string& table) override; utils::result exists(const std::string &schema_name, const std::string &table_name) override; [[nodiscard]] std::string to_escaped_string( const utils::blob& value ) const override; diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index a412411..1985bc5 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -85,7 +85,7 @@ utils::result, utils::error> postgres_co return utils::failure(make_error(sql::error_code::FETCH_FAILED, res, conn_, "Failed to fetch", context.sql)); } -// std::vector prototype; +// std::vector prototype; // const auto num_col = PQnfields(res); // for (int i = 0; i < num_col; ++i) { // const char *col_name = PQfname(res, i); @@ -170,7 +170,7 @@ utils::basic_type string2type(const char *type) { } } -utils::result, utils::error> postgres_connection::describe(const std::string &table) { +utils::result, utils::error> postgres_connection::describe(const std::string &table) { const std::string stmt( "SELECT ordinal_position, column_name, udt_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema='public' AND table_name='" + table + "'"); @@ -182,7 +182,7 @@ utils::result, utils::error> postgres_connec } postgres_result_reader reader(res); - std::vector prototype; + std::vector prototype; while (auto fetched = reader.fetch()) { if (!fetched.is_ok()) { return utils::failure(fetched.release_error()); diff --git a/include/matador/sql/column_definition.hpp b/include/matador/object/attribute_definition.hpp similarity index 54% rename from include/matador/sql/column_definition.hpp rename to include/matador/object/attribute_definition.hpp index 657a4d5..04352e5 100644 --- a/include/matador/sql/column_definition.hpp +++ b/include/matador/object/attribute_definition.hpp @@ -9,45 +9,45 @@ #include -namespace matador::sql { +namespace matador::object { enum class null_option : uint8_t { NULLABLE, NOT_NULL }; -class column_definition { +class attribute_definition { public: - explicit column_definition(const char *name); // NOLINT(*-explicit-constructor) - explicit column_definition(std::string name); // NOLINT(*-explicit-constructor) + explicit attribute_definition(const char *name); // NOLINT(*-explicit-constructor) + explicit attribute_definition(std::string name); // NOLINT(*-explicit-constructor) - column_definition(const column_definition&) = default; - column_definition& operator=(const column_definition&) = default; - column_definition(column_definition&&) noexcept = default; - column_definition& operator=(column_definition&&) noexcept = default; + attribute_definition(const attribute_definition&) = default; + attribute_definition& operator=(const attribute_definition&) = default; + attribute_definition(attribute_definition&&) noexcept = default; + attribute_definition& operator=(attribute_definition&&) noexcept = default; template - explicit column_definition(std::string name, const utils::field_attributes& attr) - : column_definition(std::move(name), utils::data_type_traits::type(attr.size()), attr) + explicit attribute_definition(std::string name, const utils::field_attributes& attr) + : attribute_definition(std::move(name), utils::data_type_traits::type(attr.size()), attr) {} template - column_definition(std::string name, const Type &, const utils::field_attributes& attr, null_option null_opt) - : column_definition(std::move(name), utils::data_type_traits::type(attr.size()), attr, null_opt) + attribute_definition(std::string name, const Type &, const utils::field_attributes& attr, null_option null_opt) + : attribute_definition(std::move(name), utils::data_type_traits::type(attr.size()), attr, null_opt) {} template - column_definition(std::string name, const char (&)[SIZE], const utils::field_attributes& attr, const null_option null_opt) - : column_definition(std::move(name), utils::data_type_traits::type(attr.size()), attr, null_opt) + attribute_definition(std::string name, const char (&)[SIZE], const utils::field_attributes& attr, const null_option null_opt) + : attribute_definition(std::move(name), utils::data_type_traits::type(attr.size()), attr, null_opt) {} - column_definition(std::string name, utils::basic_type type, const utils::field_attributes&, null_option null_opt, size_t index = 0); + attribute_definition(std::string name, utils::basic_type type, const utils::field_attributes&, null_option null_opt, size_t index = 0); template - column_definition(std::string name, std::string ref_table, std::string ref_column, const utils::field_attributes& attr, null_option null_opt) - : column_definition(std::move(name), utils::data_type_traits::type(attr.size()), ref_table, ref_column, attr, null_opt) + attribute_definition(std::string name, std::string ref_table, std::string ref_column, const utils::field_attributes& attr, null_option null_opt) + : attribute_definition(std::move(name), utils::data_type_traits::type(attr.size()), ref_table, ref_column, attr, null_opt) {} - column_definition(std::string name, utils::basic_type type, size_t index, std::string ref_table, std::string ref_column, const utils::field_attributes& attr, null_option null_opt); + attribute_definition(std::string name, utils::basic_type type, size_t index, std::string ref_table, std::string ref_column, const utils::field_attributes& attr, null_option null_opt); [[nodiscard]] const std::string& name() const; [[nodiscard]] std::string full_name() const; @@ -104,7 +104,7 @@ public: return value_.as(); } - friend std::ostream& operator<<(std::ostream &out, const column_definition &col); + friend std::ostream& operator<<(std::ostream &out, const attribute_definition &col); private: template @@ -136,39 +136,39 @@ private: * @param null_opt * @return A column object with given name */ -column_definition make_column(const std::string &name, utils::basic_type type, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL); +attribute_definition make_column(const std::string &name, utils::basic_type type, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL); template < typename Type > -column_definition make_column(const std::string &name, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL) +attribute_definition make_column(const std::string &name, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL) { return make_column(name, utils::data_type_traits::type(0), attr, null_opt); } template <> -column_definition make_column(const std::string &name, utils::field_attributes attr, null_option null_opt); +attribute_definition make_column(const std::string &name, utils::field_attributes attr, null_option null_opt); template < typename Type > -column_definition make_pk_column(const std::string &name, size_t size = 0) +attribute_definition make_pk_column(const std::string &name, size_t size = 0) { return make_column(name, { size, utils::constraints::PRIMARY_KEY }); } template <> -column_definition make_pk_column(const std::string &name, size_t size); +attribute_definition make_pk_column(const std::string &name, size_t size); template < typename Type > -column_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column) +attribute_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column) { return {name, utils::data_type_traits::type(size), ref_table, ref_column, { size, utils::constraints::FOREIGN_KEY }}; } template < typename Type > -[[maybe_unused]] column_definition make_fk_column(const std::string &name, const std::string &ref_table, const std::string &ref_column) +[[maybe_unused]] attribute_definition make_fk_column(const std::string &name, const std::string &ref_table, const std::string &ref_column) { return {name, utils::data_type_traits::type(0), 0, ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }, null_option::NOT_NULL}; } template <> -column_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column); +attribute_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column); } #endif //QUERY_COLUMN_DEFINITION_HPP diff --git a/include/matador/object/attribute_definition_generator.hpp b/include/matador/object/attribute_definition_generator.hpp new file mode 100644 index 0000000..d8450e3 --- /dev/null +++ b/include/matador/object/attribute_definition_generator.hpp @@ -0,0 +1,132 @@ +#ifndef QUERY_COLUMN_DEFINITION_GENERATOR_HPP +#define QUERY_COLUMN_DEFINITION_GENERATOR_HPP + +#include "attribute_definition.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/data_type_traits.hpp" +#include "matador/utils/field_attributes.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include +#include + +namespace matador::object { + +class schema; + +class fk_attribute_generator +{ +public: + fk_attribute_generator() = default; + + template + attribute_definition generate(const char *id, Type &x, const std::string &ref_table, const std::string &ref_column) + { + access::process(*this, x); + return attribute_definition{id, type_, 0, ref_table, ref_column, {utils::constraints::FOREIGN_KEY }, null_option::NOT_NULL}; + } + + template + void on_primary_key(const char *, ValueType &/*pk*/, std::enable_if_t && !std::is_same_v>* = nullptr) + { + type_ = utils::data_type_traits::type(0); + } + void on_primary_key(const char * /*id*/, std::string &/*pk*/, size_t size); + void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {} + template < class Type > + void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + void on_attribute(const char * /*id*/, char * /*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + template + void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {} + template + void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {} + template + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} + +private: + utils::basic_type type_{}; +}; + +class attribute_definition_generator final { +private: + attribute_definition_generator(std::vector &columns, const schema &repo); + +public: + ~attribute_definition_generator() = default; + + template < class Type > + static std::vector generate(const schema &repo) + { + std::vector columns; + attribute_definition_generator gen(columns, repo); + Type obj; + access::process(gen, obj); + return std::move(columns); + } + + template < class V > + void on_primary_key(const char *, V &x, std::enable_if_t && !std::is_same_v>* = nullptr); + void on_primary_key(const char *id, std::string &pk, size_t size); + void on_revision(const char *id, unsigned long long &rev); + + template + void on_attribute(const char *id, Type &x, const utils::field_attributes &attr = utils::null_attributes); + + template + void on_attribute(const char *id, std::optional &x, const utils::field_attributes &attr = utils::null_attributes); + + template + void on_belongs_to(const char *id, Pointer &x, const utils::foreign_attributes &/*attr*/) + { + const auto [ref_table, ref_column] = determine_foreign_ref(std::type_index(typeid(typename Pointer::value_type))); + columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column)); + } + template + void on_has_one(const char *id, Pointer &x, const utils::foreign_attributes &/*attr*/) + { + const auto [ref_table, ref_column] = determine_foreign_ref(std::type_index(typeid(typename Pointer::value_type))); + columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column)); + } + template + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {} + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} + +private: + std::pair determine_foreign_ref(const std::type_index &ti); + +private: + size_t index_ = 0; + std::vector &columns_; + const schema &repo_; + + fk_attribute_generator fk_column_generator_; +}; + +template +void attribute_definition_generator::on_primary_key(const char *id, V &x, std::enable_if_t && !std::is_same_v>*) +{ + on_attribute(id, x, { utils::constraints::PRIMARY_KEY }); +} + +template +void attribute_definition_generator::on_attribute(const char *id, Type &x, const utils::field_attributes &attr) +{ + columns_.emplace_back(id, x, attr, null_option::NOT_NULL); +} + +template +void attribute_definition_generator::on_attribute(const char *id, std::optional &x, const utils::field_attributes &attr) +{ + columns_.emplace_back(id, utils::data_type_traits::type(attr.size()), attr, null_option::NULLABLE); +} + +} +#endif //QUERY_COLUMN_DEFINITION_GENERATOR_HPP diff --git a/include/matador/object/basic_object_info.hpp b/include/matador/object/basic_object_info.hpp index 8301d96..8c4f8ad 100644 --- a/include/matador/object/basic_object_info.hpp +++ b/include/matador/object/basic_object_info.hpp @@ -1,6 +1,8 @@ #ifndef BASIC_PROTOTYPE_INFO_HPP #define BASIC_PROTOTYPE_INFO_HPP +#include "matador/object/object_definition.hpp" + #include #include @@ -14,35 +16,18 @@ public: [[nodiscard]] std::type_index type_index() const; [[nodiscard]] std::string name() const; + [[nodiscard]] const object_definition& definition() const; protected: - basic_object_info(schema_node &node, std::type_index type_index); + basic_object_info(schema_node &node, std::type_index type_index, object_definition &&definition); protected: schema_node &node_; /**< prototype node of the represented object type */ std::type_index type_index_; /**< type index of the represented object type */ + object_definition definition_; }; -template -class object_info final : public basic_object_info { -public: - explicit object_info(schema_node &node) - : basic_object_info(node, typeid(Type)) {} - - const Type &prototype() const { return prototype_; } - -private: - Type prototype_; -}; - -template -using object_info_ref = std::reference_wrapper>; - -namespace detail { -struct null_type {}; -} - -using null_info = object_info; +using basic_object_info_ref = std::reference_wrapper; } diff --git a/include/matador/object/many_to_many_relation.hpp b/include/matador/object/many_to_many_relation.hpp new file mode 100644 index 0000000..ecdd360 --- /dev/null +++ b/include/matador/object/many_to_many_relation.hpp @@ -0,0 +1,37 @@ +#ifndef QUERY_HAS_MANY_TO_MANY_RELATION_HPP +#define QUERY_HAS_MANY_TO_MANY_RELATION_HPP + +#include "matador/object/object_ptr.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +namespace matador::object { + +template < class LocalType, class ForeignType > +class many_to_many_relation { +public: + many_to_many_relation() = default; + many_to_many_relation(std::string local_name, std::string remote_name) + : local_name_(std::move(local_name)) + , remote_name_(std::move(remote_name)) {} + + template + void process(Operator &op) { + namespace field = matador::access; + field::belongs_to(op, local_name_.c_str(), local_, utils::default_foreign_attributes); + field::belongs_to(op, remote_name_.c_str(), remote_, utils::default_foreign_attributes); + } + + object_ptr local() const { return local_; } + object_ptr remote() const { return remote_; } + +private: + std::string local_name_; + std::string remote_name_; + object_ptr local_; + object_ptr remote_; +}; + +} +#endif //QUERY_HAS_MANY_TO_MANY_RELATION_HPP diff --git a/include/matador/object/object_definition.hpp b/include/matador/object/object_definition.hpp new file mode 100644 index 0000000..c8f9aa7 --- /dev/null +++ b/include/matador/object/object_definition.hpp @@ -0,0 +1,70 @@ +#ifndef QUERY_TABLE_DEFINITION_HPP +#define QUERY_TABLE_DEFINITION_HPP + +#include "matador/object/attribute_definition.hpp" + +#include + +namespace matador::object { + +class object_definition final { +private: + using column_by_index = std::vector; + using column_index_pair = std::pair, column_by_index::difference_type>; + using column_by_name_map = std::unordered_map; + +public: + using iterator = column_by_index::iterator; + using const_iterator = column_by_index::const_iterator; + + object_definition() = default; + object_definition(std::initializer_list columns); + explicit object_definition(const std::vector &columns); + object_definition(const object_definition &x); + object_definition& operator=(const object_definition &x); + object_definition(object_definition&&) noexcept = default; + object_definition& operator=(object_definition&&) noexcept = default; + ~object_definition() = default; + + [[nodiscard]] bool has_primary_key() const; + [[nodiscard]] std::optional primary_key() const; + + template < typename Type > + void append(const std::string &name, long size = -1) { + append(make_column(name, size)); + } + void append(attribute_definition col); + + [[nodiscard]] const std::vector& columns() const; + + [[nodiscard]] const attribute_definition& at(const std::string &name) const; + [[nodiscard]] const attribute_definition& at(size_t index) const; + + iterator find(const std::string &column_name); + [[nodiscard]] const_iterator find(const std::string &column_name) const; + + iterator begin(); + [[nodiscard]] const_iterator begin() const; + [[nodiscard]] const_iterator cbegin() const; + + iterator end(); + [[nodiscard]] const_iterator end() const; + [[nodiscard]] const_iterator cend() const; + + [[nodiscard]] size_t size() const; + [[nodiscard]] bool empty() const; + void clear(); + +private: + void init(); + void add_to_map(attribute_definition &col, size_t index); + +private: + column_by_index columns_; + column_by_name_map columns_by_name_; + + int pk_index_{-1}; +}; + +} +#endif //QUERY_TABLE_DEFINITION_HPP diff --git a/include/matador/object/object_info.hpp b/include/matador/object/object_info.hpp new file mode 100644 index 0000000..f8aeff0 --- /dev/null +++ b/include/matador/object/object_info.hpp @@ -0,0 +1,34 @@ +#ifndef OBJECT_INFO_HPP +#define OBJECT_INFO_HPP + +#include "matador/object/basic_object_info.hpp" +#include "matador/object/object_definition.hpp" + +namespace matador::object { + +class schema_node; + +template +class object_info final : public basic_object_info { +public: + explicit object_info(schema_node &node, object_definition &&definition) + : basic_object_info(node, typeid(Type), std::move(definition)) {} + + const Type &prototype() const { return prototype_; } + +private: + Type prototype_; +}; + +template +using object_info_ref = std::reference_wrapper>; + +namespace detail { +struct null_type {}; +} + +using null_info = object_info; + +} + +#endif //OBJECT_INFO_HPP diff --git a/include/matador/object/object_ptr.hpp b/include/matador/object/object_ptr.hpp index 6763deb..15de234 100644 --- a/include/matador/object/object_ptr.hpp +++ b/include/matador/object/object_ptr.hpp @@ -5,6 +5,7 @@ namespace matador::object { template class object_ptr { public: + using value_type = Type; Type* operator->() { return ptr; }; Type& operator*() { return *ptr; }; diff --git a/include/matador/object/schema.hpp b/include/matador/object/schema.hpp index 086f730..e64aeb3 100644 --- a/include/matador/object/schema.hpp +++ b/include/matador/object/schema.hpp @@ -93,6 +93,18 @@ public: return utils::ok(result.value()->info()); } + template + [[nodiscard]] utils::result basic_info() const { + auto result = find_node(std::type_index(typeid(Type))); + if (!result) { + return utils::failure(result.err()); + } + + return utils::ok(basic_object_info_ref{result.value()->basic_info()}); + } + + [[nodiscard]] utils::result, utils::error> reference(const std::type_index &type_index) const; + private: using node_ptr = std::shared_ptr; using t_node_map = std::unordered_map; diff --git a/include/matador/object/schema_node.hpp b/include/matador/object/schema_node.hpp index ae6f647..870ccc5 100644 --- a/include/matador/object/schema_node.hpp +++ b/include/matador/object/schema_node.hpp @@ -1,13 +1,14 @@ #ifndef SCHEMA_NODE_HPP #define SCHEMA_NODE_HPP -#include "matador/object/basic_object_info.hpp" +#include "matador/object/attribute_definition_generator.hpp" +#include "matador/object/object_info.hpp" #include -#include namespace matador::object { +class basic_object_info; class schema; class schema_node final { @@ -28,24 +29,11 @@ public: [[nodiscard]] std::string name() const; [[nodiscard]] std::type_index type_index() const; - /** - * Appends the given prototype node as a sibling - * on the same level. - * - * @param sibling The new sibling node. - */ - void append(const std::shared_ptr &sibling); - - /** - * Inserts the given node to the list of children. - * - * @param child The child node to add. - */ - void insert(const std::shared_ptr &child); - [[nodiscard]] node_ptr next() const; [[nodiscard]] node_ptr prev() const; + [[nodiscard]] const basic_object_info& basic_info() const; + template object_info_ref info() const { return std::ref(static_cast&>(*info_)); @@ -56,7 +44,7 @@ private: template < typename Type > schema_node(schema& tree, std::string name, Type *obj) : schema_(tree) - , info_(std::make_unique>(*this)) + , info_(std::make_unique>(*this, object_definition(attribute_definition_generator::generate(schema_)))) , first_child_(std::shared_ptr(new schema_node(tree))) , last_child_(std::shared_ptr(new schema_node(tree))) , name_(std::move(name)) { @@ -69,7 +57,7 @@ private: friend class schema; friend class const_schema_node_iterator; - schema &schema_; + object::schema &schema_; std::unique_ptr info_; std::shared_ptr parent_; diff --git a/include/matador/orm/session.hpp b/include/matador/orm/session.hpp index 86569ff..83b1b71 100644 --- a/include/matador/orm/session.hpp +++ b/include/matador/orm/session.hpp @@ -1,20 +1,21 @@ #ifndef QUERY_SESSION_HPP #define QUERY_SESSION_HPP +#include "matador/orm/session_query_builder.hpp" + +#include "matador/sql/column_generator.hpp" #include "matador/sql/connection.hpp" #include "matador/sql/connection_pool.hpp" -// #include "matador/sql/entity_query_builder.hpp" #include "matador/sql/statement.hpp" #include "matador/object/object_ptr.hpp" +#include "matador/object/object_definition.hpp" #include "matador/object/schema.hpp" #include namespace matador::orm { -class dialect; - enum class session_error { Ok = 0, NoConnectionAvailable, @@ -29,9 +30,9 @@ public: explicit session(sql::connection_pool &pool); template - void attach(const std::string &table_name); + [[nodiscard]] utils::result attach(const std::string &table_name); - void create_schema(); + utils::result create_schema() const; template object::object_ptr insert(Type *obj); @@ -52,7 +53,7 @@ public: return utils::failure(session_error::UnknownType); } - entity_query_builder eqb(*schema_); + session_query_builder eqb(*schema_); auto data = eqb.build(pk); if (!data.is_ok()) { return utils::failure(session_error::FailedToBuildQuery); @@ -77,7 +78,7 @@ public: return utils::failure(session_error::UnknownType); } - entity_query_builder eqb(*schema_); + session_query_builder eqb(*schema_); auto data = eqb.build(); if (!data.is_ok()) { return utils::failure(session_error::FailedToBuildQuery); @@ -97,7 +98,7 @@ public: return utils::failure(session_error::UnknownType); } - entity_query_builder eqb(*schema_); + session_query_builder eqb(*schema_); auto data = eqb.build(); if (!data.is_ok()) { return utils::failure(session_error::FailedToBuildQuery); @@ -110,12 +111,12 @@ public: void drop_table(); void drop_table(const std::string &table_name); - [[nodiscard]] sql::query_result fetch(const query_context &q) const; + [[nodiscard]] utils::result, utils::error> fetch(const sql::query_context &q) const; // [[nodiscard]] query_result fetch(const std::string &sql) const; [[nodiscard]] size_t execute(const std::string &sql) const; - [[nodiscard]] sql::statement prepare(query_context q) const; + [[nodiscard]] sql::statement prepare(const sql::query_context& q) const; - [[nodiscard]] std::vector describe_table(const std::string &table_name) const; + [[nodiscard]] std::vector describe_table(const std::string &table_name) const; [[nodiscard]] bool table_exists(const std::string &table_name) const; [[nodiscard]] const sql::dialect& dialect() const; @@ -123,22 +124,22 @@ public: private: friend class query_select; - [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; + // [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; - query_select build_select_query(sql::connection_ptr &conn, entity_query_data &&data) const; + static query::fetchable_query build_select_query(sql::connection_ptr &conn, entity_query_data &&data); private: sql::connection_pool &pool_; const sql::dialect &dialect_; std::unique_ptr schema_; - mutable std::unordered_map prototypes_; + mutable std::unordered_map prototypes_; }; template -void session::attach(const std::string &table_name) +[[nodiscard]] utils::result session::attach(const std::string &table_name) { - schema_->attach(table_name); + return schema_->attach(table_name); } template @@ -149,9 +150,9 @@ object::object_ptr session::insert(Type *obj) if (!info) { return {}; } - c->query(*schema_) - .insert() - .into(info->name, column_generator::generate(*schema_, true)) + + query::query::insert() + .into(info->name, sql::column_generator::generate(*schema_, true)) .values(*obj) .execute(); diff --git a/include/matador/orm/session_query_builder.hpp b/include/matador/orm/session_query_builder.hpp new file mode 100644 index 0000000..0ef2eaa --- /dev/null +++ b/include/matador/orm/session_query_builder.hpp @@ -0,0 +1,289 @@ +#ifndef QUERY_ENTITY_QUERY_BUILDER_HPP +#define QUERY_ENTITY_QUERY_BUILDER_HPP + +#include "matador/query/condition.hpp" +#include "matador/query/query.hpp" +#include "matador/query/query_intermediates.hpp" + +#include "matador/sql/connection.hpp" +#include "matador/sql/query_context.hpp" + +#include "matador/object/schema.hpp" + +#include "matador/utils/result.hpp" +#include "matador/utils/value.hpp" + +#include + +namespace matador::orm { + +struct join_columns +{ + std::string join_column; + std::string inverse_join_column; +}; + +class join_column_collector +{ +public: + template + join_columns collect() + { + join_columns_ = {}; + Type obj; + + matador::access::process(*this, obj); + + return join_columns_; + } + template < class V > + void on_primary_key(const char * /*id*/, V &, typename std::enable_if::value && !std::is_same::value>::type* = 0) {} + void on_primary_key(const char * /*id*/, std::string &, size_t) {} + void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {} + template + void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + template + void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} + template + void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {} + template + void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr) {} + template + void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) + { + join_columns_.join_column = join_column; + join_columns_.inverse_join_column = inverse_join_column; + } + template + void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {} + +private: + join_columns join_columns_; +}; + +struct entity_query_data { + std::string root_table_name; + std::string pk_column_; + std::vector columns; + std::vector joins; + std::unique_ptr where_clause; +}; + +enum class query_build_error : std::uint8_t { + Ok = 0, + UnknownType, + MissingPrimaryKey, + UnexpectedError +}; + +class query_builder_exception final : public std::exception +{ +public: + explicit query_builder_exception(const query_build_error error) : error_(error) {} + + [[nodiscard]] query_build_error error() const { return error_; } + +private: + const query_build_error error_; +}; + +class session_query_builder final +{ +public: + explicit session_query_builder(const object::schema &scm) + : schema_(scm) {} + + template + utils::result build(const PrimaryKeyType &pk) { + auto info = schema_.info(); + if (!info) { + return utils::failure(query_build_error::UnknownType); + } + pk_ = pk; + table_info_stack_.push(info.value()); + entity_query_data_ = { info.value().get().name() }; + try { + access::process(*this, info.value().get().prototype()); + + return {utils::ok(std::move(entity_query_data_))}; + } catch (const query_builder_exception &ex) { + return {utils::failure(ex.error())}; + } catch (...) { + return {utils::failure(query_build_error::UnexpectedError)}; + } + } + + template + utils::result build() { + const auto info = schema_.info(); + if (!info) { + return utils::failure(query_build_error::UnknownType); + } + pk_ = nullptr; + table_info_stack_.push(info.value()); + entity_query_data_ = { info->name() }; + try { + access::process(*this, info->prototype()); + + return {utils::ok(std::move(entity_query_data_))}; + } catch (const query_builder_exception &ex) { + return {utils::failure(ex.error())}; + } catch (...) { + return {utils::failure(query_build_error::UnexpectedError)}; + } + } + + template < class V > + void on_primary_key(const char *id, V &, std::enable_if_t && !std::is_same_v>* = nullptr) + { + push(id); + if (!is_root_entity()) { + return; + } + if (pk_.is_null()) { + entity_query_data_.pk_column_ = id; + } else if (pk_.is_integer()) { + auto t = std::make_shared(table_info_stack_.top().get().name()); + auto v = *pk_.as(); + auto c = sql::column{t, id, ""}; + auto co = std::make_unique>(c, query::basic_condition::operand_type::EQUAL, v); + entity_query_data_.where_clause = std::move(co); + // entity_query_data_.where_clause = query::make_condition(c == v); + entity_query_data_.pk_column_ = id; + } + } + + void on_primary_key(const char *id, std::string &, size_t); + void on_revision(const char *id, unsigned long long &/*rev*/); + + template + void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) + { + push(id); + } + + template + void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr) + { + on_foreign_object(id, obj, attr); + } + + template + void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr) + { + on_foreign_object(id, obj, attr); + } + + template + void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr) + { + if (attr.fetch() == utils::fetch_type::EAGER) { + const auto info = schema_.info(); + if (!info) { + throw query_builder_exception{query_build_error::UnknownType}; + } + table_info_stack_.push(info.value()); + typename ContainerType::value_type::value_type obj; + matador::access::process(*this , obj); + table_info_stack_.pop(); + + auto pk = info->prototype.primary_key(); + if (!pk) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + + append_join({table_info_stack_.top().get().name(), table_info_stack_.top().get().definition().primary_key()->name()}, {info->name, join_column}); + } + } + + template + void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) + { + if (attr.fetch() != utils::fetch_type::EAGER) { + return; + } + const auto info = schema_.info(); + if (!info) { + throw query_builder_exception{query_build_error::UnknownType}; + } + table_info_stack_.push(info.value()); + typename ContainerType::value_type::value_type obj; + matador::access::process(*this , obj); + table_info_stack_.pop(); + + auto pk = info->prototype.primary_key(); + if (!pk) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + + append_join(sql::column{table_info_stack_.top().get().name(), table_info_stack_.top().get().definition().primary_key()->name()}, {id, join_column}); + append_join({id, inverse_join_column}, {info->name, pk->name()}); + } + + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr) + { + if (attr.fetch() != utils::fetch_type::EAGER) { + return; + } + const auto info = schema_.info(); + if (!info) { + throw query_builder_exception{query_build_error::UnknownType}; + } + table_info_stack_.push(info.value()); + typename ContainerType::value_type::value_type obj; + matador::access::process(*this , obj); + table_info_stack_.pop(); + + auto pk = info->prototype.primary_key(); + if (!pk) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + + const auto join_columns = join_column_collector_.collect(); + + append_join({table_info_stack_.top().get().name(), table_info_stack_.top().get().definition().primary_key()->name()}, {id, join_columns.inverse_join_column}); + append_join({id, join_columns.join_column}, {info->name, pk->name()}); + } + +private: + template + void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr); + void push(const std::string &column_name); + [[nodiscard]] bool is_root_entity() const; + void append_join(const sql::column &left, const sql::column &right); + +private: + utils::value pk_; + std::stack> table_info_stack_; + const object::schema &schema_; + entity_query_data entity_query_data_; + int column_index{0}; + join_column_collector join_column_collector_; +}; + +template +void session_query_builder::on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr) +{ + if (attr.fetch() == utils::fetch_type::EAGER) { + const auto info = schema_.info(); + if (!info) { + throw query_builder_exception{query_build_error::UnknownType}; + } + table_info_stack_.push(info.value()); + typename Pointer::value_type obj; + matador::access::process(*this, obj); + table_info_stack_.pop(); + + auto pk = info->get().definition().primary_key(); + if (!pk) { + throw query_builder_exception{query_build_error::MissingPrimaryKey}; + } + append_join(sql::column{table_info_stack_.top().get().name(), id}, sql::column{info->get().name(), pk->name()}); + } else { + push(id); + } +} + +} +#endif //QUERY_ENTITY_QUERY_BUILDER_HPP diff --git a/include/matador/query/intermediates/query_create_intermediate.hpp b/include/matador/query/intermediates/query_create_intermediate.hpp index 179fc3c..287421a 100644 --- a/include/matador/query/intermediates/query_create_intermediate.hpp +++ b/include/matador/query/intermediates/query_create_intermediate.hpp @@ -12,8 +12,8 @@ class query_create_intermediate : public query_intermediate public: query_create_intermediate(); - executable_query table(const sql::table &table, std::initializer_list columns); - executable_query table(const sql::table &table, const std::vector &columns); + executable_query table(const sql::table &table, std::initializer_list columns); + executable_query table(const sql::table &table, const std::vector &columns); // template // executable_query table(const sql::table &table, const sql::schema &schema) // { diff --git a/include/matador/query/internal/query_parts.hpp b/include/matador/query/internal/query_parts.hpp index 0dd2307..ae0141d 100644 --- a/include/matador/query/internal/query_parts.hpp +++ b/include/matador/query/internal/query_parts.hpp @@ -8,7 +8,7 @@ #include "matador/query/query_part.hpp" #include "matador/sql/column.hpp" -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include "matador/sql/table.hpp" #include "matador/utils/placeholder.hpp" @@ -286,17 +286,17 @@ private: class query_create_table_part final : public query_part { public: - query_create_table_part(sql::table table, std::vector columns); + query_create_table_part(sql::table table, std::vector columns); [[nodiscard]] const sql::table& table() const; - [[nodiscard]] const std::vector& columns() const; + [[nodiscard]] const std::vector& columns() const; private: void accept(query_part_visitor &visitor) override; private: sql::table table_; - std::vector columns_; + std::vector columns_; }; class query_drop_part final : public query_part diff --git a/include/matador/query/query_data.hpp b/include/matador/query/query_data.hpp index 6029c43..7ced617 100644 --- a/include/matador/query/query_data.hpp +++ b/include/matador/query/query_data.hpp @@ -4,7 +4,7 @@ #include #include -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include "matador/sql/table.hpp" #include "matador/query/query_part.hpp" @@ -13,7 +13,7 @@ namespace matador::query { struct query_data { std::vector> parts{}; - std::vector columns{}; + std::vector columns{}; std::unordered_map tables{}; }; } diff --git a/include/matador/sql/column_generator.hpp b/include/matador/sql/column_generator.hpp new file mode 100644 index 0000000..447b2f2 --- /dev/null +++ b/include/matador/sql/column_generator.hpp @@ -0,0 +1,129 @@ +#ifndef QUERY_COLUMN_GENERATOR_HPP +#define QUERY_COLUMN_GENERATOR_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/field_attributes.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include "matador/sql/column.hpp" + +#include "matador/object/schema.hpp" + +#include +#include +#include + +namespace matador::sql { + +class column_generator +{ +private: + column_generator(std::vector &column_infos, + const object::schema &ts, + const std::string &table_name, + bool force_lazy); + +public: + ~column_generator() = default; + + template < class Type > + static std::vector generate(const object::schema &scm, 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; + matador::access::process(gen, obj); + return std::move(columns); + } + + template < class V > + void on_primary_key(const char *id, V &, std::enable_if_t && !std::is_same_v>* = nullptr) + { + push(id); + } + void on_primary_key(const char *id, std::string &, size_t); + void on_revision(const char *id, unsigned long long &/*rev*/); + + template + void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) + { + push(id); + } + + template + void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr) + { + if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { + push(id); + } else { + const auto info = table_schema_.info(); + if (!info) { + return; + } + table_name_stack_.push(info.value().get().name()); + typename Pointer::value_type obj; + matador::access::process(*this, obj); + table_name_stack_.pop(); + } + } + template + void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr) + { + if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { + push(id); + } else { + const auto info = table_schema_.info(); + if (!info) { + return; + } + table_name_stack_.push(info.value().get().name()); + typename Pointer::value_type obj; + matador::access::process(*this, obj); + table_name_stack_.pop(); + } + } + template + void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &attr) + { + if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) { + return; + } + const auto info = table_schema_.info(); + if (!info) { + return; + } + + table_name_stack_.push(info.value().get().name()); + typename ContainerType::value_type::value_type obj; + matador::access::process(*this, obj); + table_name_stack_.pop(); + } + + template + void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) + { + } + + template + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr) + { + } + +private: + void push(const std::string &column_name); + +private: + std::stack table_name_stack_; + std::vector &column_infos_; + const object::schema &table_schema_; + int column_index{0}; + bool force_lazy_{false}; +}; + +} + +#endif //QUERY_COLUMN_GENERATOR_HPP diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index 970305e..a76564f 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -2,7 +2,7 @@ #define QUERY_CONNECTION_HPP #include "matador/sql/abstract_sql_logger.hpp" -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include "matador/sql/connection_info.hpp" #include "matador/sql/executor.hpp" #include "matador/sql/statement.hpp" @@ -126,7 +126,7 @@ public: */ [[nodiscard]] utils::result rollback() const; - [[nodiscard]] utils::result, utils::error> describe(const std::string &table_name) const; + [[nodiscard]] utils::result, utils::error> describe(const std::string &table_name) const; [[nodiscard]] utils::result exists(const std::string &schema_name, const std::string &table_name) const; [[nodiscard]] utils::result exists(const std::string &table_name) const; diff --git a/include/matador/sql/interface/connection_impl.hpp b/include/matador/sql/interface/connection_impl.hpp index 0b147d3..6d59b55 100644 --- a/include/matador/sql/interface/connection_impl.hpp +++ b/include/matador/sql/interface/connection_impl.hpp @@ -3,7 +3,7 @@ #include -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include "matador/sql/connection_info.hpp" #include "matador/sql/query_context.hpp" #include "matador/utils/error.hpp" @@ -36,7 +36,7 @@ public: virtual utils::result, utils::error> fetch(const query_context &context) = 0; virtual utils::result, utils::error> prepare(const query_context &context) = 0; - virtual utils::result, utils::error> describe(const std::string &table) = 0; + virtual utils::result, utils::error> describe(const std::string &table) = 0; virtual utils::result exists(const std::string &schema_name, const std::string &table_name) = 0; [[nodiscard]] const class dialect &dialect() const; diff --git a/include/matador/sql/internal/query_result_impl.hpp b/include/matador/sql/internal/query_result_impl.hpp index 0aaab00..7be650e 100644 --- a/include/matador/sql/internal/query_result_impl.hpp +++ b/include/matador/sql/internal/query_result_impl.hpp @@ -8,7 +8,7 @@ #include "matador/sql/interface/query_result_reader.hpp" -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include #include @@ -61,8 +61,8 @@ private: class query_result_impl { public: - query_result_impl(std::unique_ptr &&reader, std::vector &&prototype, size_t column_index = 0); - query_result_impl(std::unique_ptr &&reader, const std::vector &prototype, size_t column_index = 0); + query_result_impl(std::unique_ptr &&reader, std::vector &&prototype, size_t column_index = 0); + query_result_impl(std::unique_ptr &&reader, const std::vector &prototype, size_t column_index = 0); template void on_primary_key(const char *id, ValueType &value, std::enable_if_t && !std::is_same_v>* = nullptr) @@ -149,11 +149,11 @@ public: return true; } - [[nodiscard]] const std::vector& prototype() const; + [[nodiscard]] const std::vector& prototype() const; protected: size_t column_index_ = 0; - std::vector prototype_; + std::vector prototype_; std::unique_ptr reader_; detail::pk_reader pk_reader_; }; @@ -161,7 +161,7 @@ protected: namespace detail { template -void detail::pk_reader::on_primary_key(const char *id, ValueType &value, std::enable_if_t && !std::is_same_v> *) +void pk_reader::on_primary_key(const char *id, ValueType &value, std::enable_if_t && !std::is_same_v> *) { utils::data_type_traits::read_value(reader_, id, column_index_++, value); } diff --git a/include/matador/sql/query_context.hpp b/include/matador/sql/query_context.hpp index 2ac1a3a..e1abda3 100644 --- a/include/matador/sql/query_context.hpp +++ b/include/matador/sql/query_context.hpp @@ -1,7 +1,7 @@ #ifndef QUERY_QUERY_DATA_HPP #define QUERY_QUERY_DATA_HPP -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include "matador/sql/table.hpp" #include "matador/utils/types.hpp" @@ -25,7 +25,7 @@ struct query_context sql_command command{}; std::string command_name; sql::table table{""}; - std::vector prototype; + std::vector prototype; std::vector result_vars; std::vector bind_vars; std::vector bind_types; diff --git a/include/matador/sql/query_result.hpp b/include/matador/sql/query_result.hpp index 8942feb..7a05055 100644 --- a/include/matador/sql/query_result.hpp +++ b/include/matador/sql/query_result.hpp @@ -1,7 +1,7 @@ #ifndef QUERY_QUERY_RESULT_HPP #define QUERY_QUERY_RESULT_HPP -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include "matador/sql/internal/query_result_impl.hpp" @@ -111,13 +111,13 @@ private: namespace detail { template < typename Type > -Type* create_prototype(const std::vector &/*prototype*/) +Type* create_prototype(const std::vector &/*prototype*/) { return new Type{}; } template <> -record* create_prototype(const std::vector &prototype); +record* create_prototype(const std::vector &prototype); } diff --git a/include/matador/sql/table.hpp b/include/matador/sql/table.hpp index 45f2183..729673f 100644 --- a/include/matador/sql/table.hpp +++ b/include/matador/sql/table.hpp @@ -10,8 +10,7 @@ namespace matador::sql { struct column; -struct table -{ +struct table { table() = default; table(const char *name); // NOLINT(*-explicit-constructor) table(std::string name); // NOLINT(*-explicit-constructor) diff --git a/include/matador/utils/constraints.hpp b/include/matador/utils/constraints.hpp index 44b0b18..a0c8a60 100644 --- a/include/matador/utils/constraints.hpp +++ b/include/matador/utils/constraints.hpp @@ -27,7 +27,7 @@ inline constraints operator&(constraints a, constraints b) inline constraints& operator|= (constraints& a, constraints b) { return (constraints&)((int&)a |= (int)b); } inline constraints& operator&= (constraints& a, constraints b) { return (constraints&)((int&)a &= (int)b); } -inline bool is_constraint_set(constraints source, constraints needle) +inline bool is_constraint_set(const constraints source, const constraints needle) { return static_cast(source & needle) > 0; } diff --git a/include/matador/utils/foreign_attributes.hpp b/include/matador/utils/foreign_attributes.hpp index 3798b3f..b03a6f7 100644 --- a/include/matador/utils/foreign_attributes.hpp +++ b/include/matador/utils/foreign_attributes.hpp @@ -27,7 +27,7 @@ private: fetch_type fetch_{fetch_type::LAZY}; }; -const utils::foreign_attributes default_foreign_attributes {}; +const foreign_attributes default_foreign_attributes {}; } diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index 2dd5531..741f2bd 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -1,4 +1,14 @@ add_library(matador-core STATIC + ../../include/matador/object/attribute_definition_generator.hpp + ../../include/matador/object/attribute_definition.hpp + ../../include/matador/object/basic_object_info.hpp + ../../include/matador/object/error_code.hpp + ../../include/matador/object/many_to_many_relation.hpp + ../../include/matador/object/object_definition.hpp + ../../include/matador/object/object_info.hpp + ../../include/matador/object/schema.hpp + ../../include/matador/object/schema_node.hpp + ../../include/matador/object/schema_node_iterator.hpp ../../include/matador/utils/access.hpp ../../include/matador/utils/attribute_reader.hpp ../../include/matador/utils/attribute_writer.hpp @@ -29,9 +39,19 @@ add_library(matador-core STATIC ../../include/matador/utils/types.hpp ../../include/matador/utils/value.hpp ../../include/matador/utils/version.hpp + object/attribute_definition_generator.cpp + object/attribute_definition.cpp + object/basic_object_info.cpp + object/error_code.cpp + object/object_definition.cpp + object/schema.cpp + object/schema_node.cpp + object/schema_node_iterator.cpp utils/default_type_traits.cpp utils/error.cpp + utils/errors.cpp utils/field_attributes.cpp + utils/foreign_attributes.cpp utils/identifier.cpp utils/library.cpp utils/os.cpp @@ -39,17 +59,6 @@ add_library(matador-core STATIC utils/types.cpp utils/value.cpp utils/version.cpp - utils/errors.cpp - ../../include/matador/object/basic_object_info.hpp - ../../include/matador/object/error_code.hpp - ../../include/matador/object/schema.hpp - ../../include/matador/object/schema_node.hpp - object/error_code.cpp - object/schema.cpp - object/schema_node.cpp - object/basic_object_info.cpp - ../../include/matador/object/schema_node_iterator.hpp - object/schema_node_iterator.cpp ) target_link_libraries(matador-core ${CMAKE_DL_LIBS}) diff --git a/source/orm/sql/column_definition.cpp b/source/core/object/attribute_definition.cpp similarity index 51% rename from source/orm/sql/column_definition.cpp rename to source/core/object/attribute_definition.cpp index ee58379..b081819 100644 --- a/source/orm/sql/column_definition.cpp +++ b/source/core/object/attribute_definition.cpp @@ -1,21 +1,21 @@ -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" #include #include -namespace matador::sql { +namespace matador::object { -column_definition::column_definition(const char *name) +attribute_definition::attribute_definition(const char *name) : name_(name) , attributes_(utils::null_attributes) {} -column_definition::column_definition(std::string name) +attribute_definition::attribute_definition(std::string name) : name_(std::move(name)) , attributes_(utils::null_attributes) {} -column_definition::column_definition(std::string name, const utils::basic_type type, const utils::field_attributes& attr, const null_option null_opt, const size_t index) +attribute_definition::attribute_definition(std::string name, const utils::basic_type type, const utils::field_attributes& attr, const null_option null_opt, const size_t index) : name_(std::move(name)) , index_(index) , attributes_(attr) @@ -23,7 +23,7 @@ column_definition::column_definition(std::string name, const utils::basic_type t , value_(type, attr.size()) {} -column_definition::column_definition(std::string name, +attribute_definition::attribute_definition(std::string name, const utils::basic_type type, const size_t index, std::string ref_table, @@ -38,137 +38,137 @@ column_definition::column_definition(std::string name, , ref_column_(std::move(ref_column)) {} -const std::string &column_definition::name() const +const std::string &attribute_definition::name() const { return name_; } -std::string column_definition::full_name() const +std::string attribute_definition::full_name() const { return table_ + "." + name_; } -std::string column_definition::table_name() const +std::string attribute_definition::table_name() const { return table_; } -int column_definition::index() const +int attribute_definition::index() const { return index_; } -const utils::field_attributes &column_definition::attributes() const +const utils::field_attributes &attribute_definition::attributes() const { return attributes_; } -bool column_definition::is_nullable() const +bool attribute_definition::is_nullable() const { return null_option_ == null_option::NULLABLE; } -utils::basic_type column_definition::type() const +utils::basic_type attribute_definition::type() const { return value_.type(); } -const std::string &column_definition::ref_table() const +const std::string &attribute_definition::ref_table() const { return ref_table_; } -const std::string &column_definition::ref_column() const +const std::string &attribute_definition::ref_column() const { return ref_column_; } -bool column_definition::is_foreign_reference() const +bool attribute_definition::is_foreign_reference() const { return !ref_column_.empty() && !ref_table_.empty(); } -bool column_definition::is_integer() const +bool attribute_definition::is_integer() const { return value_.is_integer(); } -bool column_definition::is_floating_point() const +bool attribute_definition::is_floating_point() const { return value_.is_floating_point(); } -bool column_definition::is_bool() const +bool attribute_definition::is_bool() const { return value_.is_bool(); } -bool column_definition::is_string() const +bool attribute_definition::is_string() const { return value_.is_string(); } -bool column_definition::is_varchar() const +bool attribute_definition::is_varchar() const { return value_.is_varchar(); } -bool column_definition::is_date() const +bool attribute_definition::is_date() const { return value_.is_date(); } -bool column_definition::is_time() const +bool attribute_definition::is_time() const { return value_.is_time(); } -bool column_definition::is_blob() const +bool attribute_definition::is_blob() const { return value_.is_blob(); } -bool column_definition::is_null() const +bool attribute_definition::is_null() const { return value_.is_null(); } -void column_definition::type(utils::basic_type /*type*/) +void attribute_definition::type(utils::basic_type /*type*/) { // type_ = type; // utils::initialize_by_utils::basic_type(type, value_); } -std::string column_definition::str() const +std::string attribute_definition::str() const { return value_.str(); } -std::ostream& operator<<(std::ostream &out, const column_definition &col) +std::ostream& operator<<(std::ostream &out, const attribute_definition &col) { out << col.str(); return out; } -column_definition make_column(const std::string &name, utils::basic_type type, utils::field_attributes attr, null_option null_opt) +attribute_definition make_column(const std::string &name, utils::basic_type type, utils::field_attributes attr, null_option null_opt) { return {name, type, attr, null_opt}; } template<> -column_definition make_column(const std::string &name, utils::field_attributes attr, null_option null_opt) +attribute_definition make_column(const std::string &name, utils::field_attributes attr, null_option null_opt) { return make_column(name, utils::data_type_traits::type(attr.size()), attr, null_opt); } template<> -column_definition make_pk_column(const std::string &name, size_t size) +attribute_definition make_pk_column(const std::string &name, size_t size) { return make_column(name, {size, utils::constraints::FOREIGN_KEY}); } template<> -[[maybe_unused]] column_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, +[[maybe_unused]] attribute_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column) { return {name, utils::data_type_traits::type(size), 0, ref_table, ref_column, {size, utils::constraints::FOREIGN_KEY}, null_option::NOT_NULL}; diff --git a/source/core/object/attribute_definition_generator.cpp b/source/core/object/attribute_definition_generator.cpp new file mode 100644 index 0000000..9ab87bb --- /dev/null +++ b/source/core/object/attribute_definition_generator.cpp @@ -0,0 +1,31 @@ +#include "matador/object/attribute_definition_generator.hpp" +#include "matador/object/schema.hpp" + +namespace matador::object { + +attribute_definition_generator::attribute_definition_generator(std::vector &columns, const schema &repo) +: columns_(columns) +, repo_(repo) +{} + +void attribute_definition_generator::on_primary_key(const char *id, std::string &pk, size_t size) +{ + on_attribute(id, pk, { size, utils::constraints::PRIMARY_KEY }); +} + +void attribute_definition_generator::on_revision(const char *id, unsigned long long int &x) +{ + on_attribute(id, x); +} + +std::pair attribute_definition_generator::determine_foreign_ref(const std::type_index &ti) +{ + return repo_.reference(ti).value(); +} + +void fk_attribute_generator::on_primary_key(const char *, std::string &, const size_t size) +{ + type_ = utils::data_type_traits::type(size); +} + +} \ No newline at end of file diff --git a/source/core/object/basic_object_info.cpp b/source/core/object/basic_object_info.cpp index c285cf4..f56f10f 100644 --- a/source/core/object/basic_object_info.cpp +++ b/source/core/object/basic_object_info.cpp @@ -6,9 +6,10 @@ namespace matador::object { -basic_object_info::basic_object_info(schema_node &node, const std::type_index type_index) +basic_object_info::basic_object_info(schema_node &node, const std::type_index type_index, object_definition &&definition) : node_(node) -, type_index_(type_index) {} +, type_index_(type_index) +, definition_(std::move(definition)) {} std::type_index basic_object_info::type_index() const { return type_index_; @@ -17,4 +18,9 @@ std::type_index basic_object_info::type_index() const { std::string basic_object_info::name() const { return node_.name(); } + +const object_definition& basic_object_info::definition() const { + return definition_; +} + } // namespace matador::object diff --git a/source/core/object/object_definition.cpp b/source/core/object/object_definition.cpp new file mode 100644 index 0000000..760e8c6 --- /dev/null +++ b/source/core/object/object_definition.cpp @@ -0,0 +1,148 @@ +#include "matador/object/object_definition.hpp" + +namespace matador::object { +object_definition::object_definition(std::initializer_list columns) +: columns_(columns) +{ + init(); +} + +object_definition::object_definition(const std::vector &columns) +: columns_(columns) +{ + init(); +} + +object_definition::object_definition(const object_definition &x) +: columns_(x.columns_) +, pk_index_(x.pk_index_) +{ + for (auto& col : columns_) { + add_to_map(col, col.index()); + } +} + +object_definition &object_definition::operator=(const object_definition &x) +{ + if (&x == this) { + return *this; + } + + columns_ = x.columns_; + columns_by_name_.clear(); + pk_index_ = x.pk_index_; + for (auto& col : columns_) { + add_to_map(col, col.index()); + } + return *this; +} + +bool object_definition::has_primary_key() const +{ + return pk_index_ > -1; +} + +std::optional object_definition::primary_key() const +{ + if (!has_primary_key()) { + return std::nullopt; + } + + return columns_[pk_index_]; +} + +void object_definition::append(attribute_definition col) +{ + auto &ref = columns_.emplace_back(std::move(col)); + add_to_map(ref, columns_.size()-1); +} + +const std::vector &object_definition::columns() const +{ + return columns_; +} + +const attribute_definition &object_definition::at(const std::string &name) const +{ + return columns_by_name_.at(name).first; +} + +const attribute_definition &object_definition::at( const size_t index) const +{ + return columns_.at(index); +} + +object_definition::iterator object_definition::find(const std::string &column_name) +{ + auto it = columns_by_name_.find(column_name); + return it != columns_by_name_.end() ? columns_.begin() + it->second.second : columns_.end(); +} + +object_definition::const_iterator object_definition::find(const std::string &column_name) const { + auto it = columns_by_name_.find(column_name); + return it != columns_by_name_.end() ? columns_.begin() + it->second.second : columns_.end(); +} + +object_definition::iterator object_definition::begin() +{ + return columns_.begin(); +} + +object_definition::const_iterator object_definition::begin() const +{ + return columns_.begin(); +} + +object_definition::const_iterator object_definition::cbegin() const +{ + return columns_.cbegin(); +} + +object_definition::iterator object_definition::end() +{ + return columns_.end(); +} + +object_definition::const_iterator object_definition::end() const +{ + return columns_.end(); +} + +object_definition::const_iterator object_definition::cend() const +{ + return columns_.cend(); +} + +size_t object_definition::size() const +{ + return columns_.size(); +} + +bool object_definition::empty() const +{ + return columns_.empty(); +} + +void object_definition::clear() +{ + columns_.clear(); + columns_by_name_.clear(); +} + +void object_definition::init() +{ + size_t index{0}; + for(auto &col : columns_) { + add_to_map(col, index++); + } +} + +void object_definition::add_to_map(attribute_definition &col, size_t index) +{ + columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index}); + if (is_constraint_set(col.attributes().options(), utils::constraints::PRIMARY_KEY)) { + pk_index_ = static_cast(index); + } +} + +} \ No newline at end of file diff --git a/source/core/object/schema.cpp b/source/core/object/schema.cpp index 140ed6f..214fe8b 100644 --- a/source/core/object/schema.cpp +++ b/source/core/object/schema.cpp @@ -35,6 +35,10 @@ std::string schema::name() const { return name_; } +utils::result, utils::error> schema::reference( const std::type_index& type_index ) const { + return utils::ok(std::pair{}); +} + utils::result, utils::error> schema::attach_node(const std::shared_ptr &node, const std::string &parent) { if (has_node(node->type_index(), node->name())) { diff --git a/source/core/object/schema_node.cpp b/source/core/object/schema_node.cpp index f6996c8..afee774 100644 --- a/source/core/object/schema_node.cpp +++ b/source/core/object/schema_node.cpp @@ -4,9 +4,9 @@ namespace matador::object { -schema_node::schema_node(schema &tree) +schema_node::schema_node(object::schema &tree) : schema_(tree) -, info_(std::make_unique(*this)) +, info_(std::make_unique(*this, object_definition{})) {} std::string schema_node::name() const { @@ -17,45 +17,8 @@ std::type_index schema_node::type_index() const { return info_->type_index(); } -void schema_node::append(const std::shared_ptr &sibling) { - sibling->parent_ = parent_; - - // sibling->previous_sibling_ = this; - // sibling->next_sibling_ = next_sibling_; - // next_sibling_ = sibling; - // sibling->next_sibling_->previous_sibling_ = sibling; - // sibling->depth = depth; - - // if (!parent_) { - // sibling->op_first = new object_proxy(); - // sibling->op_last = sibling->op_marker = new object_proxy(); - // sibling->op_first->link(sibling->op_last); - // } else { - // throw object_exception("failed to add node as sibling: node has no parent"); - // } -} - -void schema_node::insert(const std::shared_ptr &child) { - // child->parent_ = this; - // child->previous_sibling_ = last_child_->previous_sibling_; - // child->next_sibling_ = last_child_; - // last_child_->previous_sibling_->next_sibling_ = child; - // last_child_->previous_sibling_ = child; - // set depth - // child->depth = depth + 1; - // set object proxy pointer - // 1. first - // if (op_first->next() == op_last) { - // // node hasn't any serializable (proxy) - // child->op_first = op_first; - // } else { - // // node has some objects (proxy) - // child->op_first = op_last->prev(); - // } - // // 2. marker - // child->op_marker = op_last; - // // 3. last - // child->op_last = op_last; +const basic_object_info& schema_node::basic_info() const { + return *info_; } schema_node::node_ptr schema_node::next() const { diff --git a/source/core/utils/foreign_attributes.cpp b/source/core/utils/foreign_attributes.cpp new file mode 100644 index 0000000..2f175ee --- /dev/null +++ b/source/core/utils/foreign_attributes.cpp @@ -0,0 +1,25 @@ +#include "matador/utils/foreign_attributes.hpp" + +namespace matador::utils { + +foreign_attributes::foreign_attributes(cascade_type cascade) +: cascade_(cascade) {} + +foreign_attributes::foreign_attributes(fetch_type fetch) +: fetch_(fetch) {} + +foreign_attributes::foreign_attributes(cascade_type cascade, fetch_type fetch) +: cascade_(cascade) +, fetch_(fetch ) {} + +cascade_type foreign_attributes::cascade() const +{ + return cascade_; +} + +fetch_type foreign_attributes::fetch() const +{ + return fetch_; +} + +} diff --git a/source/orm/CMakeLists.txt b/source/orm/CMakeLists.txt index 9596015..71a8c2c 100644 --- a/source/orm/CMakeLists.txt +++ b/source/orm/CMakeLists.txt @@ -41,10 +41,11 @@ add_library(matador-orm STATIC ../../include/matador/query/query_part.hpp ../../include/matador/query/value_extractor.hpp ../../include/matador/orm/session.hpp + ../../include/matador/orm/session_query_builder.hpp ../../include/matador/sql/abstract_sql_logger.hpp ../../include/matador/sql/backend_provider.hpp ../../include/matador/sql/column.hpp - ../../include/matador/sql/column_definition.hpp + ../../include/matador/sql/column_generator.hpp ../../include/matador/sql/connection.hpp ../../include/matador/sql/connection_info.hpp ../../include/matador/sql/dialect.hpp @@ -104,9 +105,10 @@ add_library(matador-orm STATIC query/query_update_intermediate.cpp query/value_extractor.cpp orm/session.cpp + orm/session_query_builder.cpp sql/backend_provider.cpp sql/column.cpp - sql/column_definition.cpp + sql/column_generator.cpp sql/connection.cpp sql/connection_info.cpp sql/dialect.cpp diff --git a/source/orm/orm/session.cpp b/source/orm/orm/session.cpp index bbadff3..2c840ee 100644 --- a/source/orm/orm/session.cpp +++ b/source/orm/orm/session.cpp @@ -11,12 +11,17 @@ session::session(sql::connection_pool &pool) , dialect_(sql::backend_provider::instance().connection_dialect(pool_.info().type)) , schema_(std::make_unique(dialect_.default_schema_name())){} -void session::create_schema() -{ +utils::result session::create_schema() const { auto c = pool_.acquire(); for (const auto &t : *schema_) { - c->query(*schema_).create().table(t.second.name, t.second.prototype.columns()).execute(); + auto result = query::query::create() + .table(t.name(), t.basic_info().definition().columns()) + .execute(*c); + if ( !result ) { + return utils::failure(result.err()); + } } + return utils::ok(); } void session::drop_table(const std::string &table_name) @@ -26,10 +31,10 @@ void session::drop_table(const std::string &table_name) throw std::logic_error("no database connection available"); } - c->query(*schema_).drop().table(table_name).execute(); + // c->query(*schema_).drop().table(table_name).execute(); } -sql::query_result session::fetch(const query_context &q) const +utils::result, utils::error> session::fetch(const sql::query_context &q) const { auto c = pool_.acquire(); if (!c.valid()) { @@ -37,16 +42,20 @@ sql::query_result session::fetch(const query_context &q) const } auto it = prototypes_.find(q.table.name); if (it == prototypes_.end()) { - it = prototypes_.emplace(q.table.name, c->describe(q.table.name)).first; + auto result = c->describe(q.table.name); + if (!result) { + return utils::failure(result.err()); + } + it = prototypes_.emplace(q.table.name, *result).first; } // adjust columns from given query for (auto &col : q.prototype) { - if (const auto rit = it->second.find(col.name()); col.type() == utils::basic_type::type_unknown && rit != it->second.end()) { - const_cast(col).type(rit->type()); + if (const auto rit = it->second.find(col.name()); /*col.type() == utils::basic_type::type_unknown && */rit != it->second.end()) { + const_cast(col).type(rit->type()); } } - auto res = c->fetch(q.sql); - return sql::query_result{std::move(res), q.prototype}; + auto res = c->fetch(q); + return utils::ok(sql::query_result{std::move(*res)/*, q.prototype*/}); } //query_result session::fetch(const std::string &sql) const @@ -62,22 +71,22 @@ size_t session::execute(const std::string &sql) const { return c->execute(sql); } -sql::statement session::prepare(query_context q) const +sql::statement session::prepare(const sql::query_context& q) const { auto c = pool_.acquire(); if (!c.valid()) { throw std::logic_error("no database connection available"); } - return c->prepare(std::move(q)); + return c->prepare(q).release(); } -std::vector session::describe_table(const std::string &table_name) const +std::vector session::describe_table(const std::string &table_name) const { auto c = pool_.acquire(); if (!c.valid()) { throw std::logic_error("no database connection available"); } - return c->describe(table_name); + return c->describe(table_name).release(); } bool session::table_exists(const std::string &table_name) const @@ -89,28 +98,26 @@ bool session::table_exists(const std::string &table_name) const return c->exists(dialect_.default_schema_name(), table_name); } -const class dialect &session::dialect() const +const class sql::dialect &session::dialect() const { return dialect_; } -std::unique_ptr session::fetch(const std::string &sql) const -{ - auto c = pool_.acquire(); - if (!c.valid()) { - throw std::logic_error("no database connection available"); - } - return c->fetch(sql); -} +// std::unique_ptr session::fetch(const std::string &sql) const +// { +// auto c = pool_.acquire(); +// if (!c.valid()) { +// throw std::logic_error("no database connection available"); +// } +// return c->fetch(sql); +// } -query_select session::build_select_query(sql::connection_ptr &conn, entity_query_data &&data) const -{ - return conn->query(*schema_) - .select(data.columns) +query::fetchable_query session::build_select_query(sql::connection_ptr &conn, entity_query_data &&data) { + return query::query::select(data.columns) .from(data.root_table_name) .join_left(data.joins) .where(std::move(data.where_clause)) - .order_by({data.root_table_name, data.pk_column_}) + .order_by(sql::column{data.root_table_name, data.pk_column_}) .asc(); } diff --git a/source/orm/orm/session_query_builder.cpp b/source/orm/orm/session_query_builder.cpp new file mode 100644 index 0000000..ef9de59 --- /dev/null +++ b/source/orm/orm/session_query_builder.cpp @@ -0,0 +1,41 @@ +#include "matador/orm/session_query_builder.hpp" + +#include + +namespace matador::orm { + +void session_query_builder::on_primary_key(const char *id, std::string &, size_t) +{ + push(id); + if (!is_root_entity()) { + const auto b = pk_.is_varchar(); + std::cout << "is matching primary key: " << std::boolalpha << b << "\n"; + } +} + +void session_query_builder::on_revision(const char *id, unsigned long long &/*rev*/) +{ + push(id); +} + +void session_query_builder::push(const std::string &column_name) +{ + char str[4]; + snprintf(str, 4, "c%02d", ++column_index); + entity_query_data_.columns.emplace_back(table_info_stack_.top().get().name(), column_name, str); +} + +[[nodiscard]] bool session_query_builder::is_root_entity() const { + return table_info_stack_.size() == 1; +} + +void session_query_builder::append_join(const sql::column &left, const sql::column &right) +{ + using namespace matador::query; + entity_query_data_.joins.push_back({ + { right.table_ }, + make_condition(left == right) + }); +} + +} \ No newline at end of file diff --git a/source/orm/query/intermediates/query_create_intermediate.cpp b/source/orm/query/intermediates/query_create_intermediate.cpp index 14998d2..40436db 100644 --- a/source/orm/query/intermediates/query_create_intermediate.cpp +++ b/source/orm/query/intermediates/query_create_intermediate.cpp @@ -9,12 +9,12 @@ query_create_intermediate::query_create_intermediate() context_->parts.push_back(std::make_unique()); } -executable_query query_create_intermediate::table(const sql::table &table, const std::initializer_list columns) +executable_query query_create_intermediate::table(const sql::table &table, const std::initializer_list columns) { - return this->table(table, std::vector{columns}); + return this->table(table, std::vector{columns}); } -executable_query query_create_intermediate::table(const sql::table &table, const std::vector &columns) +executable_query query_create_intermediate::table(const sql::table &table, const std::vector &columns) { context_->parts.push_back(std::make_unique(table, columns)); return {context_}; diff --git a/source/orm/query/internal/query_parts.cpp b/source/orm/query/internal/query_parts.cpp index 0b68d90..989ed77 100644 --- a/source/orm/query/internal/query_parts.cpp +++ b/source/orm/query/internal/query_parts.cpp @@ -254,7 +254,7 @@ void query_create_part::accept(query_part_visitor &visitor) visitor.visit(*this); } -query_create_table_part::query_create_table_part(sql::table table, std::vector columns) +query_create_table_part::query_create_table_part(sql::table table, std::vector columns) : query_part(sql::dialect_token::TABLE) , table_(std::move(table)) , columns_(std::move(columns)) {} @@ -264,7 +264,7 @@ const sql::table &query_create_table_part::table() const return table_; } -const std::vector &query_create_table_part::columns() const +const std::vector &query_create_table_part::columns() const { return columns_; } diff --git a/source/orm/query/internal/query_result_impl.cpp b/source/orm/query/internal/query_result_impl.cpp index 139c4ec..32f1e78 100644 --- a/source/orm/query/internal/query_result_impl.cpp +++ b/source/orm/query/internal/query_result_impl.cpp @@ -12,14 +12,14 @@ void detail::pk_reader::on_primary_key(const char *id, std::string &value, size_ utils::data_type_traits::read_value(reader_, id, column_index_++, value, size); } -query_result_impl::query_result_impl(std::unique_ptr &&reader, std::vector &&prototype, const size_t column_index) +query_result_impl::query_result_impl(std::unique_ptr &&reader, std::vector &&prototype, const size_t column_index) : column_index_(column_index) , prototype_(std::move(prototype)) , reader_(std::move(reader)) , pk_reader_(*reader_) {} -query_result_impl::query_result_impl(std::unique_ptr &&reader, const std::vector &prototype, const size_t column_index) +query_result_impl::query_result_impl(std::unique_ptr &&reader, const std::vector &prototype, const size_t column_index) : column_index_(column_index) , prototype_(prototype) , reader_(std::move(reader)) @@ -53,7 +53,7 @@ query_result_impl::on_attribute(const char *id, utils::value &val, const utils:: reader_->read_value(id, column_index_++, val, attr.size()); } -const std::vector& query_result_impl::prototype() const +const std::vector& query_result_impl::prototype() const { return prototype_; } diff --git a/source/orm/query/query_compiler.cpp b/source/orm/query/query_compiler.cpp index 5e93ca4..4a34ea9 100644 --- a/source/orm/query/query_compiler.cpp +++ b/source/orm/query/query_compiler.cpp @@ -242,7 +242,7 @@ struct column_context std::vector foreign_contexts; }; -std::string build_create_column(const sql::column_definition &col, const sql::dialect &d, column_context &context); +std::string build_create_column(const object::attribute_definition &col, const sql::dialect &d, column_context &context); void query_compiler::visit(internal::query_create_table_part &create_table_part) { @@ -324,7 +324,7 @@ void query_compiler::visit(internal::query_drop_table_part &drop_table_part) query_.sql += " " + query_compiler::build_table_name(drop_table_part.token(), *dialect_, query_.table); } -std::string build_create_column(const sql::column_definition &col, const sql::dialect &d, column_context &context) +std::string build_create_column(const object::attribute_definition &col, const sql::dialect &d, column_context &context) { std::string result = d.prepare_identifier_string(col.name()) + " " + d.data_type_at(col.type()); if (col.attributes().size() > 0) { diff --git a/source/orm/sql/column_generator.cpp b/source/orm/sql/column_generator.cpp new file mode 100644 index 0000000..8abdb82 --- /dev/null +++ b/source/orm/sql/column_generator.cpp @@ -0,0 +1,35 @@ +#include "matador/sql/column_generator.hpp" + +#include "matador/sql/table.hpp" + +namespace matador::sql { + +column_generator::column_generator(std::vector &column_infos, + const object::schema &scm, + const std::string &table_name, + bool force_lazy) +: column_infos_(column_infos) +, table_schema_(scm) +, force_lazy_(force_lazy) +{ + table_name_stack_.push(table_name); +} + +void column_generator::on_primary_key(const char *id, std::string &, size_t) +{ + push(id); +} + +void column_generator::on_revision(const char *id, unsigned long long int &) +{ + push(id); +} + +void column_generator::push(const std::string &column_name) +{ + char str[4]; + snprintf(str, 4, "c%02d", ++column_index); + column_infos_.emplace_back(table{table_name_stack_.top()}, column_name, str); +} + +} \ No newline at end of file diff --git a/source/orm/sql/connection.cpp b/source/orm/sql/connection.cpp index 8df1a51..5b602d4 100644 --- a/source/orm/sql/connection.cpp +++ b/source/orm/sql/connection.cpp @@ -122,7 +122,7 @@ utils::result connection::rollback() const { return utils::ok(); } -utils::result, utils::error> connection::describe(const std::string &table_name) const +utils::result, utils::error> connection::describe(const std::string &table_name) const { return connection_->describe(table_name); } @@ -143,7 +143,7 @@ utils::result connection::execute(const std::string &sql) return connection_->execute(sql); } -bool has_unknown_columns(const std::vector &columns) { +bool has_unknown_columns(const std::vector &columns) { return std::any_of(std::begin(columns), std::end(columns), [](const auto &col) { return col.type() == utils::basic_type::type_null; }); @@ -199,7 +199,7 @@ utils::result connection::prepare(const query_context & return value.name() == col.name(); }); if (col.type() == utils::basic_type::type_null && rit != result->end()) { - const_cast(col).type(rit->type()); + const_cast(col).type(rit->type()); } } } diff --git a/source/orm/sql/query_result.cpp b/source/orm/sql/query_result.cpp index bbda829..57bb02f 100644 --- a/source/orm/sql/query_result.cpp +++ b/source/orm/sql/query_result.cpp @@ -6,7 +6,7 @@ namespace matador::sql::detail { template<> -record *create_prototype(const std::vector &prototype) +record *create_prototype(const std::vector &prototype) { auto result = std::make_unique(); for (const auto &col: prototype) { diff --git a/source/orm/sql/statement.cpp b/source/orm/sql/statement.cpp index 38b8ffd..c2e8cf5 100644 --- a/source/orm/sql/statement.cpp +++ b/source/orm/sql/statement.cpp @@ -29,7 +29,7 @@ utils::result statement::execute() const return statement_->execute(); } -//bool is_unknown(const std::vector &columns) { +//bool is_unknown(const std::vector &columns) { // return std::all_of(std::begin(columns), std::end(columns), [](const auto &col) { // return col.is_unknown(); // }); diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 8c432d8..561d860 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -12,11 +12,17 @@ add_executable(CoreTests utils/FieldAttributeTest.cpp utils/VersionTest.cpp utils/StringTest.cpp + object/ColumnDefinitionGeneratorTest.cpp object/SchemaTest.cpp ) target_link_libraries(CoreTests matador-core Catch2::Catch2WithMain) +target_include_directories(CoreTests + PUBLIC + ${CMAKE_SOURCE_DIR}/test/models} +) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(CoreTests PRIVATE -coverage) target_link_options(CoreTests PRIVATE -coverage) diff --git a/test/core/object/ColumnDefinitionGeneratorTest.cpp b/test/core/object/ColumnDefinitionGeneratorTest.cpp new file mode 100644 index 0000000..70c1b62 --- /dev/null +++ b/test/core/object/ColumnDefinitionGeneratorTest.cpp @@ -0,0 +1,56 @@ +#include + +#include "matador/object/attribute_definition_generator.hpp" +#include "matador/object/schema.hpp" + +#include "../test/models/product.hpp" +#include "../test/models/optional.hpp" + +using namespace matador::object; +using namespace matador::utils; + +TEST_CASE("Generate column definitions from object", "[column][definition][generator]") { + schema repo("main"); + + auto columns = attribute_definition_generator::generate(repo); + + const std::vector expected_columns = { + attribute_definition{"product_name", basic_type::type_varchar, constraints::PRIMARY_KEY, null_option::NOT_NULL }, + attribute_definition{"supplier_id", basic_type::type_uint32, constraints::FOREIGN_KEY, null_option::NOT_NULL }, + attribute_definition{"category_id", basic_type::type_uint32, constraints::FOREIGN_KEY, null_option::NOT_NULL }, + attribute_definition{"quantity_per_unit", basic_type::type_varchar, null_attributes, null_option::NOT_NULL }, + attribute_definition{"unit_price", basic_type::type_uint32, null_attributes, null_option::NOT_NULL }, + attribute_definition{"units_in_stock", basic_type::type_uint32, null_attributes, null_option::NOT_NULL }, + attribute_definition{"units_in_order", basic_type::type_uint32, null_attributes, null_option::NOT_NULL }, + attribute_definition{"reorder_level", basic_type::type_uint32, null_attributes, null_option::NOT_NULL }, + attribute_definition{"discontinued", basic_type::type_bool, null_attributes, null_option::NOT_NULL } + }; + REQUIRE(!columns.empty()); + REQUIRE(columns.size() == expected_columns.size()); + + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].name() == columns[i].name()); + REQUIRE(expected_columns[i].attributes().options() == columns[i].attributes().options() ); + REQUIRE(expected_columns[i].type() == columns[i].type() ); + } +} + +TEST_CASE("Generate columns from object with nullable columns", "[column generator]") { + schema repo("main"); + + auto columns = attribute_definition_generator::generate(repo); + + const std::vector expected_columns = { + attribute_definition{"id", basic_type::type_uint32, constraints::PRIMARY_KEY, null_option::NOT_NULL }, + attribute_definition{"name", basic_type::type_varchar, null_attributes, null_option::NOT_NULL }, + attribute_definition{"age", basic_type::type_uint32, null_attributes, null_option::NOT_NULL } + }; + REQUIRE(!columns.empty()); + REQUIRE(columns.size() == expected_columns.size()); + + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].name() == columns[i].name()); + REQUIRE(expected_columns[i].attributes().options() == columns[i].attributes().options() ); + REQUIRE(expected_columns[i].type() == columns[i].type() ); + } +} \ No newline at end of file diff --git a/test/core/object/SchemaTest.cpp b/test/core/object/SchemaTest.cpp index 0f87022..89deba0 100644 --- a/test/core/object/SchemaTest.cpp +++ b/test/core/object/SchemaTest.cpp @@ -8,6 +8,8 @@ using namespace matador; struct person { virtual ~person() = default; + template < typename Operator > + void process(Operator &op) {} }; struct student final : person {}; diff --git a/test/models/airplane.hpp b/test/models/airplane.hpp new file mode 100644 index 0000000..3ee5b57 --- /dev/null +++ b/test/models/airplane.hpp @@ -0,0 +1,37 @@ +#ifndef QUERY_AIRPLANE_HPP +#define QUERY_AIRPLANE_HPP + +#include "category.hpp" +#include "supplier.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/cascade_type.hpp" +#include "matador/utils/field_attributes.hpp" + +#include + +namespace matador::test { + +struct airplane { + airplane() = default; + airplane(unsigned int id, std::string b, std::string m) + : id(id) + , brand(std::move(b)) + , model(std::move(m)) {} + unsigned int id{}; + std::string brand; + std::string model; + + template + void process(Operator &op) { + namespace field = matador::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + field::attribute(op, "brand", brand, 255); + field::attribute(op, "model", model, 255); + } +}; + +} + +#endif //QUERY_AIRPLANE_HPP diff --git a/test/models/author.hpp b/test/models/author.hpp new file mode 100644 index 0000000..a899999 --- /dev/null +++ b/test/models/author.hpp @@ -0,0 +1,40 @@ +#ifndef QUERY_AUTHOR_HPP +#define QUERY_AUTHOR_HPP + +#include "matador/utils/access.hpp" + +#include "matador/object/object_ptr.hpp" + +#include +#include + +namespace matador::test { + +struct book; + +struct author { + unsigned int id{}; + std::string first_name; + std::string last_name; + std::string date_of_birth; + unsigned short year_of_birth{}; + bool distinguished{false}; + std::vector> books; + + template + void process(Operator &op) + { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "first_name", first_name, 63); + field::attribute(op, "last_name", last_name, 63); + field::attribute(op, "date_of_birth", date_of_birth, 31); + field::attribute(op, "year_of_birth", year_of_birth); + field::attribute(op, "distinguished", distinguished); + // field::has_many(op, books, "author_id", utils::fetch_type::LAZY); + } +}; + +} + +#endif //QUERY_AUTHOR_HPP diff --git a/test/models/book.hpp b/test/models/book.hpp new file mode 100644 index 0000000..ba5610e --- /dev/null +++ b/test/models/book.hpp @@ -0,0 +1,33 @@ +#ifndef QUERY_BOOK_HPP +#define QUERY_BOOK_HPP + +#include "matador/object/object_ptr.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include + +namespace matador::test { + +struct author; + +struct book { + unsigned int id{}; + matador::object::object_ptr book_author; + std::string title; + unsigned short published_in{}; + + template + void process(Operator &op) + { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "title", title, 511); + field::belongs_to(op, "author_id", book_author, utils::fetch_type::EAGER); + field::attribute(op, "published_in", published_in); + } +}; + +} +#endif //QUERY_BOOK_HPP diff --git a/test/models/category.hpp b/test/models/category.hpp new file mode 100644 index 0000000..718ade8 --- /dev/null +++ b/test/models/category.hpp @@ -0,0 +1,24 @@ +#ifndef QUERY_CATEGORY_HPP +#define QUERY_CATEGORY_HPP + +#include "matador/utils/access.hpp" + +#include + +namespace matador::test { + +struct category { + unsigned int id{}; + std::string name; + + template + void process(Operator &op) { + namespace field = matador::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + } +}; + +} +#endif //QUERY_CATEGORY_HPP diff --git a/test/models/flight.hpp b/test/models/flight.hpp new file mode 100644 index 0000000..db40047 --- /dev/null +++ b/test/models/flight.hpp @@ -0,0 +1,39 @@ +#ifndef QUERY_FLIGHT_HPP +#define QUERY_FLIGHT_HPP + +#include "airplane.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/cascade_type.hpp" +#include "matador/utils/fetch_type.hpp" + +#include "matador/object/object_ptr.hpp" + +#include +#include + +namespace matador::test { + +struct flight +{ + flight() = default; + flight(const unsigned int id, const object::object_ptr &plane, std::string name) + : id(id), airplane(plane), pilot_name(std::move(name)) {} + + unsigned int id{}; + object::object_ptr airplane; + std::string pilot_name; + + template + void process(Operator &op) { + namespace field = matador::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + // field::has_one(op, "airplane_id", airplane, {utils::cascade_type::ALL, utils::fetch_type::EAGER}); + field::attribute(op, "pilot_name", pilot_name, 255); + } +}; + +} + +#endif //QUERY_FLIGHT_HPP diff --git a/test/models/location.hpp b/test/models/location.hpp index 0506cac..f158a03 100644 --- a/test/models/location.hpp +++ b/test/models/location.hpp @@ -15,7 +15,7 @@ enum class Color : uint8_t { struct location { - unsigned long id{}; + unsigned int id{}; std::string name; coordinate coord; Color color{Color::Green}; diff --git a/test/models/optional.hpp b/test/models/optional.hpp new file mode 100644 index 0000000..c85c0f4 --- /dev/null +++ b/test/models/optional.hpp @@ -0,0 +1,30 @@ +#ifndef QUERY_OPTIONAL_HPP +#define QUERY_OPTIONAL_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/field_attributes.hpp" + +#include +#include + +namespace matador::test { + +struct optional +{ + unsigned int id{}; + std::optional name; + std::optional age{}; + + template + void process(Operator &op) { + namespace field = matador::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + field::attribute(op, "age", age); + } +}; + +} + +#endif //QUERY_OPTIONAL_HPP diff --git a/test/models/order.hpp b/test/models/order.hpp new file mode 100644 index 0000000..c576f2b --- /dev/null +++ b/test/models/order.hpp @@ -0,0 +1,50 @@ +#ifndef QUERY_ORDER_HPP +#define QUERY_ORDER_HPP + +#include "order_details.hpp" + +#include "matador/utils/access.hpp" + +#include "matador/object/object_ptr.hpp" + +#include + +namespace matador::test { + +struct order +{ + unsigned int order_id{}; + std::string order_date; + std::string required_date; + std::string shipped_date; + unsigned int ship_via{}; + unsigned int freight{}; + std::string ship_name; + std::string ship_address; + std::string ship_city; + std::string ship_region; + std::string ship_postal_code; + std::string ship_country; + std::vector> order_details_; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "order_id", order_id); + field::attribute(op, "order_date", order_date, 255); + field::attribute(op, "required_date", required_date, 255); + field::attribute(op, "shipped_date", shipped_date, 255); + field::attribute(op, "ship_via", ship_via); + field::attribute(op, "freight", freight); + field::attribute(op, "ship_name", ship_name, 255); + field::attribute(op, "ship_address", ship_address, 255); + field::attribute(op, "ship_city", ship_city, 255); + field::attribute(op, "ship_region", ship_region, 255); + field::attribute(op, "ship_postal_code", ship_postal_code, 255); + field::attribute(op, "ship_country", ship_country, 255); + // field::has_many(op, order_details_, "order_id", utils::fetch_type::EAGER); + } +}; + +} +#endif //QUERY_ORDER_HPP diff --git a/test/models/order_details.hpp b/test/models/order_details.hpp new file mode 100644 index 0000000..ebc93a8 --- /dev/null +++ b/test/models/order_details.hpp @@ -0,0 +1,30 @@ +#ifndef QUERY_ORDER_DETAILS_HPP +#define QUERY_ORDER_DETAILS_HPP + +#include "product.hpp" + +#include "matador/object/object_ptr.hpp" + +#include "matador/utils/foreign_attributes.hpp" + +namespace matador::test { + +struct order; + +struct order_details +{ + unsigned int order_details_id; + matador::object::object_ptr order_; + matador::object::object_ptr product_; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "order_details_id", order_details_id); + field::belongs_to(op, "order_id", order_, utils::default_foreign_attributes); + field::has_one(op, "product_id", product_, utils::default_foreign_attributes); + } +}; + +} +#endif //QUERY_ORDER_DETAILS_HPP diff --git a/test/models/product.hpp b/test/models/product.hpp new file mode 100644 index 0000000..c2dd794 --- /dev/null +++ b/test/models/product.hpp @@ -0,0 +1,45 @@ +#ifndef QUERY_PRODUCT_HPP +#define QUERY_PRODUCT_HPP + +#include "category.hpp" +#include "supplier.hpp" + +#include "matador/utils/access.hpp" +#include "matador/utils/cascade_type.hpp" +#include "matador/utils/field_attributes.hpp" + +#include "matador/object/object_ptr.hpp" + +#include + +namespace matador::test { + +struct product { + std::string product_name; + object::object_ptr supplier; + object::object_ptr category; + std::string quantity_per_unit; + unsigned int unit_price; + unsigned int units_in_stock; + unsigned int units_in_order; + unsigned int reorder_level; + bool discontinued; + + template + void process(Operator &op) { + namespace field = matador::access; + using namespace matador::utils; + field::primary_key(op, "product_name", product_name, 255); + field::has_one(op, "supplier_id", supplier, utils::cascade_type::ALL); + field::has_one(op, "category_id", category, utils::cascade_type::ALL); + field::attribute(op, "quantity_per_unit", quantity_per_unit, 255); + field::attribute(op, "unit_price", unit_price); + field::attribute(op, "units_in_stock", units_in_stock); + field::attribute(op, "units_in_order", units_in_order); + field::attribute(op, "reorder_level", reorder_level); + field::attribute(op, "discontinued", discontinued); + } +}; +} + +#endif //QUERY_PRODUCT_HPP diff --git a/test/models/recipe.hpp b/test/models/recipe.hpp new file mode 100644 index 0000000..ef8f09b --- /dev/null +++ b/test/models/recipe.hpp @@ -0,0 +1,52 @@ +#ifndef QUERY_RECIPE_HPP +#define QUERY_RECIPE_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include "matador/object/object_ptr.hpp" +#include "matador/object/many_to_many_relation.hpp" + +#include + +namespace matador::test { + +struct recipe; +struct ingredient +{ + unsigned int id{}; + std::string name; + std::vector> recipes; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + // field::has_many_to_many(op, "recipe_ingredients", recipes, "ingredient_id", "recipe_id", utils::fetch_type::EAGER); + } +}; + +struct recipe +{ + unsigned int id{}; + std::string name; + std::vector> ingredients; + + template + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + // field::has_many_to_many(op, "recipe_ingredients", ingredients, utils::fetch_type::LAZY); + } +}; + +class recipe_ingredient : public object::many_to_many_relation { +public: + recipe_ingredient() : many_to_many_relation("recipe_id", "ingredient_id") {} +}; + +} + +#endif //QUERY_RECIPE_HPP diff --git a/test/models/student.hpp b/test/models/student.hpp new file mode 100644 index 0000000..02dd0fa --- /dev/null +++ b/test/models/student.hpp @@ -0,0 +1,62 @@ +#ifndef QUERY_STUDENT_HPP +#define QUERY_STUDENT_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/foreign_attributes.hpp" + +#include "matador/object/object_ptr.hpp" +#include "matador/object/many_to_many_relation.hpp" + +#include +#include + +namespace matador::test { + +struct course; + +struct student { + unsigned int id{}; + std::string name; + std::vector> courses; + + student() = default; + explicit student(unsigned int id, std::string name) + : id(id) + , name(std::move(name)) {} + + template < class Operator > + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + // field::has_many_to_many(op, "student_courses", courses, "student_id", "course_id", utils::fetch_type::LAZY); + } + +}; + +struct course { + unsigned int id{}; + std::string title; + std::vector> students; + + course() = default; + explicit course(unsigned int id, std::string title) + : id(id) + , title(std::move(title)) {} + + template < class Operator > + void process(Operator &op) { + namespace field = matador::access; + field::primary_key(op, "id", id); + field::attribute(op, "title", title, 255); + // field::has_many_to_many(op, "student_courses", students, utils::fetch_type::EAGER); + } +}; + +class student_course : public object::many_to_many_relation { +public: + student_course() : many_to_many_relation("student_id", "course_id") {} +}; + +} +#endif //QUERY_STUDENT_HPP diff --git a/test/models/supplier.hpp b/test/models/supplier.hpp new file mode 100644 index 0000000..b785193 --- /dev/null +++ b/test/models/supplier.hpp @@ -0,0 +1,24 @@ +#ifndef QUERY_SUPPLIER_HPP +#define QUERY_SUPPLIER_HPP + +#include "matador/utils/access.hpp" + +#include + +namespace matador::test { + +struct supplier { + unsigned int id{}; + std::string name; + + template + void process(Operator &op) { + namespace field = matador::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + } +}; + +} +#endif //QUERY_SUPPLIER_HPP diff --git a/test/models/types.hpp b/test/models/types.hpp index 35e244e..f4e6a77 100644 --- a/test/models/types.hpp +++ b/test/models/types.hpp @@ -10,7 +10,7 @@ struct types { enum { CSTR_LEN=255 }; - unsigned long id_ = 0; + unsigned int id_ = 0; char char_ = 'c'; short short_ = -127; int int_ = -65000; @@ -27,8 +27,8 @@ struct types char cstr_[CSTR_LEN]{}; std::string string_ = "Welt"; std::string varchar_ = "Erde"; - matador::date date_; - matador::time time_; +// matador::date date_; +// matador::time time_; utils::blob binary_{ 1, 2, 3, 4 }; template < class Operator > diff --git a/test/orm/CMakeLists.txt b/test/orm/CMakeLists.txt index 96b0303..a8d43d7 100644 --- a/test/orm/CMakeLists.txt +++ b/test/orm/CMakeLists.txt @@ -13,12 +13,14 @@ add_executable(OrmTests backend/test_result_reader.hpp backend/test_statement.cpp backend/test_statement.hpp + orm/SessionQueryBuilderTest.cpp query/ConditionTests.cpp query/QueryBuilderTest.cpp query/QueryFixture.cpp query/QueryFixture.hpp query/QueryTest.cpp sql/ColumnTest.cpp + sql/ColumnGeneratorTest.cpp sql/ConnectionPoolTest.cpp sql/FieldTest.cpp utils/auto_reset_event.cpp diff --git a/test/orm/backend/test_connection.cpp b/test/orm/backend/test_connection.cpp index 18b585f..bfd4068 100644 --- a/test/orm/backend/test_connection.cpp +++ b/test/orm/backend/test_connection.cpp @@ -62,9 +62,9 @@ utils::result, utils::error> test_connectio return utils::ok(std::unique_ptr{}); } -utils::result, utils::error> test_connection::describe(const std::string &/*table*/) +utils::result, utils::error> test_connection::describe(const std::string &/*table*/) { - return utils::ok(std::vector{}); + return utils::ok(std::vector{}); } utils::result test_connection::exists(const std::string &/*schema_name*/, const std::string &/*table_name*/) diff --git a/test/orm/backend/test_connection.hpp b/test/orm/backend/test_connection.hpp index 5909d54..4f756c9 100644 --- a/test/orm/backend/test_connection.hpp +++ b/test/orm/backend/test_connection.hpp @@ -19,7 +19,7 @@ public: utils::result execute(const std::string &stmt) override; utils::result, utils::error> fetch(const sql::query_context &context) override; utils::result, utils::error> prepare(const sql::query_context &context) override; - utils::result, utils::error> describe(const std::string &table) override; + utils::result, utils::error> describe(const std::string &table) override; utils::result exists(const std::string &schema_name, const std::string &table_name) override; [[nodiscard]] std::string to_escaped_string( const utils::blob& value ) const override; diff --git a/test/orm/orm/SessionQueryBuilderTest.cpp b/test/orm/orm/SessionQueryBuilderTest.cpp new file mode 100644 index 0000000..f33b0d2 --- /dev/null +++ b/test/orm/orm/SessionQueryBuilderTest.cpp @@ -0,0 +1,265 @@ +#include + +#include "matador/sql/connection.hpp" + +#include "matador/query/query.hpp" + +#include "matador/orm/session_query_builder.hpp" + +#include "../../models/airplane.hpp" +#include "../../models/author.hpp" +#include "../../models/book.hpp" +#include "../../models/flight.hpp" +#include "../../models/recipe.hpp" +#include "../../models/order.hpp" +#include "../../models/student.hpp" + +using namespace matador::object; +using namespace matador::orm; +using namespace matador::sql; + +TEST_CASE("Create sql query data for entity with eager has one", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + auto result = scm.attach("airplanes") + .and_then( [&scm] { return scm.attach("flights"); } ); + REQUIRE(result); + + session_query_builder eqb(scm); + + auto data = eqb.build(17U); + + REQUIRE(data.is_ok()); + REQUIRE(data->root_table_name == "flights"); + REQUIRE(data->joins.size() == 1); + const std::vector expected_columns { + { "flights", "id", "c01" }, + { "airplanes", "id", "c02" }, + { "airplanes", "brand", "c03" }, + { "airplanes", "model", "c04" }, + { "flights", "pilot_name", "c05" }, + }; + REQUIRE(data->columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(data->columns[i])); + } + + std::vector> expected_join_data { + { "airplanes", R"("flights"."airplane_id" = "airplanes"."id")"} + }; + + query_context qc; + size_t index{0}; + for (const auto &jd : data->joins) { + REQUIRE(jd.join_table->name == expected_join_data[index].first); + REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second); + ++index; + } + + REQUIRE(data->where_clause); + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == R"("flights"."id" = 17)"); +} + +TEST_CASE("Create sql query data for entity with eager belongs to", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + auto result = scm.attach("authors") + .and_then( [&scm] { return scm.attach("books"); } ); + REQUIRE(result); + + session_query_builder eqb(scm); + + auto data = eqb.build(17); + + REQUIRE(data.is_ok()); + REQUIRE(data->root_table_name == "books"); + REQUIRE(data->joins.size() == 1); + const std::vector expected_columns { + { "books", "id", "c01" }, + { "books", "title", "c02" }, + { "authors", "id", "c03" }, + { "authors", "first_name", "c04" }, + { "authors", "last_name", "c05" }, + { "authors", "date_of_birth", "c06" }, + { "authors", "year_of_birth", "c07" }, + { "authors", "distinguished", "c08" }, + { "books", "published_in", "c09" } + }; + REQUIRE(data->columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(data->columns[i])); + } + + std::vector> expected_join_data { + { "authors", R"("books"."author_id" = "authors"."id")"} + }; + + query_context qc; + size_t index{0}; + for (const auto &jd : data->joins) { + REQUIRE(jd.join_table->name == expected_join_data[index].first); + REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second); + ++index; + } + + REQUIRE(data->where_clause); + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == R"("books"."id" = 17)"); + + auto q = matador::query::query::select(data->columns) + .from(data->root_table_name); + + for (auto &jd : data->joins) { + q.join_left(*jd.join_table) + .on(std::move(jd.condition)); + } + auto context = q + .where(std::move(data->where_clause)) + .str(db); +} + +TEST_CASE("Create sql query data for entity with eager has many belongs to", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + auto result = scm.attach("products") + .and_then( [&scm] { return scm.attach("order_details"); } ) + .and_then( [&scm] { return scm.attach("orders"); } ); + REQUIRE(result); + + session_query_builder eqb(scm); + + auto data = eqb.build(17); + + REQUIRE(data.is_ok()); + REQUIRE(data->root_table_name == "orders"); + REQUIRE(data->joins.size() == 1); + const std::vector expected_columns = { + { "orders", "order_id", "c01" }, + { "orders", "order_date", "c02" }, + { "orders", "required_date", "c03" }, + { "orders", "shipped_date", "c04" }, + { "orders", "ship_via", "c05" }, + { "orders", "freight", "c06" }, + { "orders", "ship_name", "c07" }, + { "orders", "ship_address", "c08" }, + { "orders", "ship_city", "c09" }, + { "orders", "ship_region", "c10" }, + { "orders", "ship_postal_code", "c11" }, + { "orders", "ship_country", "c12" }, + { "order_details", "order_details_id", "c13" }, + { "order_details", "order_id", "c14" }, + { "order_details", "product_id", "c15" } + }; + REQUIRE(data->columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(data->columns[i])); + } + + std::vector> expected_join_data { + { "order_details", R"("orders"."order_id" = "order_details"."order_id")"} + }; + + query_context qc; + size_t index{0}; + for (const auto &jd : data->joins) { + REQUIRE(jd.join_table->name == expected_join_data[index].first); + REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second); + ++index; + } + + REQUIRE(data->where_clause); + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == R"("orders"."order_id" = 17)"); +} + +TEST_CASE("Create sql query data for entity with eager many to many", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + auto result = scm.attach("recipes") + .and_then( [&scm] { return scm.attach("ingredients"); } ) + .and_then( [&scm] { return scm.attach("recipe_ingredients"); } ); + + session_query_builder eqb(scm); + + auto data = eqb.build(17); + + REQUIRE(data.is_ok()); + REQUIRE(data->root_table_name == "ingredients"); + REQUIRE(data->joins.size() == 2); + const std::vector expected_columns { + { "ingredients", "id", "c01" }, + { "ingredients", "name", "c02" }, + { "recipes", "id", "c03" }, + { "recipes", "name", "c04" } + }; + REQUIRE(data->columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(data->columns[i])); + } + + std::vector> expected_join_data { + { "recipe_ingredients", R"("ingredients"."id" = "recipe_ingredients"."ingredient_id")"}, + { "recipes", R"("recipe_ingredients"."recipe_id" = "recipes"."id")"} + }; + + query_context qc; + size_t index{0}; + for (const auto &jd : data->joins) { + REQUIRE(jd.join_table->name == expected_join_data[index].first); + REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second); + ++index; + } + + REQUIRE(data->where_clause); + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == R"("ingredients"."id" = 17)"); +} + +TEST_CASE("Create sql query data for entity with eager many to many (inverse part)", "[query][entity][builder]") { + using namespace matador::test; + connection db("noop://noop.db"); + schema scm("noop"); + auto result = scm.attach("students") + .and_then( [&scm] { return scm.attach("courses"); } ) + .and_then( [&scm] { return scm.attach("student_courses"); } ); + + session_query_builder eqb(scm); + + auto data = eqb.build(17); + + REQUIRE(data.is_ok()); + REQUIRE(data->root_table_name == "courses"); + REQUIRE(data->joins.size() == 2); + const std::vector expected_columns { + { "courses", "id", "c01" }, + { "courses", "title", "c02" }, + { "students", "id", "c03" }, + { "students", "name", "c04" } + }; + REQUIRE(data->columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(data->columns[i])); + } + + std::vector> expected_join_data { + { "student_courses", R"("courses"."id" = "student_courses"."course_id")"}, + { "students", R"("student_courses"."student_id" = "students"."id")"} + }; + + query_context qc; + size_t index{0}; + for (const auto &jd : data->joins) { + REQUIRE(jd.join_table->name == expected_join_data[index].first); + REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second); + ++index; + } + + REQUIRE(data->where_clause); + auto cond = data->where_clause->evaluate(db.dialect(), qc); + REQUIRE(cond == R"("courses"."id" = 17)"); +} \ No newline at end of file diff --git a/test/orm/query/QueryBuilderTest.cpp b/test/orm/query/QueryBuilderTest.cpp index be48ef2..d6890fc 100644 --- a/test/orm/query/QueryBuilderTest.cpp +++ b/test/orm/query/QueryBuilderTest.cpp @@ -5,16 +5,18 @@ #include #include -#include -#include -#include +#include "matador/object/attribute_definition_generator.hpp" -#include +#include "matador/sql/connection.hpp" +#include "matador/sql/table.hpp" + +#include "matador/utils/placeholder.hpp" // #include "models/author.hpp" // #include "models/book.hpp" using namespace matador::test; +using namespace matador::object; using namespace matador::sql; using namespace matador::query; using namespace matador::utils; diff --git a/test/orm/sql/ColumnGeneratorTest.cpp b/test/orm/sql/ColumnGeneratorTest.cpp new file mode 100644 index 0000000..95ccbec --- /dev/null +++ b/test/orm/sql/ColumnGeneratorTest.cpp @@ -0,0 +1,107 @@ +#include + +#include "matador/sql/column_generator.hpp" +#include "matador/object/schema.hpp" + +#include "../test/models/product.hpp" +#include "../test/models/order.hpp" +#include "../test/models/book.hpp" +#include "../test/models/author.hpp" +#include "matador/sql/table.hpp" + +using namespace matador::sql; +using namespace matador::object; + +TEST_CASE("Generate columns from object", "[column][generator]") { + using namespace matador::test; + schema s("main"); + auto result = s.attach("product"); + REQUIRE( result ); + + auto columns = column_generator::generate(s); + + const std::vector expected_columns = { + "product_name", + "supplier_id", + "category_id", + "quantity_per_unit", + "unit_price", + "units_in_stock", + "units_in_order", + "reorder_level", + "discontinued" + }; + REQUIRE(!columns.empty()); + REQUIRE(columns.size() == expected_columns.size()); + + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i] == columns[i].name); + } +} + +TEST_CASE("Generate columns for object with has many relation", "[column][generator][relation]") { + using namespace matador::test; + schema s("main"); + auto result = s.attach("product") + .and_then( [&s] { return s.attach("order_details"); } ) + .and_then( [&s] { return s.attach("order"); } ); + REQUIRE(result); + + auto columns = column_generator::generate(s); + + const auto order_table = std::make_shared("order"); + const auto order_details_table = std::make_shared
("order_details"); + const std::vector expected_columns = { + { order_table, "order_id", "c01" }, + { order_table, "order_date", "c02" }, + { order_table, "required_date", "c03" }, + { order_table, "shipped_date", "c04" }, + { order_table, "ship_via", "c05" }, + { order_table, "freight", "c06" }, + { order_table, "ship_name", "c07" }, + { order_table, "ship_address", "c08" }, + { order_table, "ship_city", "c09" }, + { order_table, "ship_region", "c10" }, + { order_table, "ship_postal_code", "c11" }, + { order_table, "ship_country", "c12" }, + { order_details_table, "order_details_id", "c13" }, + { order_details_table, "order_id", "c14" }, + { order_details_table, "product_id", "c15" } + }; + REQUIRE(!columns.empty()); + REQUIRE(columns.size() == expected_columns.size()); + + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(columns[i])); + } + +} + +TEST_CASE("Generate columns for object with eager foreign key relation", "[column][generator][eager]") { + using namespace matador::test; + schema s("main"); + auto result = s.attach("books") + .and_then( [&s] { return s.attach("authors"); } ); + REQUIRE(result); + + const auto books_table = std::make_shared
("books"); + const auto authors_table = std::make_shared
("authors"); + const std::vector expected_columns { + { books_table, "id", "c01" }, + { books_table, "title", "c02" }, + { authors_table, "id", "c03" }, + { authors_table, "first_name", "c04" }, + { authors_table, "last_name", "c05" }, + { authors_table, "date_of_birth", "c06" }, + { authors_table, "year_of_birth", "c07" }, + { authors_table, "distinguished", "c08" }, + { books_table, "published_in", "c09" } + }; + auto columns = column_generator::generate(s); + + REQUIRE(!columns.empty()); + REQUIRE(columns.size() == expected_columns.size()); + for (size_t i = 0; i != expected_columns.size(); ++i) { + REQUIRE(expected_columns[i].equals(columns[i])); + } +} diff --git a/test/orm/sql/ColumnTest.cpp b/test/orm/sql/ColumnTest.cpp index fce5fe5..9bcc505 100644 --- a/test/orm/sql/ColumnTest.cpp +++ b/test/orm/sql/ColumnTest.cpp @@ -1,12 +1,12 @@ #include -#include "matador/sql/column_definition.hpp" +#include "matador/object/attribute_definition.hpp" -using namespace matador::sql; +using namespace matador::object; using namespace matador::utils; TEST_CASE("Test create empty column", "[column]") { - column_definition c("name"); + attribute_definition c("name"); REQUIRE(c.name() == "name"); REQUIRE(c.index() == -1); @@ -26,7 +26,7 @@ TEST_CASE("Test create empty column", "[column]") { } TEST_CASE("Test copy and move column", "[column]") { - column_definition c("name"); + attribute_definition c("name"); c.set(std::string{"george"}, 255); REQUIRE(c.name() == "name"); REQUIRE(c.index() == -1);