introduced table repository

This commit is contained in:
Sascha Kuehl 2023-11-16 20:15:52 +01:00
parent 7c1e940814
commit c801d53e8c
25 changed files with 589 additions and 150 deletions

View File

@ -183,36 +183,53 @@ bool sqlite_query_result::fetch()
return ++row_index_ < result_.size(); 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) 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) { switch (type) {
case sql::data_type_t::type_char: case sql::data_type_t::type_char:
convert<char>(result_[row_index_][index], value);
break;
case sql::data_type_t::type_short: case sql::data_type_t::type_short:
convert<short>(result_[row_index_][index], value);
break;
case sql::data_type_t::type_int: case sql::data_type_t::type_int:
convert<int>(result_[row_index_][index], value);
break;
case sql::data_type_t::type_long: case sql::data_type_t::type_long:
case sql::data_type_t::type_long_long: { convert<long>(result_[row_index_][index], value);
long long val{}; break;
read(val, result_[row_index_][index]); case sql::data_type_t::type_long_long:
value = val; convert<long long>(result_[row_index_][index], value);
break; break;
}
case sql::data_type_t::type_unsigned_char: case sql::data_type_t::type_unsigned_char:
convert<unsigned char>(result_[row_index_][index], value);
break;
case sql::data_type_t::type_unsigned_short: case sql::data_type_t::type_unsigned_short:
convert<unsigned short>(result_[row_index_][index], value);
break;
case sql::data_type_t::type_unsigned_int: case sql::data_type_t::type_unsigned_int:
convert<unsigned int>(result_[row_index_][index], value);
break;
case sql::data_type_t::type_unsigned_long: case sql::data_type_t::type_unsigned_long:
case sql::data_type_t::type_unsigned_long_long: { convert<unsigned long>(result_[row_index_][index], value);
unsigned long long val{}; break;
read(val, result_[row_index_][index]); case sql::data_type_t::type_unsigned_long_long:
value = val; convert<unsigned long long>(result_[row_index_][index], value);
break; break;
}
case sql::data_type_t::type_float: case sql::data_type_t::type_float:
case sql::data_type_t::type_double: { convert<float>(result_[row_index_][index], value);
double val{}; break;
read(val, result_[row_index_][index]); case sql::data_type_t::type_double:
value = val; convert<double>(result_[row_index_][index], value);
break; break;
}
case sql::data_type_t::type_bool: { case sql::data_type_t::type_bool: {
int val{}; int val{};
read(val, result_[row_index_][index]); read(val, result_[row_index_][index]);

View File

@ -9,8 +9,8 @@ namespace matador::sql {
using any_type = std::variant< using any_type = std::variant<
char, short, int, long, long long, char, short, int, long, long long,
unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long,
bool,
float, double, float, double,
bool,
const char*, const char*,
std::string, std::string,
nullptr_t>; nullptr_t>;

View File

@ -6,7 +6,8 @@
#include "matador/utils/field_attributes.hpp" #include "matador/utils/field_attributes.hpp"
#include <any> #include <optional>
#include <vector>
namespace matador::sql { namespace matador::sql {
@ -37,6 +38,17 @@ public:
[[nodiscard]] const std::string& ref_table() const; [[nodiscard]] const std::string& ref_table() const;
[[nodiscard]] const std::string& ref_column() const; [[nodiscard]] const std::string& ref_column() const;
template< typename Type >
[[nodiscard]] bool is_type_of() const {
return std::holds_alternative<Type>(value_);
}
template< typename Type >
std::optional<Type> value() const {
const Type* ptr= std::get_if<Type>(&value_);
return ptr ? std::make_optional<Type>(*ptr) : std::nullopt;
}
private: private:
template<class Operator> template<class Operator>
void process(Operator &op) void process(Operator &op)
@ -44,9 +56,13 @@ private:
op.on_attribute(name_.c_str(), value_, type_, attributes_); op.on_attribute(name_.c_str(), value_, type_, attributes_);
} }
using data_type_index = std::vector<data_type_t>;
private: private:
friend class record; friend class record;
static const data_type_index data_type_index_;
std::string name_; std::string name_;
utils::field_attributes attributes_; utils::field_attributes attributes_;
data_type_t type_{}; data_type_t type_{};

View File

@ -2,6 +2,7 @@
#define QUERY_COLUMN_GENERATOR_HPP #define QUERY_COLUMN_GENERATOR_HPP
#include "matador/sql/column.hpp" #include "matador/sql/column.hpp"
#include "matador/sql/table_repository.hpp"
#include "matador/sql/types.hpp" #include "matador/sql/types.hpp"
#include "matador/utils/access.hpp" #include "matador/utils/access.hpp"
@ -18,10 +19,10 @@ public:
fk_column_generator() = default; fk_column_generator() = default;
template<class Type> template<class Type>
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); 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<typename ValueType> template<typename ValueType>
@ -50,16 +51,16 @@ private:
class column_generator class column_generator
{ {
private: private:
explicit column_generator(std::vector<column> &columns); column_generator(std::vector<column> &columns, const table_repository &repo);
public: public:
~column_generator() = default; ~column_generator() = default;
template < class Type > template < class Type >
static std::vector<column> generate() static std::vector<column> generate(const table_repository &repo)
{ {
std::vector<column> columns; std::vector<column> columns;
column_generator gen(columns); column_generator gen(columns, repo);
Type obj; Type obj;
matador::utils::access::process(gen, obj); matador::utils::access::process(gen, obj);
return std::move(columns); return std::move(columns);
@ -76,21 +77,27 @@ public:
template<class Pointer> template<class Pointer>
void on_belongs_to(const char *id, Pointer &x, utils::cascade_type) 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<class Pointer> template<class Pointer>
void on_has_one(const char *id, Pointer &x, utils::cascade_type) 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<class ContainerType> template<class ContainerType>
void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {} void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {}
template<class ContainerType> template<class ContainerType>
void on_has_many(const char *, ContainerType &, utils::cascade_type) {} void on_has_many(const char *, ContainerType &, utils::cascade_type) {}
private:
std::pair<std::string, std::string> determine_foreign_ref(const std::type_index &ti);
private: private:
size_t index_ = 0; size_t index_ = 0;
std::vector<column> &columns_; std::vector<column> &columns_;
const table_repository &repo_;
fk_column_generator fk_column_generator_; fk_column_generator fk_column_generator_;
}; };

View File

@ -9,18 +9,22 @@ template < class Type >
class foreign class foreign
{ {
public: public:
using value_type = Type;
using pointer = value_type*;
using reference = value_type&;
foreign() = default; foreign() = default;
explicit foreign(Type *obj) explicit foreign(Type *obj)
: obj_(obj) {} : obj_(obj) {}
Type* operator->() { return obj_.get(); } pointer operator->() { return obj_.get(); }
const Type* operator->() const { return obj_.get(); } const value_type* operator->() const { return obj_.get(); }
Type& operator*() { return *obj_; } reference operator*() { return *obj_; }
const Type& operator*() const { return *obj_; } const value_type& operator*() const { return *obj_; }
private: private:
std::unique_ptr<Type> obj_; std::unique_ptr<value_type> obj_;
}; };
template<class Type, typename... Args> template<class Type, typename... Args>

View File

@ -11,7 +11,6 @@
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
namespace matador::sql { namespace matador::sql {
class dialect; class dialect;
@ -97,6 +96,7 @@ public:
query_builder& create(); query_builder& create();
query_builder& drop(); query_builder& drop();
query_builder& select(std::initializer_list<std::string> column_names); query_builder& select(std::initializer_list<std::string> column_names);
query_builder& select(const std::vector<std::string> &column_names);
query_builder& insert(); query_builder& insert();
query_builder& update(const std::string &table); query_builder& update(const std::string &table);
query_builder& remove(); query_builder& remove();

View File

@ -9,6 +9,7 @@
#include "matador/sql/query_builder.hpp" #include "matador/sql/query_builder.hpp"
#include "matador/sql/query_result.hpp" #include "matador/sql/query_result.hpp"
#include "matador/sql/record.hpp" #include "matador/sql/record.hpp"
#include "matador/sql/table_repository.hpp"
#include "matador/sql/value_extractor.hpp" #include "matador/sql/value_extractor.hpp"
#include <string> #include <string>
@ -142,14 +143,18 @@ public:
class query_create_intermediate : query_intermediate class query_create_intermediate : query_intermediate
{ {
public: 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<column> columns); query_execute_finish table(const std::string &table, std::initializer_list<column> columns);
template<class Type> template<class Type>
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<Type>())}; const auto &info = repository_.attach<Type>(table_name, record{column_generator::generate<Type>(repository_)});
return {db(), query().table(table_name, info.prototype.columns())};
} }
private:
table_repository &repository_;
}; };
class query_drop_intermediate : query_intermediate class query_drop_intermediate : query_intermediate

View File

@ -23,6 +23,7 @@ public:
record() = default; record() = default;
record(std::initializer_list<column> columns); record(std::initializer_list<column> columns);
explicit record(const std::vector<column> &columns);
record(const record&) = default; record(const record&) = default;
record& operator=(const record&) = default; record& operator=(const record&) = default;
record(record&&) noexcept = default; record(record&&) noexcept = default;
@ -44,8 +45,13 @@ public:
void append(column col); void append(column col);
bool has_primary_key() const;
const column& primary_key() const;
[[nodiscard]] const std::vector<column>& columns() const;
[[nodiscard]] const column& at(const std::string &name) 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); iterator find(const std::string &column_name);
[[nodiscard]] const_iterator find(const std::string &column_name) const; [[nodiscard]] const_iterator find(const std::string &column_name) const;
@ -62,9 +68,15 @@ public:
[[nodiscard]] bool empty() const; [[nodiscard]] bool empty() const;
void clear(); void clear();
private:
void init();
void add_to_map(column &col, size_t index);
private: private:
column_by_index columns_; column_by_index columns_;
column_by_name_map columns_by_name_; column_by_name_map columns_by_name_;
int pk_index_{-1};
}; };
} }

View File

@ -1,10 +1,14 @@
#ifndef QUERY_SESSION_HPP #ifndef QUERY_SESSION_HPP
#define QUERY_SESSION_HPP #define QUERY_SESSION_HPP
#include "matador/sql/column_name_generator.hpp"
#include "matador/sql/connection.hpp" #include "matador/sql/connection.hpp"
#include "matador/sql/connection_pool.hpp" #include "matador/sql/connection_pool.hpp"
#include "matador/sql/query_builder.hpp" #include "matador/sql/query_builder.hpp"
#include "matador/sql/query_intermediates.hpp" #include "matador/sql/query_intermediates.hpp"
#include "matador/sql/table_repository.hpp"
#include <unordered_map>
namespace matador::sql { namespace matador::sql {
@ -15,6 +19,11 @@ public:
query_create_intermediate create(); query_create_intermediate create();
query_drop_intermediate drop(); query_drop_intermediate drop();
template < class Type >
query_select_intermediate select()
{
return query_select_intermediate{*this, query_.select(column_name_generator::generate<Type>())};
}
query_select_intermediate select(std::initializer_list<std::string> column_names); query_select_intermediate select(std::initializer_list<std::string> column_names);
query_insert_intermediate insert(); query_insert_intermediate insert();
query_update_intermediate update(const std::string &table); query_update_intermediate update(const std::string &table);
@ -23,9 +32,19 @@ public:
query_result<record> fetch(const std::string &sql); query_result<record> fetch(const std::string &sql);
std::pair<size_t, std::string> execute(const std::string &sql); std::pair<size_t, std::string> execute(const std::string &sql);
template<typename Type>
void attach(const std::string &table_name)
{
table_repository_.attach<Type>(table_name);
}
[[nodiscard]] const table_repository& tables() const;
private: private:
connection_pool<connection> &pool_; connection_pool<connection> &pool_;
query_builder query_; query_builder query_;
table_repository table_repository_;
}; };
} }

View File

@ -0,0 +1,55 @@
#ifndef QUERY_TABLE_REPOSITORY_HPP
#define QUERY_TABLE_REPOSITORY_HPP
#include "matador/sql/record.hpp"
#include <optional>
#include <string>
#include <typeindex>
#include <unordered_map>
namespace matador::sql {
struct table_info
{
std::string name;
record prototype;
};
class table_repository
{
public:
template<typename Type>
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<typename Type>
std::optional<table_info> info()
{
return info(std::type_index(typeid(Type)));
}
std::optional<table_info> info(std::type_index ti);
template<typename Type>
[[nodiscard]] std::pair<std::string, std::string> reference() const
{
return reference(std::type_index(typeid(Type)));
}
[[nodiscard]] std::pair<std::string, std::string> reference(const std::type_index &ti) const;
private:
using repository = std::unordered_map<std::type_index, table_info>;
repository repository_;
};
;
}
#endif //QUERY_TABLE_REPOSITORY_HPP

View File

@ -4,13 +4,14 @@
namespace matador::utils { namespace matador::utils {
enum class constraints : unsigned char { enum class constraints : unsigned char {
NONE = 0, NONE = 0,
NOT_NULL = 1 << 0, NOT_NULL = 1 << 0,
INDEX = 1 << 1, INDEX = 1 << 1,
UNIQUE = 1 << 2, UNIQUE = 1 << 2,
PRIMARY_KEY = 1 << 3, PRIMARY_KEY = 1 << 3,
FOREIGN_KEY = 1 << 4, FOREIGN_KEY = 1 << 4,
DEFAULT = 1 << 5, DEFAULT = 1 << 5,
AUTO_INCREMENT = 1 << 6,
UNIQUE_NOT_NULL = UNIQUE | NOT_NULL UNIQUE_NOT_NULL = UNIQUE | NOT_NULL
}; };

View File

@ -15,7 +15,8 @@ set(SQL_SOURCES
sql/column_generator.cpp sql/column_generator.cpp
sql/column_name_generator.cpp sql/column_name_generator.cpp
sql/key_value_generator.cpp sql/key_value_generator.cpp
sql/fk_value_extractor.cpp) sql/fk_value_extractor.cpp
sql/table_repository.cpp)
set(SQL_HEADER set(SQL_HEADER
../include/matador/sql/dialect.hpp ../include/matador/sql/dialect.hpp
@ -42,7 +43,8 @@ set(SQL_HEADER
../include/matador/sql/any_type.hpp ../include/matador/sql/any_type.hpp
../include/matador/sql/key_value_generator.hpp ../include/matador/sql/key_value_generator.hpp
../include/matador/sql/foreign.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 set(UTILS_HEADER
../include/matador/utils/field_attributes.hpp ../include/matador/utils/field_attributes.hpp

View File

@ -3,6 +3,27 @@
#include "matador/sql/column.hpp" #include "matador/sql/column.hpp"
namespace matador::sql { 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) column::column(std::string name, data_type_t type, utils::field_attributes attr)
: name_(std::move(name)) : name_(std::move(name))
, type_(type) , type_(type)

View File

@ -2,8 +2,10 @@
namespace matador::sql { namespace matador::sql {
column_generator::column_generator(std::vector<column> &columns) column_generator::column_generator(std::vector<column> &columns, const table_repository &repo)
: columns_(columns) {} : columns_(columns)
, repo_(repo)
{}
void column_generator::on_primary_key(const char *id, std::string &pk, size_t size) 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); on_attribute(id, x);
} }
std::pair<std::string, std::string> 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) void fk_column_generator::on_primary_key(const char *, std::string &, size_t size)
{ {
type_ = data_type_traits<std::string>::builtin_type(size); type_ = data_type_traits<std::string>::builtin_type(size);

View File

@ -98,6 +98,11 @@ query_builder& query_builder::drop() {
} }
query_builder& query_builder::select(std::initializer_list<std::string> column_names) query_builder& query_builder::select(std::initializer_list<std::string> column_names)
{
return select(std::vector<std::string>{column_names});
}
query_builder& query_builder::select(const std::vector<std::string> &column_names)
{ {
initialize(command_t::SELECT, state_t::QUERY_SELECT); initialize(command_t::SELECT, state_t::QUERY_SELECT);

View File

@ -100,6 +100,10 @@ query_execute_finish query_into_intermediate::values(std::initializer_list<any_t
return {db(), query().values(values)}; return {db(), query().values(values)};
} }
query_create_intermediate::query_create_intermediate(session &db, query_builder &query, table_repository &repo)
: query_intermediate(db, query)
, repository_(repo) {}
query_execute_finish query_create_intermediate::table(const std::string &table, std::initializer_list<column> columns) query_execute_finish query_create_intermediate::table(const std::string &table, std::initializer_list<column> columns)
{ {
return {db(), query().table(table, columns)}; return {db(), query().table(table, columns)};

View File

@ -1,13 +1,38 @@
#include "matador/sql/record.hpp" #include "matador/sql/record.hpp"
#include <stdexcept>
namespace matador::sql { namespace matador::sql {
record::record(std::initializer_list<column> columns) record::record(std::initializer_list<column> columns)
: columns_(columns) { : columns_(columns)
size_t index{0}; {
for(auto &col : columns_) { init();
columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index++}); }
record::record(const std::vector<column> &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<column> &record::columns() const
{
return columns_;
} }
const column &record::at(const std::string &name) const 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) void record::append(column col)
{ {
auto &ref = columns_.emplace_back(std::move(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() record::iterator record::begin()
@ -83,4 +108,21 @@ void record::clear()
columns_by_name_.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<int>(index);
}
}
} }

View File

@ -12,7 +12,7 @@ session::session(connection_pool<connection> &pool)
query_create_intermediate session::create() 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() query_drop_intermediate session::drop()
@ -55,4 +55,9 @@ std::pair<size_t, std::string> session::execute(const std::string &sql) {
} }
return c->execute(sql); return c->execute(sql);
} }
const table_repository& session::tables() const
{
return table_repository_;
}
} }

View File

@ -0,0 +1,39 @@
#include "matador/sql/table_repository.hpp"
#include <stdexcept>
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_info> 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<std::string, std::string> 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 {};
}
}

View File

@ -20,7 +20,10 @@ add_executable(tests builder.cpp
column_name_generator.cpp column_name_generator.cpp
value_generator.cpp value_generator.cpp
models/category.hpp models/category.hpp
models/supplier.hpp) models/supplier.hpp
models/airplane.hpp
models/flight.hpp
models/person.hpp)
target_link_libraries(tests PRIVATE target_link_libraries(tests PRIVATE
Catch2::Catch2WithMain Catch2::Catch2WithMain
matador matador

View File

@ -1,14 +1,16 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include "matador/sql/column_generator.hpp" #include "matador/sql/column_generator.hpp"
#include "matador/sql/table_repository.hpp"
#include "models/product.hpp" #include "models/product.hpp"
using namespace matador::sql; using namespace matador::sql;
TEST_CASE("Generate columns from object", "[column generator]") { TEST_CASE("Generate columns from object", "[column generator]") {
table_repository repo;
auto columns = column_generator::generate<matador::test::product>(); auto columns = column_generator::generate<matador::test::product>(repo);
const std::vector<std::string> expected_columns = { const std::vector<std::string> expected_columns = {
"product_name", "product_name",

35
test/models/airplane.hpp Normal file
View File

@ -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 <string>
namespace matador::test {
struct airplane
{
unsigned long id;
std::string brand;
std::string model;
template<class Operator>
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

33
test/models/flight.hpp Normal file
View File

@ -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 <string>
namespace matador::test {
struct flight {
unsigned long id;
sql::foreign<test::airplane> airplane;
std::string pilot_name;
template<class Operator>
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

28
test/models/person.hpp Normal file
View File

@ -0,0 +1,28 @@
#ifndef QUERY_PERSON_HPP
#define QUERY_PERSON_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/field_attributes.hpp"
#include <string>
namespace matador::test {
struct person
{
unsigned long id{};
std::string name;
unsigned int age{};
template<class Operator>
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

View File

@ -5,11 +5,14 @@
#include <matador/sql/session.hpp> #include <matador/sql/session.hpp>
#include "models/product.hpp" #include "models/product.hpp"
#include "models/airplane.hpp"
#include "models/flight.hpp"
#include "models/person.hpp"
using namespace matador::sql; using namespace matador::sql;
using namespace matador::test; using namespace matador::test;
TEST_CASE("Execute create and drop table statement", "[session]") { TEST_CASE("Create and drop table statement", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4); connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool); session s(pool);
@ -27,13 +30,20 @@ TEST_CASE("Execute create and drop table statement", "[session]") {
REQUIRE(res.second == R"(DROP TABLE "person")"); REQUIRE(res.second == R"(DROP TABLE "person")");
} }
TEST_CASE("Execute create table statement with foreign keys", "[session]") { TEST_CASE("Create table with foreign key relation", "[session]") {
// res = s.create().table<product>("product").execute(); connection_pool<connection> 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>("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(); res = s.create().table<flight>("flight").execute();
// REQUIRE(res.first == 1); 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]") { TEST_CASE("Execute insert record statement", "[session]") {
@ -69,149 +79,216 @@ TEST_CASE("Execute insert record statement", "[session]") {
REQUIRE(i.size() == 3); REQUIRE(i.size() == 3);
REQUIRE(i.at(0).name() == "id"); REQUIRE(i.at(0).name() == "id");
REQUIRE(i.at(0).type() == data_type_t::type_long_long); REQUIRE(i.at(0).type() == data_type_t::type_long_long);
REQUIRE(i.at(0).value<long long>() == 7);
REQUIRE(i.at(1).name() == "name"); REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).type() == data_type_t::type_varchar); REQUIRE(i.at(1).type() == data_type_t::type_varchar);
REQUIRE(i.at(1).value<std::string>() == "george");
REQUIRE(i.at(2).name() == "age"); REQUIRE(i.at(2).name() == "age");
REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int); REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int);
REQUIRE(i.at(2).value<int>() == 45);
} }
s.drop().table("person").execute();
// using namespace matador::test; s.drop().table("person").execute();
// product p; }
// p.discontinued = false;
// p.reorder_level = 1; TEST_CASE("Execute update record statement", "[session]") {
// p.units_in_order = 2; connection_pool<connection> pool("sqlite://sqlite.db", 4);
// p.units_in_stock = 100; session s(pool);
// p.unit_price = 49;
// p.quantity_per_unit = "pcs"; auto res = s
// p.category = make_foreign<matador::test::category>(); .create()
// p.category->id = 7; .table("person", {
// p.supplier = make_foreign<matador::test::supplier>();; make_pk_column<unsigned long>("id"),
// p.supplier->id = 13; make_column<std::string>("name", 255),
// p.product_name = "candle"; make_column<unsigned short>("age")
// })
// res = s.insert().into<product>("product").values(p).execute(); .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))"); REQUIRE(res.first == 0);
//
// res = s.insert().into("product", p).execute(); res = s
// .insert()
// 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))"); .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<long long>() == 7);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).type() == data_type_t::type_varchar);
REQUIRE(i.at(1).value<std::string>() == "jane");
REQUIRE(i.at(2).name() == "age");
REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int);
REQUIRE(i.at(2).value<int>() == 35);
}
s.drop().table("person").execute();
} }
TEST_CASE("Execute select statement with where clause", "[session]") { TEST_CASE("Execute select statement with where clause", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4); connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool); session s(pool);
auto res = s.select({"id", "name", "color"}) auto res = s.create()
.from("person") .table<person>("person")
.where("id"_col == 8) .execute();
.fetch_all();
REQUIRE(res.first == 0);
person george{7, "george", 45};
res = s.insert()
.into("person", george)
.execute();
REQUIRE(res.first == 1);
auto result = s.select<person>()
.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<long long>() == george.id);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).type() == data_type_t::type_varchar);
REQUIRE(i.at(1).value<std::string>() == 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<long long>() == george.age);
}
// Todo: prepare test data
REQUIRE(true);
// REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); // 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]") { TEST_CASE("Execute select statement with order by", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4); connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool); session s(pool);
auto res = s.select({"id", "name", "color"}) auto res = s
.from("person") .create()
.where("id"_col == 8) .table("person", {
.order_by("name").desc() make_pk_column<unsigned long>("id"),
.fetch_all(); make_column<std::string>("name", 255),
make_column<std::string>("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 // 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]") { TEST_CASE("Execute select statement with group by and order by", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4); connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool); session s(pool);
auto res = s.select({"id", "name", "color"}) auto res = s.create()
.from("person") .table("person", {
.where("id"_col == 8) make_pk_column<unsigned long>("id"),
.group_by("color") make_column<std::string>("name", 255),
.order_by("name").asc() make_column<std::string>("color", 63)
.fetch_all(); })
.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 // 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]") { TEST_CASE("Execute insert statement", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4); connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool); session s(pool);
auto res = s.insert() auto res = s.create()
.into("person", {"id", "name", "color"}) .table("person", {
.values({7, "george", "green"}) make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<std::string>("color", 63)
})
.execute(); .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'))"); REQUIRE(res.second == R"(INSERT INTO "person" ("id", "name", "color") VALUES (7, 'george', 'green'))");
using namespace matador::test; s.drop().table("person").execute();
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<matador::test::category>();
p.category->id = 7;
p.supplier = make_foreign<matador::test::supplier>();
p.supplier->id = 13;
p.product_name = "candle";
res = s.insert().into<product>("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))");
} }
TEST_CASE("Execute update statement", "[session]") { TEST_CASE("Execute update statement", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4); connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool); session s(pool);
auto res = s.update("person") auto res = s.create()
.set({ .table("person", {
{"name", "george"}, make_pk_column<unsigned long>("id"),
{"color", "green"} make_column<std::string>("name", 255),
}) make_column<std::string>("color", 63)
.where("id"_col == 9) })
.execute(); .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; REQUIRE(res.first == 1);
product p; REQUIRE(res.second == R"(INSERT INTO "person" ("id", "name", "color") VALUES (7, 'george', 'green'))");
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<matador::test::category>();
p.category->id = 7;
p.supplier = make_foreign<matador::test::supplier>();
p.supplier->id = 13;
p.product_name = "candle";
res = s.update("product") res = s.update("person")
.set(p) .set({
{"name", "george"},
{"color", "green"}
})
.where("id"_col == 9) .where("id"_col == 9)
.execute(); .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]") { TEST_CASE("Execute delete statement", "[session]") {