diff --git a/backends/sqlite/src/sqlite_query_result.cpp b/backends/sqlite/src/sqlite_query_result.cpp index 31a1832..9ee758d 100644 --- a/backends/sqlite/src/sqlite_query_result.cpp +++ b/backends/sqlite/src/sqlite_query_result.cpp @@ -183,36 +183,53 @@ bool sqlite_query_result::fetch() return ++row_index_ < result_.size(); } +template < typename Type > +void convert(const char *valstr, sql::any_type &value) +{ + Type val{}; + read(val, valstr); + value = val; +} + void sqlite_query_result::read_value(const char *id, size_t index, sql::any_type &value, sql::data_type_t type, size_t size) { switch (type) { case sql::data_type_t::type_char: + convert(result_[row_index_][index], value); + break; case sql::data_type_t::type_short: + convert(result_[row_index_][index], value); + break; case sql::data_type_t::type_int: + convert(result_[row_index_][index], value); + break; case sql::data_type_t::type_long: - case sql::data_type_t::type_long_long: { - long long val{}; - read(val, result_[row_index_][index]); - value = val; + convert(result_[row_index_][index], value); + break; + case sql::data_type_t::type_long_long: + convert(result_[row_index_][index], value); break; - } case sql::data_type_t::type_unsigned_char: + convert(result_[row_index_][index], value); + break; case sql::data_type_t::type_unsigned_short: + convert(result_[row_index_][index], value); + break; case sql::data_type_t::type_unsigned_int: + convert(result_[row_index_][index], value); + break; case sql::data_type_t::type_unsigned_long: - case sql::data_type_t::type_unsigned_long_long: { - unsigned long long val{}; - read(val, result_[row_index_][index]); - value = val; + convert(result_[row_index_][index], value); + break; + case sql::data_type_t::type_unsigned_long_long: + convert(result_[row_index_][index], value); break; - } case sql::data_type_t::type_float: - case sql::data_type_t::type_double: { - double val{}; - read(val, result_[row_index_][index]); - value = val; + convert(result_[row_index_][index], value); + break; + case sql::data_type_t::type_double: + convert(result_[row_index_][index], value); break; - } case sql::data_type_t::type_bool: { int val{}; read(val, result_[row_index_][index]); diff --git a/include/matador/sql/any_type.hpp b/include/matador/sql/any_type.hpp index 9999598..2bf6a83 100644 --- a/include/matador/sql/any_type.hpp +++ b/include/matador/sql/any_type.hpp @@ -9,8 +9,8 @@ namespace matador::sql { using any_type = std::variant< char, short, int, long, long long, unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, - bool, float, double, + bool, const char*, std::string, nullptr_t>; diff --git a/include/matador/sql/column.hpp b/include/matador/sql/column.hpp index abe15fb..2906cec 100644 --- a/include/matador/sql/column.hpp +++ b/include/matador/sql/column.hpp @@ -6,7 +6,8 @@ #include "matador/utils/field_attributes.hpp" -#include +#include +#include namespace matador::sql { @@ -37,6 +38,17 @@ public: [[nodiscard]] const std::string& ref_table() const; [[nodiscard]] const std::string& ref_column() const; + template< typename Type > + [[nodiscard]] bool is_type_of() const { + return std::holds_alternative(value_); + } + + template< typename Type > + std::optional value() const { + const Type* ptr= std::get_if(&value_); + return ptr ? std::make_optional(*ptr) : std::nullopt; + } + private: template void process(Operator &op) @@ -44,9 +56,13 @@ private: op.on_attribute(name_.c_str(), value_, type_, attributes_); } + using data_type_index = std::vector; + private: friend class record; + static const data_type_index data_type_index_; + std::string name_; utils::field_attributes attributes_; data_type_t type_{}; diff --git a/include/matador/sql/column_generator.hpp b/include/matador/sql/column_generator.hpp index 890e335..888af3d 100644 --- a/include/matador/sql/column_generator.hpp +++ b/include/matador/sql/column_generator.hpp @@ -2,6 +2,7 @@ #define QUERY_COLUMN_GENERATOR_HPP #include "matador/sql/column.hpp" +#include "matador/sql/table_repository.hpp" #include "matador/sql/types.hpp" #include "matador/utils/access.hpp" @@ -18,10 +19,10 @@ public: fk_column_generator() = default; template - column generate(const char *id, Type &x) + column generate(const char *id, Type &x, const std::string &ref_table, const std::string &ref_column) { utils::access::process(*this, x); - return column{id, type_, { utils::constraints::FOREIGN_KEY }}; + return column{id, type_, ref_table, ref_column, { utils::constraints::FOREIGN_KEY }}; } template @@ -50,16 +51,16 @@ private: class column_generator { private: - explicit column_generator(std::vector &columns); + column_generator(std::vector &columns, const table_repository &repo); public: ~column_generator() = default; template < class Type > - static std::vector generate() + static std::vector generate(const table_repository &repo) { std::vector columns; - column_generator gen(columns); + column_generator gen(columns, repo); Type obj; matador::utils::access::process(gen, obj); return std::move(columns); @@ -76,21 +77,27 @@ public: template void on_belongs_to(const char *id, Pointer &x, utils::cascade_type) { - columns_.push_back(fk_column_generator_.generate(id, *x)); + 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, utils::cascade_type) { - columns_.push_back(fk_column_generator_.generate(id, *x)); + 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(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {} template void on_has_many(const char *, ContainerType &, utils::cascade_type) {} +private: + std::pair determine_foreign_ref(const std::type_index &ti); + private: size_t index_ = 0; std::vector &columns_; + const table_repository &repo_; fk_column_generator fk_column_generator_; }; diff --git a/include/matador/sql/foreign.hpp b/include/matador/sql/foreign.hpp index f8700b4..0fa92bc 100644 --- a/include/matador/sql/foreign.hpp +++ b/include/matador/sql/foreign.hpp @@ -9,18 +9,22 @@ template < class Type > class foreign { public: + using value_type = Type; + using pointer = value_type*; + using reference = value_type&; + foreign() = default; explicit foreign(Type *obj) : obj_(obj) {} - Type* operator->() { return obj_.get(); } - const Type* operator->() const { return obj_.get(); } + pointer operator->() { return obj_.get(); } + const value_type* operator->() const { return obj_.get(); } - Type& operator*() { return *obj_; } - const Type& operator*() const { return *obj_; } + reference operator*() { return *obj_; } + const value_type& operator*() const { return *obj_; } private: - std::unique_ptr obj_; + std::unique_ptr obj_; }; template diff --git a/include/matador/sql/query_builder.hpp b/include/matador/sql/query_builder.hpp index 1935413..c5fed9d 100644 --- a/include/matador/sql/query_builder.hpp +++ b/include/matador/sql/query_builder.hpp @@ -11,7 +11,6 @@ #include #include - namespace matador::sql { class dialect; @@ -97,6 +96,7 @@ public: query_builder& create(); query_builder& drop(); query_builder& select(std::initializer_list column_names); + query_builder& select(const std::vector &column_names); query_builder& insert(); query_builder& update(const std::string &table); query_builder& remove(); diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 01227f5..0b3c3d5 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -9,6 +9,7 @@ #include "matador/sql/query_builder.hpp" #include "matador/sql/query_result.hpp" #include "matador/sql/record.hpp" +#include "matador/sql/table_repository.hpp" #include "matador/sql/value_extractor.hpp" #include @@ -142,14 +143,18 @@ public: class query_create_intermediate : query_intermediate { public: - using query_intermediate::query_intermediate; + query_create_intermediate(session &db, query_builder &query, table_repository &repo); query_execute_finish table(const std::string &table, std::initializer_list columns); template - query_execute_finish table(const std::string &table) + query_execute_finish table(const std::string &table_name) { - return {db(), query().table(table, column_generator::generate())}; + const auto &info = repository_.attach(table_name, record{column_generator::generate(repository_)}); + return {db(), query().table(table_name, info.prototype.columns())}; } + +private: + table_repository &repository_; }; class query_drop_intermediate : query_intermediate diff --git a/include/matador/sql/record.hpp b/include/matador/sql/record.hpp index 881683a..60bbbeb 100644 --- a/include/matador/sql/record.hpp +++ b/include/matador/sql/record.hpp @@ -23,6 +23,7 @@ public: record() = default; record(std::initializer_list columns); + explicit record(const std::vector &columns); record(const record&) = default; record& operator=(const record&) = default; record(record&&) noexcept = default; @@ -44,8 +45,13 @@ public: void append(column col); + bool has_primary_key() const; + const column& primary_key() const; + + [[nodiscard]] const std::vector& columns() const; + [[nodiscard]] const column& at(const std::string &name) const; - const column& at(size_t index) const; + [[nodiscard]] const column& at(size_t index) const; iterator find(const std::string &column_name); [[nodiscard]] const_iterator find(const std::string &column_name) const; @@ -62,9 +68,15 @@ public: [[nodiscard]] bool empty() const; void clear(); +private: + void init(); + void add_to_map(column &col, size_t index); + private: column_by_index columns_; column_by_name_map columns_by_name_; + + int pk_index_{-1}; }; } diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index 0e36add..b82b3e1 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -1,10 +1,14 @@ #ifndef QUERY_SESSION_HPP #define QUERY_SESSION_HPP +#include "matador/sql/column_name_generator.hpp" #include "matador/sql/connection.hpp" #include "matador/sql/connection_pool.hpp" #include "matador/sql/query_builder.hpp" #include "matador/sql/query_intermediates.hpp" +#include "matador/sql/table_repository.hpp" + +#include namespace matador::sql { @@ -15,6 +19,11 @@ public: query_create_intermediate create(); query_drop_intermediate drop(); + template < class Type > + query_select_intermediate select() + { + return query_select_intermediate{*this, query_.select(column_name_generator::generate())}; + } query_select_intermediate select(std::initializer_list column_names); query_insert_intermediate insert(); query_update_intermediate update(const std::string &table); @@ -23,9 +32,19 @@ public: query_result fetch(const std::string &sql); std::pair execute(const std::string &sql); + template + void attach(const std::string &table_name) + { + table_repository_.attach(table_name); + } + + [[nodiscard]] const table_repository& tables() const; + private: connection_pool &pool_; query_builder query_; + + table_repository table_repository_; }; } diff --git a/include/matador/sql/table_repository.hpp b/include/matador/sql/table_repository.hpp new file mode 100644 index 0000000..5bff18d --- /dev/null +++ b/include/matador/sql/table_repository.hpp @@ -0,0 +1,55 @@ +#ifndef QUERY_TABLE_REPOSITORY_HPP +#define QUERY_TABLE_REPOSITORY_HPP + +#include "matador/sql/record.hpp" + +#include +#include +#include +#include + +namespace matador::sql { + +struct table_info +{ + std::string name; + record prototype; +}; + +class table_repository +{ +public: + template + const table_info& attach(const std::string &table_name, const record &proto) + { + return attach(std::type_index(typeid(Type)), table_name, proto); + } + + const table_info& attach(std::type_index ti, const std::string &table_name, const record &proto); + const table_info& attach(std::type_index ti, const table_info& table); + + template + std::optional info() + { + return info(std::type_index(typeid(Type))); + } + + std::optional info(std::type_index ti); + + template + [[nodiscard]] std::pair reference() const + { + return reference(std::type_index(typeid(Type))); + } + + [[nodiscard]] std::pair reference(const std::type_index &ti) const; + +private: + using repository = std::unordered_map; + repository repository_; +}; +; + +} + +#endif //QUERY_TABLE_REPOSITORY_HPP diff --git a/include/matador/utils/constraints.hpp b/include/matador/utils/constraints.hpp index 95c5327..c518ef9 100644 --- a/include/matador/utils/constraints.hpp +++ b/include/matador/utils/constraints.hpp @@ -4,13 +4,14 @@ namespace matador::utils { enum class constraints : unsigned char { - NONE = 0, - NOT_NULL = 1 << 0, - INDEX = 1 << 1, - UNIQUE = 1 << 2, - PRIMARY_KEY = 1 << 3, - FOREIGN_KEY = 1 << 4, - DEFAULT = 1 << 5, + NONE = 0, + NOT_NULL = 1 << 0, + INDEX = 1 << 1, + UNIQUE = 1 << 2, + PRIMARY_KEY = 1 << 3, + FOREIGN_KEY = 1 << 4, + DEFAULT = 1 << 5, + AUTO_INCREMENT = 1 << 6, UNIQUE_NOT_NULL = UNIQUE | NOT_NULL }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2f9a399..ba8f6fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,8 @@ set(SQL_SOURCES sql/column_generator.cpp sql/column_name_generator.cpp sql/key_value_generator.cpp - sql/fk_value_extractor.cpp) + sql/fk_value_extractor.cpp + sql/table_repository.cpp) set(SQL_HEADER ../include/matador/sql/dialect.hpp @@ -42,7 +43,8 @@ set(SQL_HEADER ../include/matador/sql/any_type.hpp ../include/matador/sql/key_value_generator.hpp ../include/matador/sql/foreign.hpp - ../include/matador/sql/fk_value_extractor.hpp) + ../include/matador/sql/fk_value_extractor.hpp + "../include/matador/sql/table_repository.hpp") set(UTILS_HEADER ../include/matador/utils/field_attributes.hpp diff --git a/src/sql/column.cpp b/src/sql/column.cpp index 574ae3d..6e59d77 100644 --- a/src/sql/column.cpp +++ b/src/sql/column.cpp @@ -3,6 +3,27 @@ #include "matador/sql/column.hpp" namespace matador::sql { + +const column::data_type_index column::data_type_index_ { + data_type_t::type_char, + data_type_t::type_short, + data_type_t::type_int, + data_type_t::type_long, + data_type_t::type_long_long, + data_type_t::type_unsigned_char, + data_type_t::type_unsigned_short, + data_type_t::type_unsigned_int, + data_type_t::type_unsigned_long, + data_type_t::type_unsigned_long_long, + data_type_t::type_float, + data_type_t::type_double, + data_type_t::type_bool, + data_type_t::type_char_pointer, + data_type_t::type_varchar, + data_type_t::type_text, + data_type_t::type_null, +}; + column::column(std::string name, data_type_t type, utils::field_attributes attr) : name_(std::move(name)) , type_(type) diff --git a/src/sql/column_generator.cpp b/src/sql/column_generator.cpp index 8f1b36e..3846408 100644 --- a/src/sql/column_generator.cpp +++ b/src/sql/column_generator.cpp @@ -2,8 +2,10 @@ namespace matador::sql { -column_generator::column_generator(std::vector &columns) -: columns_(columns) {} +column_generator::column_generator(std::vector &columns, const table_repository &repo) +: columns_(columns) +, repo_(repo) +{} void column_generator::on_primary_key(const char *id, std::string &pk, size_t size) { @@ -15,6 +17,11 @@ void column_generator::on_revision(const char *id, unsigned long long int &x) on_attribute(id, x); } +std::pair column_generator::determine_foreign_ref(const std::type_index &ti) +{ + return repo_.reference(ti); +} + void fk_column_generator::on_primary_key(const char *, std::string &, size_t size) { type_ = data_type_traits::builtin_type(size); diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index 1619ddd..8b2dd9e 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -98,6 +98,11 @@ query_builder& query_builder::drop() { } query_builder& query_builder::select(std::initializer_list column_names) +{ + return select(std::vector{column_names}); +} + +query_builder& query_builder::select(const std::vector &column_names) { initialize(command_t::SELECT, state_t::QUERY_SELECT); diff --git a/src/sql/query_intermediates.cpp b/src/sql/query_intermediates.cpp index b96c38d..847c27f 100644 --- a/src/sql/query_intermediates.cpp +++ b/src/sql/query_intermediates.cpp @@ -100,6 +100,10 @@ query_execute_finish query_into_intermediate::values(std::initializer_list columns) { return {db(), query().table(table, columns)}; diff --git a/src/sql/record.cpp b/src/sql/record.cpp index c05a133..c023cce 100644 --- a/src/sql/record.cpp +++ b/src/sql/record.cpp @@ -1,13 +1,38 @@ #include "matador/sql/record.hpp" +#include + namespace matador::sql { record::record(std::initializer_list columns) -: columns_(columns) { - size_t index{0}; - for(auto &col : columns_) { - columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index++}); +: columns_(columns) +{ + init(); +} + +record::record(const std::vector &columns) +: columns_(columns) +{ + init(); +} + +bool record::has_primary_key() const +{ + return pk_index_ > -1; +} + +const column &record::primary_key() const +{ + if (!has_primary_key()) { + throw std::logic_error("record has no primary key"); } + + return columns_[pk_index_]; +} + +const std::vector &record::columns() const +{ + return columns_; } const column &record::at(const std::string &name) const @@ -34,7 +59,7 @@ record::const_iterator record::find(const std::string &column_name) const { void record::append(column col) { auto &ref = columns_.emplace_back(std::move(col)); - columns_by_name_.emplace(ref.name(), column_index_pair{std::ref(ref), columns_.size()-1}); + add_to_map(ref, columns_.size()-1); } record::iterator record::begin() @@ -83,4 +108,21 @@ void record::clear() columns_by_name_.clear(); } +void record::init() +{ + size_t index{0}; + for(auto &col : columns_) { + add_to_map(col, index++); +// columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index++}); + } +} + +void record::add_to_map(column &col, size_t index) +{ + columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index}); + if (utils::is_constraint_set(col.attributes().options(), utils::constraints::PRIMARY_KEY)) { + pk_index_ = static_cast(index); + } +} + } diff --git a/src/sql/session.cpp b/src/sql/session.cpp index 97221c0..caf8b8a 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -12,7 +12,7 @@ session::session(connection_pool &pool) query_create_intermediate session::create() { - return query_create_intermediate{*this, query_.create()}; + return query_create_intermediate{*this, query_.create(), table_repository_}; } query_drop_intermediate session::drop() @@ -55,4 +55,9 @@ std::pair session::execute(const std::string &sql) { } return c->execute(sql); } + +const table_repository& session::tables() const +{ + return table_repository_; +} } \ No newline at end of file diff --git a/src/sql/table_repository.cpp b/src/sql/table_repository.cpp new file mode 100644 index 0000000..b6697e1 --- /dev/null +++ b/src/sql/table_repository.cpp @@ -0,0 +1,39 @@ +#include "matador/sql/table_repository.hpp" + +#include + +namespace matador::sql { + +const table_info &table_repository::attach(std::type_index ti, const std::string &table_name, const record &proto) +{ + return attach(ti, table_info{table_name, proto}); +} + +const table_info& table_repository::attach(const std::type_index ti, const table_info& table) +{ + return repository_.try_emplace(ti, table).first->second; +} + +std::optional table_repository::info(std::type_index ti) +{ + const auto it = repository_.find(ti); + if (it == repository_.end()) { + return std::nullopt; + } + return it->second; +} + +std::pair table_repository::reference(const std::type_index &ti) const +{ + const auto it = repository_.find(ti); + if (it != repository_.end()) { + if (!it->second.prototype.has_primary_key()) { + throw std::logic_error("table doesn't has primary key"); + } + return { it->second.name, it->second.prototype.primary_key().name() }; + } + + return {}; +} + +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 026c6a3..33c64cb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,7 +20,10 @@ add_executable(tests builder.cpp column_name_generator.cpp value_generator.cpp models/category.hpp - models/supplier.hpp) + models/supplier.hpp + models/airplane.hpp + models/flight.hpp + models/person.hpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain matador diff --git a/test/column_generator.cpp b/test/column_generator.cpp index 766368d..f8ec55b 100644 --- a/test/column_generator.cpp +++ b/test/column_generator.cpp @@ -1,14 +1,16 @@ #include #include "matador/sql/column_generator.hpp" +#include "matador/sql/table_repository.hpp" #include "models/product.hpp" using namespace matador::sql; TEST_CASE("Generate columns from object", "[column generator]") { + table_repository repo; - auto columns = column_generator::generate(); + auto columns = column_generator::generate(repo); const std::vector expected_columns = { "product_name", diff --git a/test/models/airplane.hpp b/test/models/airplane.hpp new file mode 100644 index 0000000..fe7adef --- /dev/null +++ b/test/models/airplane.hpp @@ -0,0 +1,35 @@ +#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 "matador/sql/foreign.hpp" + +#include + +namespace matador::test { + +struct airplane +{ + unsigned long id; + std::string brand; + std::string model; + + template + void process(Operator &op) { + namespace field = matador::utils::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/flight.hpp b/test/models/flight.hpp new file mode 100644 index 0000000..3283c1f --- /dev/null +++ b/test/models/flight.hpp @@ -0,0 +1,33 @@ +#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/field_attributes.hpp" + +#include "matador/sql/foreign.hpp" + +#include + +namespace matador::test { + +struct flight { + unsigned long id; + sql::foreign airplane; + std::string pilot_name; + + template + void process(Operator &op) { + namespace field = matador::utils::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + field::has_one(op, "airplane_id", airplane, utils::cascade_type::ALL); + field::attribute(op, "pilot_name", pilot_name, 255); + } +}; + +} + +#endif //QUERY_FLIGHT_HPP diff --git a/test/models/person.hpp b/test/models/person.hpp new file mode 100644 index 0000000..613cb68 --- /dev/null +++ b/test/models/person.hpp @@ -0,0 +1,28 @@ +#ifndef QUERY_PERSON_HPP +#define QUERY_PERSON_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/field_attributes.hpp" + +#include + +namespace matador::test { + +struct person +{ + unsigned long id{}; + std::string name; + unsigned int age{}; + + template + void process(Operator &op) { + namespace field = matador::utils::access; + using namespace matador::utils; + field::primary_key(op, "id", id); + field::attribute(op, "name", name, 255); + field::attribute(op, "age", age); + } +}; + +} +#endif //QUERY_PERSON_HPP diff --git a/test/session.cpp b/test/session.cpp index e8faea4..76204b6 100644 --- a/test/session.cpp +++ b/test/session.cpp @@ -5,11 +5,14 @@ #include #include "models/product.hpp" +#include "models/airplane.hpp" +#include "models/flight.hpp" +#include "models/person.hpp" using namespace matador::sql; using namespace matador::test; -TEST_CASE("Execute create and drop table statement", "[session]") { +TEST_CASE("Create and drop table statement", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); @@ -27,13 +30,20 @@ TEST_CASE("Execute create and drop table statement", "[session]") { REQUIRE(res.second == R"(DROP TABLE "person")"); } -TEST_CASE("Execute create table statement with foreign keys", "[session]") { - // res = s.create().table("product").execute(); +TEST_CASE("Create table with foreign key relation", "[session]") { + connection_pool pool("sqlite://sqlite.db", 4); + session s(pool); - // REQUIRE(res.second == R"(CREATE TABLE "product" ("product_name" VARCHAR(255) PRIMARY KEY, "supplier_id" BIGINT NOT NULL FOREIGN KEY, "category_id" BIGINT NOT NULL FOREIGN KEY, "quantity_per_unit" VARCHAR(255), "unit_price" BIGINT, "units_in_stock" BIGINT, "units_in_order" BIGINT, "reorder_level" BIGINT, "discontinued" BOOLEAN))"); + auto res = s.create().table("airplane").execute(); + REQUIRE(res.first == 0); + REQUIRE(res.second == R"(CREATE TABLE "airplane" ("id" BIGINT, "brand" VARCHAR(255), "model" VARCHAR(255), CONSTRAINT PK_airplane PRIMARY KEY (id)))"); - // res = s.drop().table("person").execute(); - // REQUIRE(res.first == 1); + res = s.create().table("flight").execute(); + REQUIRE(res.first == 0); + REQUIRE(res.second == R"(CREATE TABLE "flight" ("id" BIGINT, "airplane_id" BIGINT, "pilot_name" VARCHAR(255), CONSTRAINT PK_flight PRIMARY KEY (id), CONSTRAINT FK_flight_airplane_id FOREIGN KEY (airplane_id) REFERENCES airplane(id)))"); + + s.drop().table("flight").execute(); + s.drop().table("airplane").execute(); } TEST_CASE("Execute insert record statement", "[session]") { @@ -69,149 +79,216 @@ TEST_CASE("Execute insert record statement", "[session]") { REQUIRE(i.size() == 3); REQUIRE(i.at(0).name() == "id"); REQUIRE(i.at(0).type() == data_type_t::type_long_long); + REQUIRE(i.at(0).value() == 7); REQUIRE(i.at(1).name() == "name"); REQUIRE(i.at(1).type() == data_type_t::type_varchar); + REQUIRE(i.at(1).value() == "george"); REQUIRE(i.at(2).name() == "age"); REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int); + REQUIRE(i.at(2).value() == 45); } - s.drop().table("person").execute(); - // using namespace matador::test; - // product p; - // p.discontinued = false; - // p.reorder_level = 1; - // p.units_in_order = 2; - // p.units_in_stock = 100; - // p.unit_price = 49; - // p.quantity_per_unit = "pcs"; - // p.category = make_foreign(); - // p.category->id = 7; - // p.supplier = make_foreign();; - // p.supplier->id = 13; - // p.product_name = "candle"; - // - // res = s.insert().into("product").values(p).execute(); - // - // REQUIRE(res.second == R"(INSERT INTO "product" ("product_name", "supplier_id", "category_id", "quantity_per_unit", "unit_price", "units_in_stock", "units_in_order", "reorder_level", "discontinued") VALUES ('candle', 13, 7, 'pcs', 49, 100, 2, 1, 0))"); - // - // res = s.insert().into("product", p).execute(); - // - // REQUIRE(res.second == R"(INSERT INTO "product" ("product_name", "supplier_id", "category_id", "quantity_per_unit", "unit_price", "units_in_stock", "units_in_order", "reorder_level", "discontinued") VALUES ('candle', 13, 7, 'pcs', 49, 100, 2, 1, 0))"); + s.drop().table("person").execute(); +} + +TEST_CASE("Execute update record statement", "[session]") { + connection_pool pool("sqlite://sqlite.db", 4); + session s(pool); + + auto res = s + .create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("age") + }) + .execute(); + + REQUIRE(res.first == 0); + + res = s + .insert() + .into("person", {"id", "name", "age"}) + .values({7, "george", 45}) + .execute(); + + REQUIRE(res.first == 1); + REQUIRE(res.second == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 45))"); + + res = s.update("person") + .set({{"id", 7}, {"name", "jane"}, {"age", 35}}) + .where("id"_col == 7) + .execute(); + + REQUIRE(res.first == 1); + REQUIRE(res.second == R"(UPDATE "person" SET "id"=7, "name"='jane', "age"=35 WHERE "id" = 7)"); + + auto result = s + .select({"id", "name", "age"}) + .from("person") + .fetch_all(); + + for (const auto& i : result) { + REQUIRE(i.size() == 3); + REQUIRE(i.at(0).name() == "id"); + REQUIRE(i.at(0).type() == data_type_t::type_long_long); + REQUIRE(i.at(0).value() == 7); + REQUIRE(i.at(1).name() == "name"); + REQUIRE(i.at(1).type() == data_type_t::type_varchar); + REQUIRE(i.at(1).value() == "jane"); + REQUIRE(i.at(2).name() == "age"); + REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int); + REQUIRE(i.at(2).value() == 35); + } + + s.drop().table("person").execute(); } TEST_CASE("Execute select statement with where clause", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); - auto res = s.select({"id", "name", "color"}) - .from("person") - .where("id"_col == 8) - .fetch_all(); + auto res = s.create() + .table("person") + .execute(); + + REQUIRE(res.first == 0); + + person george{7, "george", 45}; + + res = s.insert() + .into("person", george) + .execute(); + REQUIRE(res.first == 1); + + auto result = s.select() + .from("person") + .where("id"_col == 7) + .fetch_all(); + + for (const auto& i : result) { + REQUIRE(i.size() == 3); + REQUIRE(i.at(0).name() == "id"); + REQUIRE(i.at(0).type() == data_type_t::type_long_long); + REQUIRE(i.at(0).value() == george.id); + REQUIRE(i.at(1).name() == "name"); + REQUIRE(i.at(1).type() == data_type_t::type_varchar); + REQUIRE(i.at(1).value() == george.name); + REQUIRE(i.at(2).name() == "age"); + REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_long_long); + REQUIRE(i.at(2).value() == george.age); + } - // Todo: prepare test data - REQUIRE(true); // REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); + + s.drop().table("person").execute(); + } TEST_CASE("Execute select statement with order by", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); - auto res = s.select({"id", "name", "color"}) - .from("person") - .where("id"_col == 8) - .order_by("name").desc() - .fetch_all(); + auto res = s + .create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("color", 63) + }) + .execute(); + + REQUIRE(res.first == 0); + + auto result = s.select({"id", "name", "color"}) + .from("person") + .where("id"_col == 8) + .order_by("name").desc() + .fetch_all(); // Todo: prepare test data - REQUIRE(true); -// REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8 ORDER BY "name" DESC)"); + + s.drop().table("person").execute(); } TEST_CASE("Execute select statement with group by and order by", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); - auto res = s.select({"id", "name", "color"}) - .from("person") - .where("id"_col == 8) - .group_by("color") - .order_by("name").asc() - .fetch_all(); + auto res = s.create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("color", 63) + }) + .execute(); + + auto result = s.select({"id", "name", "color"}) + .from("person") + .where("id"_col == 8) + .group_by("color") + .order_by("name").asc() + .fetch_all(); // Todo: prepare test data - REQUIRE(true); -// REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8 GROUP BY "color" ORDER BY "name" ASC)"); + + s.drop().table("person").execute(); } TEST_CASE("Execute insert statement", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); - auto res = s.insert() - .into("person", {"id", "name", "color"}) - .values({7, "george", "green"}) + auto res = s.create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("color", 63) + }) .execute(); + res = s.insert() + .into("person", {"id", "name", "color"}) + .values({7, "george", "green"}) + .execute(); + + REQUIRE(res.first == 1); REQUIRE(res.second == R"(INSERT INTO "person" ("id", "name", "color") VALUES (7, 'george', 'green'))"); - using namespace matador::test; - product p; - p.discontinued = false; - p.reorder_level = 1; - p.units_in_order = 2; - p.units_in_stock = 100; - p.unit_price = 49; - p.quantity_per_unit = "pcs"; - p.category = make_foreign(); - p.category->id = 7; - p.supplier = make_foreign(); - p.supplier->id = 13; - p.product_name = "candle"; - - res = s.insert().into("product").values(p).execute(); - - REQUIRE(res.second == R"(INSERT INTO "product" ("product_name", "supplier_id", "category_id", "quantity_per_unit", "unit_price", "units_in_stock", "units_in_order", "reorder_level", "discontinued") VALUES ('candle', 13, 7, 'pcs', 49, 100, 2, 1, 0))"); - - res = s.insert().into("product", p).execute(); - - REQUIRE(res.second == R"(INSERT INTO "product" ("product_name", "supplier_id", "category_id", "quantity_per_unit", "unit_price", "units_in_stock", "units_in_order", "reorder_level", "discontinued") VALUES ('candle', 13, 7, 'pcs', 49, 100, 2, 1, 0))"); + s.drop().table("person").execute(); } TEST_CASE("Execute update statement", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); - auto res = s.update("person") - .set({ - {"name", "george"}, - {"color", "green"} - }) - .where("id"_col == 9) - .execute(); + auto res = s.create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("color", 63) + }) + .execute(); - REQUIRE(res.second == R"(UPDATE "person" SET "name"='george', "color"='green' WHERE "id" = 9)"); + res = s.insert() + .into("person", {"id", "name", "color"}) + .values({7, "george", "green"}) + .execute(); - using namespace matador::test; - product p; - p.discontinued = false; - p.reorder_level = 1; - p.units_in_order = 2; - p.units_in_stock = 100; - p.unit_price = 49; - p.quantity_per_unit = "pcs"; - p.category = make_foreign(); - p.category->id = 7; - p.supplier = make_foreign(); - p.supplier->id = 13; - p.product_name = "candle"; + REQUIRE(res.first == 1); + REQUIRE(res.second == R"(INSERT INTO "person" ("id", "name", "color") VALUES (7, 'george', 'green'))"); - res = s.update("product") - .set(p) + res = s.update("person") + .set({ + {"name", "george"}, + {"color", "green"} + }) .where("id"_col == 9) .execute(); - REQUIRE(res.second == R"(UPDATE "product" SET "product_name"='candle', "supplier_id"=13, "category_id"=7, "quantity_per_unit"='pcs', "unit_price"=49, "units_in_stock"=100, "units_in_order"=2, "reorder_level"=1, "discontinued"=0 WHERE "id" = 9)"); + REQUIRE(res.second == R"(UPDATE "person" SET "name"='george', "color"='green' WHERE "id" = 9)"); + + s.drop().table("person").execute(); } TEST_CASE("Execute delete statement", "[session]") {