diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e239be..8f9807b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ project(query) set(CMAKE_CXX_STANDARD 17) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(GCC_CLANG_COMMON_FLAGS "-Wall -Wconversion -Wextra -pedantic -ftemplate-backtrace-limit=0") + list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) find_package(ODBC REQUIRED) diff --git a/README.md b/README.md index f296c0c..f0732b9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ # query -A fluent sql query builder \ No newline at end of file +A fluent sql query builder + +Object definition +```cpp +struct airplane +{ + unsigned long id; + std::string brand; + std::string model; + + template + void process(Operator &op) { + namespace field = matador::utils::access; + field::primary_key(op, "id", id); + field::attribute(op, "brand", brand, 255); + field::attribute(op, "model", model, 255); + } +}; +``` +```cpp +connection_pool pool("sqlite://sqlite.db", 4); +session s(pool); + +// register entity +s.attach("airplane") + +// create all tables +s.create(); + +// insert +s.insert(1, "Airbus", "A380"); +s.insert(2, "Boeing", "748"); +s.insert(3, "Boeing", "707"); + +s.update().set({"model", "747"}).where("id"_col == 2); + +auto result = s.select().where("brand"_col == "Boeing"); + +for (const auto &plane : result) { + std::cout << "airplane: " << plane->brand << " - " << plane->model << "\n"; +} + +``` \ No newline at end of file diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..bdbab93 --- /dev/null +++ b/Todo.md @@ -0,0 +1,8 @@ +# Todo + +- Add is_valid() method to connection & connection_impl +- Read in foreign fields +- Add special handling for update in backends +- Add PostgreSQL backend +- Add MySQL/MariaDB backend +- Add ODBC/SQL Server backend \ No newline at end of file diff --git a/backends/sqlite/include/sqlite_connection.hpp b/backends/sqlite/include/sqlite_connection.hpp index fdd02a0..5ead81d 100644 --- a/backends/sqlite/include/sqlite_connection.hpp +++ b/backends/sqlite/include/sqlite_connection.hpp @@ -33,6 +33,8 @@ public: sql::record describe(const std::string& table) override; + bool exists(const std::string &table_name) override; + private: static int parse_result(void* param, int column_count, char** values, char** columns); diff --git a/backends/sqlite/src/sqlite_connection.cpp b/backends/sqlite/src/sqlite_connection.cpp index f854ba7..3585afd 100644 --- a/backends/sqlite/src/sqlite_connection.cpp +++ b/backends/sqlite/src/sqlite_connection.cpp @@ -153,14 +153,27 @@ sql::record sqlite_connection::describe(const std::string& table) options = utils::constraints::NOT_NULL; } // f.default_value(res->column(4)); - // end = nullptr; - // f.is_primary_key(strtoul(res->column(3), &end, 10) == 0); prototype.append({name, type, {options}}); } return std::move(prototype); } +bool sqlite_connection::exists(const std::string &table_name) +{ + const auto result = fetch("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name='" + table_name + "' LIMIT 1"); + + if (!result->fetch()) { + // Todo: throw an exception? + return false; + } + + int v{}; + result->read_value(nullptr, 0, v); + + return v == 1; +} + } extern "C" diff --git a/include/matador/sql/any_type_to_visitor.hpp b/include/matador/sql/any_type_to_visitor.hpp new file mode 100644 index 0000000..690b75d --- /dev/null +++ b/include/matador/sql/any_type_to_visitor.hpp @@ -0,0 +1,131 @@ +#ifndef QUERY_ANY_TYPE_TO_VISITOR_HPP +#define QUERY_ANY_TYPE_TO_VISITOR_HPP + +#include +#include +#include +#include +#include +#include + +namespace matador::sql { + +template < typename DestType, typename SourceType > +void convert(DestType &dest, SourceType source, typename std::enable_if::value>::type* = nullptr) +{ + dest = source; +} + +template < typename DestType, typename SourceType > +void convert(DestType &dest, SourceType source, typename std::enable_if::value && std::is_arithmetic::value && !std::is_same::value>::type* = nullptr) +{ + dest = static_cast(source); +} + +template < typename DestType, typename SourceType > +void convert(DestType &dest, SourceType source, typename std::enable_if::value && std::is_arithmetic::value && !std::is_same::value>::type* = nullptr) +{ + dest = static_cast(source); +} + +void convert(std::string &dest, bool source); + +template < typename SourceType > +void convert(std::string &dest, SourceType source, typename std::enable_if::value && !std::is_same::value>::type* = nullptr) +{ + std::array buffer{}; + auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), source, 10); + if (ec == std::errc{}) { + dest.assign(buffer.data(), ptr); + } else { + throw std::logic_error("couldn't convert value to std::string"); + } +} + +template < typename SourceType > +void convert(std::string &dest, SourceType source, typename std::enable_if::value>::type* = nullptr) +{ + std::array buffer{}; + auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), source); + if (ec == std::errc{}) { + dest.assign(buffer.data(), ptr); + } else { + throw std::logic_error("couldn't convert value to std::string"); + } +} + +void convert(std::string &dest, const char* source); + +unsigned long long to_unsigned_long_long(const char *source); + +template < typename DestType > +void convert(DestType &dest, const std::string &source, typename std::enable_if::value && std::is_unsigned::value>::type* = nullptr) +{ + dest = to_unsigned_long_long(source.c_str()); +} + +template < typename DestType > +void convert(DestType &dest, const char *source, typename std::enable_if::value && std::is_unsigned::value>::type* = nullptr) +{ + dest = to_unsigned_long_long(source); +} + +long long to_long_long(const char *source); + +template < typename DestType > +void convert(DestType &dest, const std::string &source, typename std::enable_if::value && std::is_signed::value>::type* = nullptr) +{ + dest = to_long_long(source.c_str()); +} + +template < typename DestType > +void convert(DestType &dest, const char *source, typename std::enable_if::value && std::is_signed::value>::type* = nullptr) +{ + dest = to_long_long(source); +} + +long double to_double(const char *source); + +template < typename DestType > +void convert(DestType &dest, const std::string &source, typename std::enable_if::value>::type* = nullptr) +{ + dest = to_double(source.c_str()); +} + +template < typename DestType > +void convert(DestType &dest, const char *source, typename std::enable_if::value>::type* = nullptr) +{ + dest = to_double(source); +} + +template < typename DestType > +void convert(DestType &dest, bool source, typename std::enable_if::value>::type* = nullptr) +{ + dest = static_cast(source); +} + +template < typename Type > +struct any_type_to_visitor +{ + void operator()(char &x) { convert(result, x); } + void operator()(short &x) { convert(result, x); } + void operator()(int &x) { convert(result, x); } + void operator()(long &x) { convert(result, x); } + void operator()(long long &x) { convert(result, x); } + void operator()(unsigned char &x) { convert(result, x); } + void operator()(unsigned short &x) { convert(result, x); } + void operator()(unsigned int &x) { convert(result, x); } + void operator()(unsigned long &x) { convert(result, x); } + void operator()(unsigned long long &x) { convert(result, x); } + void operator()(bool &x) { convert(result, x); } + void operator()(float &x) { convert(result, x); } + void operator()(double &x) { convert(result, x); } + void operator()(const char *x) { convert(result, x); } + void operator()(std::string &x) { convert(result, x); } + + Type result{}; +}; + +} + +#endif //QUERY_ANY_TYPE_TO_VISITOR_HPP diff --git a/include/matador/sql/column.hpp b/include/matador/sql/column.hpp index 2906cec..2405fb1 100644 --- a/include/matador/sql/column.hpp +++ b/include/matador/sql/column.hpp @@ -2,6 +2,7 @@ #define QUERY_COLUMN_HPP #include "matador/sql/any_type.hpp" +#include "matador/sql/any_type_to_visitor.hpp" #include "matador/sql/types.hpp" #include "matador/utils/field_attributes.hpp" @@ -11,28 +12,42 @@ namespace matador::sql { +namespace detail { + + +} class column { public: explicit column(std::string name) : name_(std::move(name)) , attributes_(utils::null_attributes) {} + column(const column&) = default; + column& operator=(const column&) = default; + column(column&&) noexcept = default; + column& operator=(column&&) noexcept = default; + template explicit column(std::string name, utils::field_attributes attr = utils::null_attributes) : column(std::move(name), data_type_traits::builtin_type(attr.size()), attr) {} + template column(std::string name, const Type &, utils::field_attributes attr = utils::null_attributes) : column(std::move(name), data_type_traits::builtin_type(attr.size()), attr) {} + column(std::string name, data_type_t type, utils::field_attributes attr = utils::null_attributes); template + column(std::string name, std::string ref_table, std::string ref_column, utils::field_attributes attr = utils::null_attributes) : column(std::move(name), data_type_traits::builtin_type(attr.size()), ref_table, ref_column, attr) {} - column(std::string name, data_type_t type, std::string ref_table, std::string ref_column, utils::field_attributes attr = utils::null_attributes); + + column(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, utils::field_attributes attr = utils::null_attributes); [[nodiscard]] const std::string& name() const; + [[nodiscard]] size_t index() const; [[nodiscard]] const utils::field_attributes& attributes() const; [[nodiscard]] data_type_t type() const; [[nodiscard]] const std::string& ref_table() const; @@ -43,10 +58,40 @@ public: return std::holds_alternative(value_); } - template< typename Type > - std::optional value() const { + [[nodiscard]] std::string str() const; + + template + void set(const Type &value, const utils::field_attributes &attr = utils::null_attributes) + { + type_ = data_type_traits::builtin_type(attr.size()); + attributes_ = attr; + value_ = value; + } + + void set(const std::string &value, const utils::field_attributes &attr) + { + type_ = data_type_traits::builtin_type(attr.size()); + attributes_ = attr; + value_ = value; + } + + void set(const char *value, const utils::field_attributes &attr) + { + type_ = data_type_traits::builtin_type(attr.size()); + attributes_ = attr; + value_ = value; + } + + template + Type as() const + { const Type* ptr= std::get_if(&value_); - return ptr ? std::make_optional(*ptr) : std::nullopt; + if (ptr) { + return *ptr; + } + any_type_to_visitor visitor; + std::visit(visitor, const_cast(value_)); + return visitor.result; } private: @@ -64,8 +109,9 @@ private: static const data_type_index data_type_index_; std::string name_; + size_t index_{}; utils::field_attributes attributes_; - data_type_t type_{}; + data_type_t type_{data_type_t::type_unknown}; any_type value_; std::string ref_table_; std::string ref_column_; @@ -107,7 +153,7 @@ column make_fk_column(const std::string &name, size_t size, const std::string &r template < typename Type > [[maybe_unused]] column make_fk_column(const std::string &name, const std::string &ref_table, const std::string &ref_column) { - return {name, data_type_traits::builtin_type(0), ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }}; + return {name, data_type_traits::builtin_type(0), 0, ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }}; } template <> diff --git a/include/matador/sql/column_generator.hpp b/include/matador/sql/column_generator.hpp index 172106e..21f092c 100644 --- a/include/matador/sql/column_generator.hpp +++ b/include/matador/sql/column_generator.hpp @@ -23,7 +23,7 @@ public: 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_, ref_table, ref_column, { utils::constraints::FOREIGN_KEY }}; + return column{id, type_, 0, ref_table, ref_column, { utils::constraints::FOREIGN_KEY }}; } template diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index 578b84b..51a7246 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -30,6 +30,7 @@ public: [[nodiscard]] const connection_info& info() const; [[nodiscard]] record describe(const std::string &table_name) const; + bool exists(const std::string &table_name) const; template query_result fetch(const std::string &sql) diff --git a/include/matador/sql/connection_impl.hpp b/include/matador/sql/connection_impl.hpp index 9b0ee1c..07c3170 100644 --- a/include/matador/sql/connection_impl.hpp +++ b/include/matador/sql/connection_impl.hpp @@ -25,6 +25,7 @@ public: virtual void prepare(const std::string &stmt) = 0; virtual record describe(const std::string &table) = 0; + virtual bool exists(const std::string &table_name) = 0; protected: explicit connection_impl(const connection_info &info); diff --git a/include/matador/sql/query_builder.hpp b/include/matador/sql/query_builder.hpp index 80564d6..145e0f4 100644 --- a/include/matador/sql/query_builder.hpp +++ b/include/matador/sql/query_builder.hpp @@ -57,6 +57,7 @@ enum class join_type_t { struct query { std::string sql; + std::string table_name; record prototype; std::vector host_vars; }; @@ -128,7 +129,7 @@ public: query_builder& offset(size_t count); query_builder& limit(size_t count); - std::string compile(); + query compile(); private: void transition_to(state_t next); diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 52bb441..f1a511d 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -25,12 +25,8 @@ public: query_intermediate(session &db, query_builder &query); protected: - session& db(); - query_builder& query(); - -private: - session &db_; - query_builder &query_; + session &session_; + query_builder &builder_; }; class query_execute_finish : public query_intermediate @@ -127,10 +123,20 @@ public: query_order_by_intermediate order_by(const std::string &name); }; -class query_select_intermediate : public query_intermediate +class query_start_intermediate { public: - using query_intermediate::query_intermediate; + explicit query_start_intermediate(session &s); + +protected: + session &session_; + query_builder builder_; +}; + +class query_select_intermediate : public query_start_intermediate +{ +public: + query_select_intermediate(session &s, std::vector column_names); query_from_intermediate from(const std::string &table, const std::string &as = ""); }; @@ -144,50 +150,50 @@ public: template query_execute_finish values(const Type &obj) { - return {db(), query().values(value_extractor::extract(obj))}; + return {session_, builder_.values(value_extractor::extract(obj))}; } }; -class query_create_intermediate : query_intermediate +class query_create_intermediate : query_start_intermediate { public: - query_create_intermediate(session &db, query_builder &query, table_repository &repo); + query_create_intermediate(session &s, table_repository &repo); query_execute_finish table(const std::string &table, std::initializer_list columns); template query_execute_finish table(const std::string &table_name) { - const auto &info = repository_.attach(table_name/*, record{column_generator::generate(repository_)}*/); - return {db(), query().table(table_name, info.prototype.columns())}; + const auto &info = repository_.attach(table_name); + return {session_, builder_.table(table_name, info.prototype.columns())}; } private: table_repository &repository_; }; -class query_drop_intermediate : query_intermediate +class query_drop_intermediate : query_start_intermediate { public: - using query_intermediate::query_intermediate; + explicit query_drop_intermediate(session &s); query_execute_finish table(const std::string &table); }; -class query_insert_intermediate : public query_intermediate +class query_insert_intermediate : public query_start_intermediate { public: - using query_intermediate::query_intermediate; + explicit query_insert_intermediate(session &s); query_into_intermediate into(const std::string &table, std::initializer_list column_names); template query_into_intermediate into(const std::string &table) { - return {db(), query().into(table, column_name_generator::generate())}; + return {session_, builder_.into(table, column_name_generator::generate())}; } template query_execute_finish into(const std::string &table, const Type &obj) { - return {db(), query().into(table, column_name_generator::generate()) + return {session_, builder_.into(table, column_name_generator::generate()) .values(value_extractor::extract(obj))}; } }; @@ -208,16 +214,16 @@ public: query_execute_where_intermediate where(const basic_condition &cond); }; -class query_update_intermediate : public query_intermediate +class query_update_intermediate : public query_start_intermediate { public: - using query_intermediate::query_intermediate; + query_update_intermediate(session &s, std::string table_name); query_set_intermediate set(std::initializer_list columns); template query_set_intermediate set(const Type &obj) { - return {db(), query().set(key_value_generator::generate(obj))}; + return {session_, builder_.set(key_value_generator::generate(obj))}; } }; @@ -229,10 +235,10 @@ public: query_execute_where_intermediate where(const basic_condition &cond); }; -class query_delete_intermediate : public query_intermediate +class query_delete_intermediate : public query_start_intermediate { public: - using query_intermediate::query_intermediate; + explicit query_delete_intermediate(session &s); query_delete_from_intermediate from(const std::string &table); }; diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index 172ccdb..d4534a3 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -12,6 +12,8 @@ namespace matador::sql { +class dialect; + class session { public: @@ -22,14 +24,15 @@ public: template < class Type > query_select_intermediate select() { - return query_select_intermediate{*this, query_.select(column_name_generator::generate())}; + return query_select_intermediate{*this, 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); query_delete_intermediate remove(); - [[nodiscard]] query_result fetch(const std::string &sql) const; + [[nodiscard]] query_result fetch(const query &q) const; +// [[nodiscard]] query_result fetch(const std::string &sql) const; [[nodiscard]] std::pair execute(const std::string &sql) const; template @@ -40,6 +43,8 @@ public: [[nodiscard]] const table_repository& tables() const; + const class dialect& dialect() const; + private: friend class query_select_finish; @@ -47,9 +52,10 @@ private: private: connection_pool &pool_; - query_builder query_; + const class dialect &dialect_; table_repository table_repository_; + mutable std::unordered_map prototypes_; }; } diff --git a/include/matador/sql/types.hpp b/include/matador/sql/types.hpp index ad38184..cc0d9c8 100644 --- a/include/matador/sql/types.hpp +++ b/include/matador/sql/types.hpp @@ -180,7 +180,7 @@ template <> struct data_type_traits template <> struct data_type_traits { inline static database_type_t type(std::size_t size) { return size == 0 ? database_type_t::type_text : database_type_t::type_varchar; } - inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_char_pointer; } + inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_varchar; } inline static unsigned long size() { return sizeof(char*); } inline static const char* name() { return "char*"; } }; @@ -188,7 +188,7 @@ template <> struct data_type_traits template <> struct data_type_traits { inline static database_type_t type(std::size_t size) { return size == 0 ? database_type_t::type_text : database_type_t::type_varchar; } - inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_char_pointer; } + inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_varchar; } inline static unsigned long size() { return 1023; } inline static const char* name() { return "std::string"; } }; diff --git a/include/matador/utils/field_attributes.hpp b/include/matador/utils/field_attributes.hpp index 1ae7717..9736b2c 100644 --- a/include/matador/utils/field_attributes.hpp +++ b/include/matador/utils/field_attributes.hpp @@ -21,6 +21,7 @@ public: ~field_attributes() = default; [[nodiscard]] size_t size() const; + void size(size_t size); [[nodiscard]] constraints options() const; private: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba8f6fd..5f37d7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,8 @@ set(SQL_SOURCES sql/column_name_generator.cpp sql/key_value_generator.cpp sql/fk_value_extractor.cpp - sql/table_repository.cpp) + sql/table_repository.cpp + sql/any_type_to_visitor.cpp) set(SQL_HEADER ../include/matador/sql/dialect.hpp @@ -44,7 +45,8 @@ set(SQL_HEADER ../include/matador/sql/key_value_generator.hpp ../include/matador/sql/foreign.hpp ../include/matador/sql/fk_value_extractor.hpp - "../include/matador/sql/table_repository.hpp") + "../include/matador/sql/table_repository.hpp" + ../include/matador/sql/any_type_to_visitor.hpp) set(UTILS_HEADER ../include/matador/utils/field_attributes.hpp diff --git a/src/sql/any_type_to_visitor.cpp b/src/sql/any_type_to_visitor.cpp new file mode 100644 index 0000000..b5fe425 --- /dev/null +++ b/src/sql/any_type_to_visitor.cpp @@ -0,0 +1,60 @@ +#include "matador/sql/any_type_to_visitor.hpp" + +namespace matador::sql { + +void convert(std::string &dest, bool source) +{ + dest = source ? "true" : "false"; +} + +void convert(std::string &dest, const char *source) +{ + dest = source; +} + +long long to_long_long(const char *source) +{ + if (strlen(source) == 0) { + return{}; + } + char *end; + const auto result = strtoll(source, &end, 10); + if (end == nullptr) { + // Todo: check error + throw std::logic_error("couldn't convert value to number"); + } + + return result; +} + +unsigned long long to_unsigned_long_long(const char *source) +{ + if (strlen(source) == 0) { + return{}; + } + char *end; + const auto result = strtoull(source, &end, 10); + if (end == nullptr) { + // Todo: check error + throw std::logic_error("couldn't convert value to number"); + } + + return result; +} + +long double to_double(const char *source) +{ + if (strlen(source) == 0) { + return{}; + } + char *end; + const auto result = strtold(source, &end); + if (end == nullptr) { + // Todo: check error + throw std::logic_error("couldn't convert value to number"); + } + + return result; +} + +} \ No newline at end of file diff --git a/src/sql/column.cpp b/src/sql/column.cpp index 6e59d77..d5e8766 100644 --- a/src/sql/column.cpp +++ b/src/sql/column.cpp @@ -29,8 +29,9 @@ column::column(std::string name, data_type_t type, utils::field_attributes attr) , type_(type) , attributes_(attr) {} -column::column(std::string name, data_type_t type, std::string ref_table, std::string ref_column, utils::field_attributes attr) +column::column(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, utils::field_attributes attr) : name_(std::move(name)) +, index_(index) , type_(type) , attributes_(attr) , ref_table_(std::move(ref_table)) @@ -41,6 +42,11 @@ const std::string &column::name() const return name_; } +size_t column::index() const +{ + return index_; +} + const utils::field_attributes &column::attributes() const { return attributes_; @@ -61,6 +67,13 @@ const std::string &column::ref_column() const return ref_column_; } +std::string column::str() const +{ + any_type_to_visitor visitor; + std::visit(visitor, const_cast(value_)); + return visitor.result; +} + column operator "" _col(const char *name, size_t len) { return column(std::string(name, len)); @@ -86,6 +99,6 @@ column make_pk_column( const std::string& name, size_t size ) template<> [[maybe_unused]] column make_fk_column(const std::string& name, size_t size, const std::string &ref_table, const std::string &ref_column) { - return {name, data_type_traits::builtin_type(size), ref_table, ref_column, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL}}; + return {name, data_type_traits::builtin_type(size), 0, ref_table, ref_column, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL}}; } } \ No newline at end of file diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index 0e1ccec..867fb15 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -68,6 +68,11 @@ record connection::describe(const std::string &table_name) const return std::move(connection_->describe(table_name)); } +bool connection::exists(const std::string &table_name) const +{ + return connection_->exists(table_name); +} + query_result connection::fetch(const std::string &sql) const { auto rec = connection_->describe("person"); diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index 00ba5af..2075a6e 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -141,6 +141,7 @@ query_builder& query_builder::insert() { query_builder& query_builder::update(const std::string &table) { initialize(command_t::UPDATE, state_t::QUERY_UPDATE); + query_.table_name = table; query_parts_.emplace_back(dialect_.token_at(dialect::token_t::UPDATE) + " " + dialect_.prepare_identifier(table)); return *this; @@ -178,6 +179,7 @@ query_builder &query_builder::table(const std::string &table, const std::vector< transition_to(state_t::QUERY_TABLE_CREATE); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table) + " "); + query_.table_name = table; std::string result = "("; @@ -215,6 +217,7 @@ query_builder& query_builder::table(const std::string &table) { transition_to(state_t::QUERY_TABLE_DROP); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table)); + query_.table_name = table; return *this; } @@ -229,6 +232,7 @@ query_builder &query_builder::into(const std::string &table, const std::vector query_select_finish::fetch_all() { - return db().fetch(query().compile()); + return session_.fetch(builder_.compile()); } record query_select_finish::fetch_one() { - return *db().fetch(query().compile()).begin().get(); + return *session_.fetch(builder_.compile()).begin().get(); } std::unique_ptr query_select_finish::fetch() { - return db().call_fetch(query().compile()); + return session_.call_fetch(builder_.compile().sql); } query_intermediate::query_intermediate(session &db, query_builder &query) -: db_(db), query_(query) {} +: session_(db), builder_(query) {} query_offset_intermediate query_order_direction_intermediate::offset(size_t offset) { - return {db(), query()}; + return {session_, builder_}; } query_limit_intermediate query_offset_intermediate::limit(size_t limit) { - return {db(), query()}; + return {session_, builder_}; } query_limit_intermediate query_order_direction_intermediate::limit(size_t limit) { - return {db(), query()}; + return {session_, builder_}; } query_order_by_intermediate query_group_by_intermediate::order_by(const std::string &name) { - return {db(), query().order_by(name)}; + return {session_, builder_.order_by(name)}; } query_order_direction_intermediate query_order_by_intermediate::asc() { - return {db(), query().asc()}; + return {session_, builder_.asc()}; } query_order_direction_intermediate query_order_by_intermediate::desc() { - return {db(), query().desc()}; + return {session_, builder_.desc()}; } query_group_by_intermediate query_from_intermediate::group_by(const std::string &name) { - return {db(), query().group_by(name)}; + return {session_, builder_.group_by(name)}; } query_order_by_intermediate query_from_intermediate::order_by(const std::string &name) { - return {db(), query().order_by(name)}; + return {session_, builder_.order_by(name)}; } query_group_by_intermediate query_where_intermediate::group_by(const std::string &name) { - return {db(), query().group_by(name)}; + return {session_, builder_.group_by(name)}; } query_order_by_intermediate query_where_intermediate::order_by(const std::string &name) { - return {db(), query().order_by(name)}; + return {session_, builder_.order_by(name)}; } query_where_intermediate query_from_intermediate::where(const basic_condition &cond) { - return query_where_intermediate{db(), query().where(cond)}; + return query_where_intermediate{session_, builder_.where(cond)}; +} + +query_select_intermediate::query_select_intermediate(session &s, std::vector column_names) +: query_start_intermediate(s) +{ + builder_.select(std::move(column_names)); } query_from_intermediate query_select_intermediate::from(const std::string &table, const std::string &as) { - return {db(), query().from(table, as)}; + return {session_, builder_.from(table, as)}; +} + +query_insert_intermediate::query_insert_intermediate(session &s) +: query_start_intermediate(s) +{ + builder_.insert(); } query_into_intermediate query_insert_intermediate::into(const std::string &table, std::initializer_list column_names) { - return {db(), query().into(table, column_names)}; + return {session_, builder_.into(table, column_names)}; } std::pair query_execute_finish::execute() { - return db().execute(query().compile()); + return session_.execute(builder_.compile().sql); } query_execute_finish query_into_intermediate::values(std::initializer_list values) { - return {db(), query().values(values)}; + return {session_, builder_.values(values)}; } -query_create_intermediate::query_create_intermediate(session &db, query_builder &query, table_repository &repo) -: query_intermediate(db, query) -, repository_(repo) {} +query_create_intermediate::query_create_intermediate(session &s, table_repository &repo) +: query_start_intermediate(s) +, repository_(repo) { + builder_.create(); +} query_execute_finish query_create_intermediate::table(const std::string &table, std::initializer_list columns) { - return {db(), query().table(table, columns)}; + return {session_, builder_.table(table, columns)}; +} + +query_drop_intermediate::query_drop_intermediate(session &s) +: query_start_intermediate(s) +{ + builder_.drop(); } query_execute_finish query_drop_intermediate::table(const std::string &table) { - return {db(), query().table(table)}; + return {session_, builder_.table(table)}; } query_execute_finish query_execute_where_intermediate::limit(int limit) { - return {db(), query().limit(limit)}; + return {session_, builder_.limit(limit)}; } query_execute_where_intermediate query_set_intermediate::where(const basic_condition &cond) { - return {db(), query().where(cond)}; + return {session_, builder_.where(cond)}; +} + +query_update_intermediate::query_update_intermediate(session &s, std::string table_name) +: query_start_intermediate(s) +{ + builder_.update(std::move(table_name)); } query_set_intermediate query_update_intermediate::set(std::initializer_list columns) { - return {db(), query().set(columns)}; + return {session_, builder_.set(columns)}; } query_execute_where_intermediate query_delete_from_intermediate::where(const basic_condition &cond) { - return {db(), query().where(cond)}; + return {session_, builder_.where(cond)}; +} + +query_delete_intermediate::query_delete_intermediate(session &s) +: query_start_intermediate(s) +{ + builder_.remove(); } query_delete_from_intermediate query_delete_intermediate::from(const std::string &table) { - return {db(), query().from(table)}; + return {session_, builder_.from(table)}; } + +query_start_intermediate::query_start_intermediate(session &s) +: session_(s) +, builder_(s.dialect()) +{} } \ No newline at end of file diff --git a/src/sql/session.cpp b/src/sql/session.cpp index b176358..6a0c824 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -8,46 +8,58 @@ namespace matador::sql { session::session(connection_pool &pool) : pool_(pool) -, query_(backend_provider::instance().connection_dialect(pool.info().type)) {} +, dialect_(backend_provider::instance().connection_dialect(pool_.info().type)) {} query_create_intermediate session::create() { - return query_create_intermediate{*this, query_.create(), table_repository_}; + return query_create_intermediate{*this, table_repository_}; } query_drop_intermediate session::drop() { - return query_drop_intermediate{*this, query_.drop()}; + return query_drop_intermediate{*this}; } query_select_intermediate session::select(std::initializer_list column_names) { - return query_select_intermediate{*this, query_.select(column_names)}; + return query_select_intermediate{*this, column_names}; } query_insert_intermediate session::insert() { - return query_insert_intermediate{*this, query_.insert()}; + return query_insert_intermediate{*this}; } query_update_intermediate session::update(const std::string &table) { - return query_update_intermediate{*this, query_.update(table)}; + return query_update_intermediate{*this, table}; } query_delete_intermediate session::remove() { - return query_delete_intermediate{*this, query_.remove()}; + return query_delete_intermediate{*this}; } -query_result session::fetch(const std::string &sql) const +query_result session::fetch(const query &q) const { - auto res = call_fetch(sql); - auto proto = res->prototype(); - return query_result{std::move(res), [proto]() { return new record(proto); }}; + auto c = pool_.acquire(); + if (!c.valid()) { + throw std::logic_error("no database connection available"); + } + auto it = prototypes_.find(q.table_name); + if (it == prototypes_.end()) { + it = prototypes_.emplace(q.table_name, c->describe(q.table_name)).first; + } + auto res = c->call_fetch(q.sql); + return query_result{std::move(res), [it]() { return new record(it->second); }}; } +//query_result session::fetch(const std::string &sql) const +//{ +// return query_result(std::unique_ptr()); +//} + std::pair session::execute(const std::string &sql) const { auto c = pool_.acquire(); if (!c.valid()) { @@ -61,6 +73,11 @@ const table_repository& session::tables() const return table_repository_; } +const class dialect &session::dialect() const +{ + return dialect_; +} + std::unique_ptr session::call_fetch(const std::string &sql) const { auto c = pool_.acquire(); diff --git a/src/utils/field_attributes.cpp b/src/utils/field_attributes.cpp index 2adfebf..4ab2d57 100644 --- a/src/utils/field_attributes.cpp +++ b/src/utils/field_attributes.cpp @@ -20,6 +20,11 @@ size_t field_attributes::size() const return size_; } +void field_attributes::size(size_t size) +{ + size_ = size; +} + constraints field_attributes::options() const { return options_; diff --git a/test/AnyTypeToVisitorTest.cpp b/test/AnyTypeToVisitorTest.cpp new file mode 100644 index 0000000..2150a7e --- /dev/null +++ b/test/AnyTypeToVisitorTest.cpp @@ -0,0 +1,78 @@ +#include + +#include "matador/sql/any_type.hpp" +#include "matador/sql/any_type_to_visitor.hpp" + +using namespace matador::sql; + +TEST_CASE("Convert any type to string", "[any type visitor]") { + any_type_to_visitor to_string_visitor; + + any_type value = 6; + std::visit(to_string_visitor, value); + REQUIRE(to_string_visitor.result == "6"); + + value = 2.5; + std::visit(to_string_visitor, value); + REQUIRE(to_string_visitor.result == "2.5"); + + value = true; + std::visit(to_string_visitor, value); + REQUIRE(to_string_visitor.result == "true"); + + value = "hello"; + std::visit(to_string_visitor, value); + REQUIRE(to_string_visitor.result == "hello"); + + value = std::string{"world"}; + std::visit(to_string_visitor, value); + REQUIRE(to_string_visitor.result == "world"); +} + +TEST_CASE("Convert any type to integral", "[any type visitor]") { + any_type_to_visitor to_long_visitor; + + any_type value = 6; + std::visit(to_long_visitor, value); + REQUIRE(to_long_visitor.result == 6); + + value = 2.5; + std::visit(to_long_visitor, value); + REQUIRE(to_long_visitor.result == 2); + + value = true; + std::visit(to_long_visitor, value); + REQUIRE(to_long_visitor.result == 1); + + value = "hello"; + std::visit(to_long_visitor, value); + REQUIRE(to_long_visitor.result == 0); + + value = std::string{"world"}; + std::visit(to_long_visitor, value); + REQUIRE(to_long_visitor.result == 0); +} + +TEST_CASE("Convert any type to floating point", "[any type visitor]") { + any_type_to_visitor to_double_visitor; + + any_type value = 6; + std::visit(to_double_visitor, value); + REQUIRE(to_double_visitor.result == 6); + + value = 2.5; + std::visit(to_double_visitor, value); + REQUIRE(to_double_visitor.result == 2.5); + + value = true; + std::visit(to_double_visitor, value); + REQUIRE(to_double_visitor.result == 1); + + value = "hello"; + std::visit(to_double_visitor, value); + REQUIRE(to_double_visitor.result == 0); + + value = std::string{"world"}; + std::visit(to_double_visitor, value); + REQUIRE(to_double_visitor.result == 0); +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 33c64cb..06da1dc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,7 +23,9 @@ add_executable(tests builder.cpp models/supplier.hpp models/airplane.hpp models/flight.hpp - models/person.hpp) + models/person.hpp + AnyTypeToVisitorTest.cpp + ColumnTest.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain matador diff --git a/test/ColumnTest.cpp b/test/ColumnTest.cpp new file mode 100644 index 0000000..273e4fe --- /dev/null +++ b/test/ColumnTest.cpp @@ -0,0 +1,63 @@ +#include + +#include "matador/sql/column.hpp" + +using namespace matador::sql; + +TEST_CASE("Create empty column", "[column]") { + column c("name"); + + REQUIRE(c.name() == "name"); + REQUIRE(c.index() == 0); + REQUIRE(c.type() == data_type_t::type_unknown); + REQUIRE(c.ref_table().empty()); + REQUIRE(c.ref_column().empty()); + + c.set(std::string{"george"}, 255); + REQUIRE(c.type() == data_type_t::type_varchar); + REQUIRE(c.as() == "george"); + + c.set(7); + REQUIRE(c.type() == data_type_t::type_int); + REQUIRE(c.as() == "7"); + REQUIRE(c.as() == 7); + REQUIRE(c.str() == "7"); +} + +TEST_CASE("Copy and move column", "[column]") { + column c("name"); + c.set(std::string{"george"}, 255); + REQUIRE(c.name() == "name"); + REQUIRE(c.index() == 0); + REQUIRE(c.ref_table().empty()); + REQUIRE(c.ref_column().empty()); + REQUIRE(c.type() == data_type_t::type_varchar); + REQUIRE(c.as() == "george"); + REQUIRE(c.attributes().size() == 255); + + auto c2 = c; + REQUIRE(c2.name() == "name"); + REQUIRE(c2.index() == 0); + REQUIRE(c2.ref_table().empty()); + REQUIRE(c2.ref_column().empty()); + REQUIRE(c2.type() == data_type_t::type_varchar); + REQUIRE(c2.as() == "george"); + REQUIRE(c2.attributes().size() == 255); + + auto c3 = std::move(c2); + REQUIRE(c3.name() == "name"); + REQUIRE(c3.index() == 0); + REQUIRE(c3.ref_table().empty()); + REQUIRE(c3.ref_column().empty()); + REQUIRE(c3.type() == data_type_t::type_varchar); + REQUIRE(c3.as() == "george"); + REQUIRE(c3.attributes().size() == 255); + + REQUIRE(c2.name().empty()); + REQUIRE(c2.index() == 0); + REQUIRE(c2.ref_table().empty()); + REQUIRE(c2.ref_column().empty()); + REQUIRE(c2.type() == data_type_t::type_varchar); + REQUIRE(c2.as().empty()); + REQUIRE(c2.attributes().size() == 255); +} \ No newline at end of file diff --git a/test/builder.cpp b/test/builder.cpp index 9694143..bfa77e1 100644 --- a/test/builder.cpp +++ b/test/builder.cpp @@ -8,120 +8,132 @@ using namespace matador::sql; TEST_CASE("Create table sql statement string", "[query]") { - dialect d; - query_builder query(d); - auto sql = query.create().table("person", { - make_pk_column("id"), - make_column("name", 255), - make_column("age") - }).compile(); + dialect d; + query_builder query(d); + auto q = query.create().table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("age") + }).compile(); - REQUIRE(sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255), "age" INTEGER, CONSTRAINT PK_person PRIMARY KEY (id)))##"); + REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255), "age" INTEGER, CONSTRAINT PK_person PRIMARY KEY (id)))##"); + REQUIRE(q.table_name == "person"); - sql = query.create().table("person", { - make_pk_column("id"), - make_column("name", 255), - make_column("age"), - make_fk_column("address", "address", "id") - }).compile(); + q = query.create().table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("age"), + make_fk_column("address", "address", "id") + }).compile(); - REQUIRE(sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255), "age" INTEGER, "address" BIGINT, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##"); + REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255), "age" INTEGER, "address" BIGINT, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Drop table sql statement string", "[query]") { dialect d; query_builder query(d); - const auto sql = query.drop().table("person").compile(); + const auto q = query.drop().table("person").compile(); - REQUIRE(sql == R"(DROP TABLE "person")"); + REQUIRE(q.sql == R"(DROP TABLE "person")"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Select sql statement string", "[query]") { - dialect d; - query_builder query(d); - const auto sql = query.select({"id", "name", "age"}).from("person").compile(); + dialect d; + query_builder query(d); + const auto q = query.select({"id", "name", "age"}).from("person").compile(); - REQUIRE(sql == R"(SELECT "id", "name", "age" FROM "person")"); + REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person")"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Insert sql statement string", "[query]") { dialect d; query_builder query(d); - const auto sql = query.insert().into("person", { + const auto q = query.insert().into("person", { "id", "name", "age" }).values({7UL, "george", 65U}).compile(); - REQUIRE(sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))"); + REQUIRE(q.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Update sql statement string", "[query]") { dialect d; query_builder query(d); - const auto sql = query.update("person").set({ + const auto q = query.update("person").set({ {"id", 7UL}, {"name", "george"}, {"age", 65U} }).compile(); - REQUIRE(sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)"); + REQUIRE(q.sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Delete sql statement string", "[query]") { dialect d; query_builder query(d); - const auto sql = query.remove().from("person").compile(); + const auto q = query.remove().from("person").compile(); - REQUIRE(sql == R"(DELETE FROM "person")"); + REQUIRE(q.sql == R"(DELETE FROM "person")"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Select sql statement string with where clause", "[query]") { dialect d; query_builder query(d); - auto sql = query.select({"id", "name", "age"}) - .from("person") - .where("id"_col == 8 && "age"_col > 50) - .compile(); + auto q = query.select({"id", "name", "age"}) + .from("person") + .where("id"_col == 8 && "age"_col > 50) + .compile(); - REQUIRE(sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))"); + REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))"); + REQUIRE(q.table_name == "person"); - sql = query.select({"id", "name", "age"}) - .from("person") - .where("id"_col == _ && "age"_col > 50) - .compile(); + q = query.select({"id", "name", "age"}) + .from("person") + .where("id"_col == _ && "age"_col > 50) + .compile(); - REQUIRE(sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))"); + REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Select sql statement string with order by", "[query]") { - dialect d; - query_builder query(d); - const auto sql = query.select({"id", "name", "age"}) - .from("person") - .order_by("name").asc() - .compile(); + dialect d; + query_builder query(d); + const auto q = query.select({"id", "name", "age"}) + .from("person") + .order_by("name").asc() + .compile(); - REQUIRE(sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)"); + REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Select sql statement string with group by", "[query]") { - dialect d; - query_builder query(d); - const auto sql = query.select({"id", "name", "age"}) - .from("person") - .group_by("age") - .compile(); + dialect d; + query_builder query(d); + const auto q = query.select({"id", "name", "age"}) + .from("person") + .group_by("age") + .compile(); - REQUIRE(sql == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")"); + REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")"); + REQUIRE(q.table_name == "person"); } TEST_CASE("Select sql statement string with offset and limit", "[query]") { dialect d; query_builder query(d); - const auto sql = query.select({"id", "name", "age"}) + const auto q = query.select({"id", "name", "age"}) .from("person") .offset(10) .limit(20) .compile(); - REQUIRE(sql == R"(SELECT "id", "name", "age" FROM "person" OFFSET 10 LIMIT 20)"); + REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" OFFSET 10 LIMIT 20)"); + REQUIRE(q.table_name == "person"); } \ No newline at end of file diff --git a/test/session.cpp b/test/session.cpp index 78fb06a..c631a27 100644 --- a/test/session.cpp +++ b/test/session.cpp @@ -79,13 +79,13 @@ 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(0).as() == 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(1).as() == "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); + REQUIRE(i.at(2).as() == 45); } s.drop().table("person").execute(); @@ -132,13 +132,13 @@ TEST_CASE("Execute update 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(0).as() == 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(1).as() == "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); + REQUIRE(i.at(2).as() == 35); } s.drop().table("person").execute(); @@ -161,23 +161,41 @@ TEST_CASE("Execute select statement with where clause", "[session]") { .execute(); REQUIRE(res.first == 1); - // fetch person as record - auto result_record = s.select() + // fetch specific columns as record + auto result_record = s.select({"id", "name", "age"}) .from("person") - .where("id"_col == 7) .fetch_all(); for (const auto& i : result_record) { 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(0).as() == 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(1).as() == 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); + REQUIRE(i.at(2).as() == george.age); + } + + // fetch person as record + result_record = s.select() + .from("person") + .where("id"_col == 7) + .fetch_all(); + + for (const auto& i : result_record) { + 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).as() == george.id); + REQUIRE(i.at(1).name() == "name"); + REQUIRE(i.at(1).type() == data_type_t::type_varchar); + REQUIRE(i.at(1).as() == 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).as() == george.age); } // fetch person as person