From f01e9ff87f1b88066f6b56acacb22cb3355c7d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Tue, 4 Feb 2025 15:15:16 +0100 Subject: [PATCH] schema progress --- CMakeLists.txt | 10 +- demo/CMakeLists.txt | 3 +- demo/main.cpp | 289 +++++++++--------- include/matador/object/object_ptr.hpp | 17 ++ include/matador/object/schema.hpp | 48 ++- include/matador/object/schema_node.hpp | 5 + .../matador/object/schema_node_iterator.hpp | 2 +- include/matador/sql/connection.hpp | 1 - include/matador/sql/query_macro.hpp | 24 ++ include/matador/utils/macro_map.hpp | 42 +++ source/core/CMakeLists.txt | 1 + source/core/object/schema.cpp | 90 +++--- source/core/object/schema_node.cpp | 34 +++ source/core/object/schema_node_iterator.cpp | 36 +-- source/orm/CMakeLists.txt | 1 + test/core/CMakeLists.txt | 2 +- .../{PrototypeTreeTest.cpp => SchemaTest.cpp} | 87 +++--- 17 files changed, 416 insertions(+), 276 deletions(-) create mode 100644 include/matador/object/object_ptr.hpp create mode 100644 include/matador/sql/query_macro.hpp create mode 100644 include/matador/utils/macro_map.hpp rename test/core/object/{PrototypeTreeTest.cpp => SchemaTest.cpp} (52%) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc0ed08..90fad2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS_DEBUG "${GCC_CLANG_COMMON_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_RELEASE "${GCC_CLANG_COMMON_FLAGS_RELEASE}") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - MESSAGE(STATUS "MSVC detected - Adding compiler flags") - SET(CMAKE_CXX_FLAGS "/W3 /EHsc /bigobj") - SET(CMAKE_CXX_FLAGS_DEBUG "/MDd /Od /Zi /D_DEBUG /DDEBUG") - SET(CMAKE_CXX_FLAGS_RELEASE "/O1 /DNDEBUG") + message(STATUS "MSVC detected - Adding compiler flags") + set(CMAKE_CXX_FLAGS "/W3 /EHsc /bigobj") + set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Od /Zi /D_DEBUG /DDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "/O1 /DNDEBUG") + add_compile_options(/Zc:preprocessor) endif () #if(ENABLE_COVERAGE) # # set compiler flags @@ -53,5 +54,6 @@ endif () add_subdirectory(source) +add_subdirectory(demo) add_subdirectory(backends) add_subdirectory(test) diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index b36df9e..7c83cea 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -2,7 +2,8 @@ add_executable(demo main.cpp) target_link_libraries(demo PRIVATE - matador + matador-core + matador-orm ${CMAKE_DL_LIBS} ${SQLite3_LIBRARIES} ) diff --git a/demo/main.cpp b/demo/main.cpp index 618b70c..16b4892 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,13 +1,18 @@ #include "matador/sql/column.hpp" -#include "matador/sql/condition.hpp" -#include "matador/sql/schema.hpp" #include "matador/sql/connection.hpp" -#include "matador/sql/entity.hpp" +#include "matador/sql/query_macro.hpp" + +#include "matador/query/condition.hpp" + +#include "matador/object/object_ptr.hpp" +#include "matador/object/schema.hpp" #include "matador/utils/access.hpp" +#include "matador/utils/default_type_traits.hpp" #include "matador/utils/enum_mapper.hpp" +#include "matador/utils/types.hpp" -#include "matador/sql/query_helper.hpp" +#include "matador/sql/query_macro.hpp" #include #include @@ -22,7 +27,7 @@ struct author { template void process( Operator& op ) { - namespace field = matador::utils::access; + 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 ); @@ -34,13 +39,13 @@ struct author { struct book { unsigned long id{}; - matador::sql::entity book_author; + matador::object::object_ptr book_author; std::string title; unsigned short published_in{}; template void process( Operator& op ) { - namespace field = matador::utils::access; + namespace field = matador::access; field::primary_key( op, "id", id ); field::attribute( op, "title", title, 511 ); field::has_one( op, "author_id", book_author, matador::utils::default_foreign_attributes ); @@ -53,7 +58,7 @@ struct payload { template void process( Operator& op ) { - namespace field = matador::utils::access; + namespace field = matador::access; field::primary_key( op, "id", id ); } }; @@ -73,7 +78,7 @@ struct job { }; unsigned long id{}; - matador::sql::entity payload; + matador::object::object_ptr payload; std::string type; std::string description; job_state state; @@ -81,7 +86,7 @@ struct job { template void process( Operator& op ) { - namespace field = matador::utils::access; + namespace field = matador::access; field::primary_key( op, "id", id ); field::belongs_to( op, "payload", payload, matador::utils::default_foreign_attributes ); field::attribute( op, "type", type, 511 ); @@ -105,13 +110,10 @@ static const matador::utils::enum_mapper job_mode_enum({ }); template<> -struct matador::sql::data_type_traits -{ - inline static data_type_t builtin_type(std::size_t size) - { return data_type_traits::builtin_type(size); } +struct matador::utils::data_type_traits { + static basic_type type(const std::size_t size) { return data_type_traits::type(size); } - static void read_value(query_result_reader &reader, const char *id, size_t index, job::job_state &value) - { + static void read_value(attribute_reader &reader, const char *id, const size_t index, job::job_state &value) { std::string enum_string; reader.read_value(id, index, enum_string, 64); if (const auto enum_opt = job_state_enum.to_enum(enum_string)) { @@ -119,25 +121,16 @@ struct matador::sql::data_type_traits } } - static any_type create_value(const job::job_state &value) - { - return job_state_enum.to_string(value); - } - - static void bind_value(parameter_binder &binder, size_t index, job::job_state &value) - { - binder.bind(index, job_state_enum.to_string(value)); + static void write_value(attribute_writer &binder, const size_t index, const job::job_state &value) { + binder.write_value(index, job_state_enum.to_string(value)); } }; template<> -struct matador::sql::data_type_traits -{ - inline static data_type_t builtin_type(std::size_t size) - { return data_type_traits::builtin_type(size); } +struct matador::utils::data_type_traits { + static basic_type type( const std::size_t size) { return data_type_traits::type(size); } - static void read_value(query_result_reader &reader, const char *id, size_t index, job::job_mode &value) - { + static void read_value(attribute_reader &reader, const char *id, const size_t index, job::job_mode &value) { std::string enum_string; reader.read_value(id, index, enum_string, 64); if (const auto enum_opt = job_mode_enum.to_enum(enum_string)) { @@ -145,14 +138,8 @@ struct matador::sql::data_type_traits } } - static any_type create_value(const job::job_mode &value) - { - return job_mode_enum.to_string(value); - } - - static void bind_value(parameter_binder &binder, size_t index, job::job_mode &value) - { - binder.bind(index, job_mode_enum.to_string(value)); + static void bind_value(attribute_writer &binder, const size_t index, const job::job_mode &value) { + binder.write_value(index, job_mode_enum.to_string(value)); } }; @@ -168,7 +155,7 @@ QUERY_HELPER( temporary_table, id ); int main() { using namespace matador::sql; - using namespace matador; + using namespace matador::object; const std::string env_var{"MATADOR_BACKENDS_PATH"}; @@ -178,118 +165,118 @@ int main() { s.attach( "books" ); connection c( dns ); - c.open(); - s.create( c ); - - auto create_authors_sql = c.query( s ) - .create() - .table( qh::authors ) - .execute(); - - c.query( s ) - .create() - .table( qh::books ) - .execute(); - - std::cout << "SQL: " << create_authors_sql << "\n"; - - author mc; - mc.id = 1; - mc.first_name = "Michael"; - mc.last_name = "Crichton"; - mc.date_of_birth = "19.8.1954"; - mc.year_of_birth = 1954; - mc.distinguished = true; - auto insert_authors_sql = c.query( s ) - .insert() - .into( qh::authors ) - .values( mc ) - .execute(); - - std::cout << "SQL: " << insert_authors_sql << "\n"; - - auto result = c.query( s ) - .select( qh::authors.columns ) - .from( qh::authors ) - .fetch_all(); - - for (const auto& row: result) { std::cout << "Author " << row.at( qh::authors.first_name ) << "\n"; } - - auto update_authors_sql = c.query( s ) - .update( qh::authors ) - .set( {{qh::authors.first_name, "Stephen"}, - {qh::authors.last_name, "King"}} ) - .where( qh::authors.last_name == "Crichton" ) - .execute(); - - std::cout << "SQL: " << update_authors_sql << "\n"; - - auto authors = c.query( s ) - .select( qh::authors.columns ) - .from( qh::authors ) - .fetch_all(); - - for (const auto& a: authors) { std::cout << "Author " << a.first_name << "\n"; } - - c.query( s ) - .insert() - .into( qh::books ) - .values( {2, "It", mc.id, 1980} ) - .execute(); - - c.query( s ) - .insert() - .into( qh::books ) - .values( {3, "Misery", mc.id, 1984} ) - .execute(); - - auto select_books_sql = c.query( s ) - .select( qh::books.columns, {qh::authors.last_name} ) - .from( qh::books ) - .join_left( qh::authors ) - .on( qh::books.author_id == qh::authors.id ) - .where( qh::books.published_in < 2008 && qh::authors.last_name == "King" ) - .group_by( qh::books.published_in ) - .order_by( qh::books.title ).asc() - .limit( 5 ) - .offset( 2 ) - .fetch_all(); - - for (const auto& r: select_books_sql) { std::cout << "R: " << r.at( qh::books.title ) << ", " << r.at( qh::authors.last_name ) << "\n"; } - // SELECT book.title, book.id, book.author_id, book.published_in, author.name - // FROM book - // INNER JOIN author ON book.author_id = author.id - // WHERE book.published_in < 2008 AND author.name = "Michael Crichton" - // ORDER BY "book.title" ASC - // OFFSET 2 LIMIT 5 - - c.query( s ).drop().table( qh::books ).execute(); - - auto drop_authors_sql = c.query( s ) - .drop() - .table( qh::authors ) - .execute(); - - std::cout << "SQL: " << drop_authors_sql << "\n"; - - auto res = c.query( s ) - .select( {qh::payload.id} ) - .from( qh::payload ) - .join_left( qh::job ) - .on( qh::job.payload == qh::payload.id ) - .where( - in( qh::payload.id, c.query( s ) - .select( {qh::job.state} ) - .from( qh::job ) - .where( qh::job.state == job::job_state::Running ) - ) && - in( qh::payload.id, c.query( s ) - .select( {qh::temporary_table.id} ) - .from( qh::temporary_table ) ) - ) - .build(); - // .fetch_value(); - std::cout << "SQL: " << res.sql << "\n"; + auto result = c.open(); + // s.create( c ); + // + // auto create_authors_sql = c.query( s ) + // .create() + // .table( qh::authors ) + // .execute(); + // + // c.query( s ) + // .create() + // .table( qh::books ) + // .execute(); + // + // std::cout << "SQL: " << create_authors_sql << "\n"; + // + // author mc; + // mc.id = 1; + // mc.first_name = "Michael"; + // mc.last_name = "Crichton"; + // mc.date_of_birth = "19.8.1954"; + // mc.year_of_birth = 1954; + // mc.distinguished = true; + // auto insert_authors_sql = c.query( s ) + // .insert() + // .into( qh::authors ) + // .values( mc ) + // .execute(); + // + // std::cout << "SQL: " << insert_authors_sql << "\n"; + // + // auto result = c.query( s ) + // .select( qh::authors.columns ) + // .from( qh::authors ) + // .fetch_all(); + // + // for (const auto& row: result) { std::cout << "Author " << row.at( qh::authors.first_name ) << "\n"; } + // + // auto update_authors_sql = c.query( s ) + // .update( qh::authors ) + // .set( {{qh::authors.first_name, "Stephen"}, + // {qh::authors.last_name, "King"}} ) + // .where( qh::authors.last_name == "Crichton" ) + // .execute(); + // + // std::cout << "SQL: " << update_authors_sql << "\n"; + // + // auto authors = c.query( s ) + // .select( qh::authors.columns ) + // .from( qh::authors ) + // .fetch_all(); + // + // for (const auto& a: authors) { std::cout << "Author " << a.first_name << "\n"; } + // + // c.query( s ) + // .insert() + // .into( qh::books ) + // .values( {2, "It", mc.id, 1980} ) + // .execute(); + // + // c.query( s ) + // .insert() + // .into( qh::books ) + // .values( {3, "Misery", mc.id, 1984} ) + // .execute(); + // + // auto select_books_sql = c.query( s ) + // .select( qh::books.columns, {qh::authors.last_name} ) + // .from( qh::books ) + // .join_left( qh::authors ) + // .on( qh::books.author_id == qh::authors.id ) + // .where( qh::books.published_in < 2008 && qh::authors.last_name == "King" ) + // .group_by( qh::books.published_in ) + // .order_by( qh::books.title ).asc() + // .limit( 5 ) + // .offset( 2 ) + // .fetch_all(); + // + // for (const auto& r: select_books_sql) { std::cout << "R: " << r.at( qh::books.title ) << ", " << r.at( qh::authors.last_name ) << "\n"; } + // // SELECT book.title, book.id, book.author_id, book.published_in, author.name + // // FROM book + // // INNER JOIN author ON book.author_id = author.id + // // WHERE book.published_in < 2008 AND author.name = "Michael Crichton" + // // ORDER BY "book.title" ASC + // // OFFSET 2 LIMIT 5 + // + // c.query( s ).drop().table( qh::books ).execute(); + // + // auto drop_authors_sql = c.query( s ) + // .drop() + // .table( qh::authors ) + // .execute(); + // + // std::cout << "SQL: " << drop_authors_sql << "\n"; + // + // auto res = c.query( s ) + // .select( {qh::payload.id} ) + // .from( qh::payload ) + // .join_left( qh::job ) + // .on( qh::job.payload == qh::payload.id ) + // .where( + // in( qh::payload.id, c.query( s ) + // .select( {qh::job.state} ) + // .from( qh::job ) + // .where( qh::job.state == job::job_state::Running ) + // ) && + // in( qh::payload.id, c.query( s ) + // .select( {qh::temporary_table.id} ) + // .from( qh::temporary_table ) ) + // ) + // .build(); + // // .fetch_value(); + // std::cout << "SQL: " << res.sql << "\n"; return 0; } \ No newline at end of file diff --git a/include/matador/object/object_ptr.hpp b/include/matador/object/object_ptr.hpp new file mode 100644 index 0000000..6763deb --- /dev/null +++ b/include/matador/object/object_ptr.hpp @@ -0,0 +1,17 @@ +#ifndef OBJECT_PTR_HPP +#define OBJECT_PTR_HPP + +namespace matador::object { +template +class object_ptr { +public: + Type* operator->() { return ptr; }; + Type& operator*() { return *ptr; }; + +private: + Type* ptr{nullptr}; +}; + +} + +#endif //OBJECT_PTR_HPP diff --git a/include/matador/object/schema.hpp b/include/matador/object/schema.hpp index 8fef66c..5878e8a 100644 --- a/include/matador/object/schema.hpp +++ b/include/matador/object/schema.hpp @@ -18,7 +18,10 @@ class schema { public: typedef const_schema_node_iterator const_iterator; /**< Shortcut for the list const iterator. */ - schema(); + /** + * Creates an empty schema + */ + explicit schema( const std::string& name = ""); template utils::result attach(const std::string name, const std::string &parent = "") { @@ -36,6 +39,11 @@ public: utils::result attach(const std::string name) { auto node = schema_node::make_node(*this, name); + auto result = attach_node(node, std::type_index(typeid(ParentType))); + if (!result) { + return utils::failure(result.err()); + } + return utils::ok(); } @@ -53,26 +61,46 @@ public: */ [[nodiscard]] const_iterator end() const; + /** + * Returns true if the schema contains + * no schema nodes. + * + * @return True if schema is empty + */ [[nodiscard]] bool empty() const; + /** + * Returns the current number of schema node. + * + * @return Number of schema nodes + */ [[nodiscard]] size_t size() const; + /** + * Returns the name of the schema. + * + * @return The name of the schema + */ + [[nodiscard]] std::string name() const; + private: - using t_node_map = std::unordered_map>; - // type_index -> [name -> prototype] - using t_type_index_node_map = std::unordered_map; using node_ptr = std::shared_ptr; + using t_node_map = std::unordered_map; + using t_type_index_node_map = std::unordered_map; - utils::result, utils::error> attach_node(const std::shared_ptr &node, - const std::string &parent); - utils::result, utils::error> find_parent(const std::string &name) const; - utils::result, utils::error> find_node(const std::string &name) const; + [[nodiscard]] utils::result, utils::error> attach_node(const std::shared_ptr &node, + const std::string &parent); + [[nodiscard]] utils::result, utils::error> attach_node(const std::shared_ptr &node, + const std::type_index &type_index); + [[nodiscard]] utils::result, utils::error> find_node(const std::string &name) const; + [[nodiscard]] utils::result, utils::error> find_node(const std::type_index &type_index) const; - void push_back_child(const node_ptr &parent, const node_ptr &child); + [[nodiscard]] bool has_node(const std::type_index& index, const std::string &name) const; - bool has_node(const std::type_index& index, const std::string &name) const; + static void push_back_child(const node_ptr &parent, const node_ptr &child); private: + std::string name_; std::shared_ptr root_; t_node_map node_map_; diff --git a/include/matador/object/schema_node.hpp b/include/matador/object/schema_node.hpp index 30d9421..69b3bb9 100644 --- a/include/matador/object/schema_node.hpp +++ b/include/matador/object/schema_node.hpp @@ -12,6 +12,8 @@ class schema; class schema_node final { public: + using node_ptr = std::shared_ptr; + template < typename Type > static std::shared_ptr make_node(schema& tree, const std::string& name) { return std::shared_ptr(new schema_node(tree, name, static_cast(nullptr))); @@ -41,6 +43,9 @@ public: */ void insert(const std::shared_ptr &child); + [[nodiscard]] node_ptr next() const; + [[nodiscard]] node_ptr prev() const; + private: explicit schema_node(schema& tree); template < typename Type > diff --git a/include/matador/object/schema_node_iterator.hpp b/include/matador/object/schema_node_iterator.hpp index 60503e3..2bc64d9 100644 --- a/include/matador/object/schema_node_iterator.hpp +++ b/include/matador/object/schema_node_iterator.hpp @@ -81,7 +81,7 @@ public: * * @return Returns iterator before incrementing. */ - const_schema_node_iterator operator++(int); + const_schema_node_iterator operator++( int ); /** * Pre increments the iterator diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index 0878dcc..970305e 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -10,7 +10,6 @@ #include namespace matador::sql { -class schema; class connection_impl; /** diff --git a/include/matador/sql/query_macro.hpp b/include/matador/sql/query_macro.hpp new file mode 100644 index 0000000..0aaf0f9 --- /dev/null +++ b/include/matador/sql/query_macro.hpp @@ -0,0 +1,24 @@ +#ifndef QUERY_QUERY_HELPER_HPP +#define QUERY_QUERY_HELPER_HPP + +#include "matador/utils/macro_map.hpp" + +#include "matador/sql/table.hpp" +#include "matador/sql/column.hpp" + +#include +#include + +#define FIELD(x) const sql::column x{*this, #x, ""}; + +#define QUERY_HELPER(C, ...) \ +namespace matador::qh { \ +namespace internal { \ +struct C##_query : sql::table { \ +C##_query() : table(#C) {} \ +MAP(FIELD, __VA_ARGS__) \ +}; } \ +static const internal:: C##_query C; \ +} + +#endif //QUERY_QUERY_HELPER_HPP diff --git a/include/matador/utils/macro_map.hpp b/include/matador/utils/macro_map.hpp new file mode 100644 index 0000000..dee5f31 --- /dev/null +++ b/include/matador/utils/macro_map.hpp @@ -0,0 +1,42 @@ +#ifndef QUERY_MACRO_MAP_HPP +#define QUERY_MACRO_MAP_HPP + +#define EVAL0(...) __VA_ARGS__ +#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) +#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) +#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) +#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) +#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__))) + +#define MAP_END(...) +#define MAP_OUT +#define MAP_COMMA , + +#define MAP_GET_END2() 0, MAP_END +#define MAP_GET_END1(...) MAP_GET_END2 +#define MAP_GET_END(...) MAP_GET_END1 +#define MAP_NEXT0(test, next, ...) next MAP_OUT +#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0) +#define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next) + +#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__) +#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__) + +#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0) +#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next) + +#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__) +#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__) + +/** + * Applies the function macro `f` to each of the remaining parameters. + */ +#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +/** + * Applies the function macro `f` to each of the remaining parameters and + * inserts commas between the results. + */ +#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#endif //QUERY_MACRO_MAP_HPP diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index c5076fb..2dd5531 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(matador-core STATIC ../../include/matador/utils/foreign_attributes.hpp ../../include/matador/utils/identifier.hpp ../../include/matador/utils/library.hpp + ../../include/matador/utils/macro_map.hpp ../../include/matador/utils/os.hpp ../../include/matador/utils/placeholder.hpp ../../include/matador/utils/result.hpp diff --git a/source/core/object/schema.cpp b/source/core/object/schema.cpp index 4033efb..140ed6f 100644 --- a/source/core/object/schema.cpp +++ b/source/core/object/schema.cpp @@ -6,8 +6,9 @@ utils::error make_error(const error_code ec, const std::string& msg) { return utils::error(ec, msg); } -schema::schema() -: root_(std::shared_ptr(new schema_node(*this))) { +schema::schema( const std::string& name) +: name_(name) +, root_(std::shared_ptr(new schema_node(*this))) { root_->first_child_ = std::shared_ptr(new schema_node(*this)); root_->last_child_ = std::shared_ptr(new schema_node(*this)); root_->first_child_->next_sibling_ = root_->last_child_; @@ -30,64 +31,67 @@ size_t schema::size() const { return static_cast(std::distance(begin(), end())); } +std::string schema::name() const { + return name_; +} + utils::result, utils::error> schema::attach_node(const std::shared_ptr &node, const std::string &parent) { - if (!has_node(node->type_index(), node->name())) { + if (has_node(node->type_index(), node->name())) { return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + node->name() + "' already exists.")); } // set node to root node auto parent_node = root_; - auto result = find_parent(parent); + if (!parent.empty()) { + auto result = find_node(parent); + if (!result.is_ok() && result.err().ec() != error_code::NodeNotFound) { + return result; + } + parent_node = *result; + } + + push_back_child(parent_node, node); + + // Todo: check return value + node_map_.insert({node->name(), node})/*.first*/; + type_index_node_map_.insert({node->type_index(), node}); + + return utils::ok(node); +} + +utils::result, utils::error> schema::attach_node( const std::shared_ptr& node, + const std::type_index& type_index ) { + if (has_node(node->type_index(), node->name())) { + return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + node->name() + "' already exists.")); + } + auto result = find_node(type_index); if (!result.is_ok() && result.err().ec() != error_code::NodeNotFound) { return result; } - parent_node = *result; - push_back_child(root_, node); + push_back_child(*result, node); - // if (!pk.is_null()) { - // node->primary_key_ = pk; - // } - // store prototype in map // Todo: check return value - node_map_.insert(std::make_pair(node->name(), node))/*.first*/; - type_index_node_map_[node->type_index()].insert(std::make_pair(node->name(), node)); + node_map_.insert({node->name(), node})/*.first*/; + type_index_node_map_.insert({node->type_index(), node}); - // return nptr.release(); - return {}; -} - -utils::result, utils::error> schema::find_parent(const std::string &name) const { - if (name.empty()) { - return utils::failure(make_error(error_code::Failure, "Name of parent cannot be empty")); - } - - return find_node(name); + return utils::ok(node); } utils::result, utils::error> schema::find_node(const std::string &name) const { // first search in the prototype map const auto i = node_map_.find(name); if (i == node_map_.end()) { - // if not found search in the typeid to prototype map - // const auto j = type_index_node_map_.find(name); - // if (j == type_index_node_map_.end()) { - // return utils::failure( - // make_error(error_code::NodeNotFound, std::string("Could not find node with name '") + name + "'")); - // } - // const t_node_map &val = j->second; - /* - * if size is greater one (1) the name - * is a typeid and has more than one prototype - * node and therefor it is not unique and an - * exception is thrown - */ - // if (val.size() > 1) { - // return utils::failure(make_error(error_code::NodeNotFound, std::string("Type id '") + name + "' is not unique")); - // } - // // return the only prototype - // return utils::ok(val.begin()->second); + return utils::failure(make_error(error_code::NodeNotFound, "Couldn't find node by name '" + name + "'")); + } + return utils::ok(i->second); +} + +utils::result, utils::error> schema::find_node( const std::type_index& type_index ) const { + const auto i = type_index_node_map_.find(type_index); + if (i == type_index_node_map_.end()) { + return utils::failure(make_error(error_code::NodeNotFound, "Couldn't find node by type '" + std::string(type_index.name()) + "'")); } return utils::ok(i->second); } @@ -96,6 +100,12 @@ void schema::push_back_child(const node_ptr &parent, const node_ptr &child) { child->parent_ = parent; child->previous_sibling_ = parent->last_child_->previous_sibling_; child->next_sibling_ = parent->last_child_; + /* + * +-----------------------------<- (first) parent (last) -> ----------------------------+ + * | | + * first (next) -> <- (prev) child_1 (next) -> <- (prev) new_child (next) -> <- (prev) last + * ^^^^^^^ inserted ^^^^^^ + */ parent->last_child_->previous_sibling_->next_sibling_ = child; parent->last_child_->previous_sibling_ = child; // set depth diff --git a/source/core/object/schema_node.cpp b/source/core/object/schema_node.cpp index d6257cc..f6996c8 100644 --- a/source/core/object/schema_node.cpp +++ b/source/core/object/schema_node.cpp @@ -57,4 +57,38 @@ void schema_node::insert(const std::shared_ptr &child) { // // 3. last // child->op_last = op_last; } + +schema_node::node_ptr schema_node::next() const { + // if we have a child, child is the next iterator to return + // (if we don't do iterate over the siblings) + if (first_child_ && first_child_->next_sibling_ != last_child_) { + return first_child_->next_sibling_; + } + // if there is no child, we check for sibling + // if there is a sibling, this is our next iterator to return + // if not, we go back to the parent + auto *node = this; + while (node->parent_ && node->next_sibling_ == node->parent_->last_child_) { + node = node->parent_.get(); + } + return node->parent_ ? node->next_sibling_ : node->last_child_; +} + +schema_node::node_ptr schema_node::prev() const { + // if node has a previous sibling, we set it + // as our next iterator. then we check if there + // are last children. if so, we set the last-last + // child as our iterator + if (previous_sibling_ && previous_sibling_->previous_sibling_) { + auto node = previous_sibling_; + while (node->last_child_ && node->first_child_->next_sibling_ != node->last_child_) { + node = node->last_child_->previous_sibling_; + } + return node; + } + // if there is no previous sibling, our next iterator + // is the parent of the node + return parent_->parent_ ? parent_ : parent_->first_child_->next_sibling_; +} + } diff --git a/source/core/object/schema_node_iterator.cpp b/source/core/object/schema_node_iterator.cpp index 1a65373..82f1e97 100644 --- a/source/core/object/schema_node_iterator.cpp +++ b/source/core/object/schema_node_iterator.cpp @@ -24,8 +24,7 @@ const_schema_node_iterator& const_schema_node_iterator::operator++() return *this; } -const_schema_node_iterator const_schema_node_iterator::operator++(int) -{ +const_schema_node_iterator const_schema_node_iterator::operator++( int ) { const std::shared_ptr tmp = node_; increment(); return const_schema_node_iterator(tmp); @@ -65,42 +64,15 @@ void const_schema_node_iterator::increment() return; } - // if we have a child, child is the next iterator to return - // (if we don't do iterate over the siblings) - if (node_->first_child_ && node_->first_child_->next_sibling_ != node_->last_child_) { - node_ = node_->first_child_->next_sibling_; - } else { - // if there is no child, we check for sibling - // if there is a sibling, this is our next iterator to return - // if not, we go back to the parent - std::shared_ptr node = node_; - while (node_->parent_ && node_->next_sibling_ == node_->parent_->last_child_) { - node = node->parent_; - } - node_ = node_->next_sibling_; - } - + node_ = node_->next(); } void const_schema_node_iterator::decrement() { if (!node_) { return; } - // if node has a previous sibling, we set it - // as our next iterator. then we check if there - // are last children. if so, we set the last-last - // child as our iterator - if (node_->previous_sibling_ && node_->previous_sibling_->previous_sibling_) { - std::shared_ptr node = node_->previous_sibling_; - while (node_->last_child_ && node_->first_child_->next_sibling_ != node_->last_child_) { - node = node->last_child_->previous_sibling_; - } - node_ = node; - // if there is no previous sibling, our next iterator - // is the parent of the node - } else { - node_ = node_->parent_; - } + + node_ = node_->prev(); } } \ No newline at end of file diff --git a/source/orm/CMakeLists.txt b/source/orm/CMakeLists.txt index 470b6ec..bdebd83 100644 --- a/source/orm/CMakeLists.txt +++ b/source/orm/CMakeLists.txt @@ -59,6 +59,7 @@ add_library(matador-orm STATIC ../../include/matador/sql/internal/object_result_binder.hpp ../../include/matador/sql/internal/query_result_impl.hpp ../../include/matador/sql/query_context.hpp + ../../include/matador/sql/query_macro.hpp ../../include/matador/sql/query_result.hpp ../../include/matador/sql/record.hpp ../../include/matador/sql/statement.hpp diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index d44a1d3..8c432d8 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -12,7 +12,7 @@ add_executable(CoreTests utils/FieldAttributeTest.cpp utils/VersionTest.cpp utils/StringTest.cpp - object/PrototypeTreeTest.cpp + object/SchemaTest.cpp ) target_link_libraries(CoreTests matador-core Catch2::Catch2WithMain) diff --git a/test/core/object/PrototypeTreeTest.cpp b/test/core/object/SchemaTest.cpp similarity index 52% rename from test/core/object/PrototypeTreeTest.cpp rename to test/core/object/SchemaTest.cpp index 00d71c3..0f87022 100644 --- a/test/core/object/PrototypeTreeTest.cpp +++ b/test/core/object/SchemaTest.cpp @@ -1,35 +1,52 @@ -#include - -#include "matador/object/schema.hpp" - -struct node {}; - -using namespace matador; - -struct person { - virtual ~person() = default; -}; - -struct student final : person {}; -struct teacher final : person {}; - -TEST_CASE("Test empty prototype tree", "[prototype_tree][empty]") { - const object::schema tree; - - REQUIRE( tree.empty() ); -} - -TEST_CASE("Test add type to prototype tree", "[prototype_tree][add]") { - object::schema tree; - - REQUIRE( tree.empty() ); - - auto res = tree.attach("person"); - REQUIRE( res.is_ok() ); - res = tree.attach("student"); - REQUIRE( res.is_ok() ); - res = tree.attach("teacher"); - REQUIRE( res.is_ok() ); - - REQUIRE( tree.size() == 3 ); -} +#include + +#include "matador/object/schema.hpp" + +struct node {}; + +using namespace matador; + +struct person { + virtual ~person() = default; +}; + +struct student final : person {}; +struct teacher final : person {}; + +TEST_CASE("Test empty prototype tree", "[schema_node][empty]") { + const object::schema tree; + + REQUIRE( tree.empty() ); +} + +TEST_CASE("Test add type to prototype tree", "[schema_node][add]") { + object::schema tree; + + REQUIRE( tree.empty() ); + + auto res = tree.attach("person"); + REQUIRE( res.is_ok() ); + res = tree.attach("student"); + REQUIRE( res.is_ok() ); + res = tree.attach("teacher"); + REQUIRE( res.is_ok() ); + + REQUIRE( !tree.empty() ); + REQUIRE( tree.size() == 3 ); +} + +TEST_CASE("Test next and previous of schema node", "[schema_node][next][previous]") { + object::schema tree; + + REQUIRE( tree.empty() ); + + auto res = tree.attach("person"); + REQUIRE( res.is_ok() ); + + REQUIRE( tree.size() == 1 ); + + auto it = tree.begin(); + REQUIRE( it->name() == "person" ); + REQUIRE( (--it)->name() == "person" ); + REQUIRE( ++it == tree.end() ); +} \ No newline at end of file