added any type visitor and converter

This commit is contained in:
Sascha Kuehl 2023-11-21 20:07:07 +01:00
parent 171e8d36ce
commit 145c4dc0a3
28 changed files with 730 additions and 163 deletions

View File

@ -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)

View File

@ -1,3 +1,45 @@
# query
A fluent sql query builder
Object definition
```cpp
struct airplane
{
unsigned long id;
std::string brand;
std::string model;
template<class Operator>
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<connection> pool("sqlite://sqlite.db", 4);
session s(pool);
// register entity
s.attach<airplane>("airplane")
// create all tables
s.create();
// insert
s.insert<airplane>(1, "Airbus", "A380");
s.insert<airplane>(2, "Boeing", "748");
s.insert<airplane>(3, "Boeing", "707");
s.update<airplane>().set({"model", "747"}).where("id"_col == 2);
auto result = s.select<airplane>().where("brand"_col == "Boeing");
for (const auto &plane : result) {
std::cout << "airplane: " << plane->brand << " - " << plane->model << "\n";
}
```

8
Todo.md Normal file
View File

@ -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

View File

@ -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);

View File

@ -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"

View File

@ -0,0 +1,131 @@
#ifndef QUERY_ANY_TYPE_TO_VISITOR_HPP
#define QUERY_ANY_TYPE_TO_VISITOR_HPP
#include <array>
#include <charconv>
#include <cstring>
#include <stdexcept>
#include <string>
#include <type_traits>
namespace matador::sql {
template < typename DestType, typename SourceType >
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_same<DestType, SourceType>::value>::type* = nullptr)
{
dest = source;
}
template < typename DestType, typename SourceType >
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_integral<DestType>::value && std::is_arithmetic<SourceType>::value && !std::is_same<DestType, SourceType>::value>::type* = nullptr)
{
dest = static_cast<DestType>(source);
}
template < typename DestType, typename SourceType >
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_floating_point<DestType>::value && std::is_arithmetic<SourceType>::value && !std::is_same<DestType, SourceType>::value>::type* = nullptr)
{
dest = static_cast<DestType>(source);
}
void convert(std::string &dest, bool source);
template < typename SourceType >
void convert(std::string &dest, SourceType source, typename std::enable_if<std::is_integral<SourceType>::value && !std::is_same<bool, SourceType>::value>::type* = nullptr)
{
std::array<char, 128> 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<std::is_floating_point<SourceType>::value>::type* = nullptr)
{
std::array<char, 128> 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<std::is_integral<DestType>::value && std::is_unsigned<DestType>::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<std::is_integral<DestType>::value && std::is_unsigned<DestType>::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<std::is_integral<DestType>::value && std::is_signed<DestType>::value>::type* = nullptr)
{
dest = to_long_long(source.c_str());
}
template < typename DestType >
void convert(DestType &dest, const char *source, typename std::enable_if<std::is_integral<DestType>::value && std::is_signed<DestType>::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<std::is_floating_point<DestType>::value>::type* = nullptr)
{
dest = to_double(source.c_str());
}
template < typename DestType >
void convert(DestType &dest, const char *source, typename std::enable_if<std::is_floating_point<DestType>::value>::type* = nullptr)
{
dest = to_double(source);
}
template < typename DestType >
void convert(DestType &dest, bool source, typename std::enable_if<std::is_floating_point<DestType>::value>::type* = nullptr)
{
dest = static_cast<DestType>(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

View File

@ -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<typename Type>
explicit column(std::string name, utils::field_attributes attr = utils::null_attributes)
: column(std::move(name), data_type_traits<Type>::builtin_type(attr.size()), attr)
{}
template<typename Type>
column(std::string name, const Type &, utils::field_attributes attr = utils::null_attributes)
: column(std::move(name), data_type_traits<Type>::builtin_type(attr.size()), attr)
{}
column(std::string name, data_type_t type, utils::field_attributes attr = utils::null_attributes);
template<typename Type>
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<Type>::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<Type>(value_);
}
template< typename Type >
std::optional<Type> value() const {
[[nodiscard]] std::string str() const;
template<typename Type>
void set(const Type &value, const utils::field_attributes &attr = utils::null_attributes)
{
type_ = data_type_traits<Type>::builtin_type(attr.size());
attributes_ = attr;
value_ = value;
}
void set(const std::string &value, const utils::field_attributes &attr)
{
type_ = data_type_traits<std::string>::builtin_type(attr.size());
attributes_ = attr;
value_ = value;
}
void set(const char *value, const utils::field_attributes &attr)
{
type_ = data_type_traits<std::string>::builtin_type(attr.size());
attributes_ = attr;
value_ = value;
}
template<class Type>
Type as() const
{
const Type* ptr= std::get_if<Type>(&value_);
return ptr ? std::make_optional<Type>(*ptr) : std::nullopt;
if (ptr) {
return *ptr;
}
any_type_to_visitor<Type> visitor;
std::visit(visitor, const_cast<any_type&>(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<Type>::builtin_type(0), ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }};
return {name, data_type_traits<Type>::builtin_type(0), 0, ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }};
}
template <>

View File

@ -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<typename ValueType>

View File

@ -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<class Type>
query_result<Type> fetch(const std::string &sql)

View File

@ -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);

View File

@ -57,6 +57,7 @@ enum class join_type_t {
struct query
{
std::string sql;
std::string table_name;
record prototype;
std::vector<any_type> 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);

View File

@ -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<std::string> column_names);
query_from_intermediate from(const std::string &table, const std::string &as = "");
};
@ -144,50 +150,50 @@ public:
template<class Type>
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<column> columns);
template<class Type>
query_execute_finish table(const std::string &table_name)
{
const auto &info = repository_.attach<Type>(table_name/*, record{column_generator::generate<Type>(repository_)}*/);
return {db(), query().table(table_name, info.prototype.columns())};
const auto &info = repository_.attach<Type>(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<std::string> column_names);
template<class Type>
query_into_intermediate into(const std::string &table)
{
return {db(), query().into(table, column_name_generator::generate<Type>())};
return {session_, builder_.into(table, column_name_generator::generate<Type>())};
}
template<class Type>
query_execute_finish into(const std::string &table, const Type &obj)
{
return {db(), query().into(table, column_name_generator::generate<Type>())
return {session_, builder_.into(table, column_name_generator::generate<Type>())
.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<key_value_pair> columns);
template<class Type>
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);
};

View File

@ -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<Type>())};
return query_select_intermediate{*this, column_name_generator::generate<Type>()};
}
query_select_intermediate select(std::initializer_list<std::string> column_names);
query_insert_intermediate insert();
query_update_intermediate update(const std::string &table);
query_delete_intermediate remove();
[[nodiscard]] query_result<record> fetch(const std::string &sql) const;
[[nodiscard]] query_result<record> fetch(const query &q) const;
// [[nodiscard]] query_result<record> fetch(const std::string &sql) const;
[[nodiscard]] std::pair<size_t, std::string> execute(const std::string &sql) const;
template<typename Type>
@ -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<connection> &pool_;
query_builder query_;
const class dialect &dialect_;
table_repository table_repository_;
mutable std::unordered_map<std::string, record> prototypes_;
};
}

View File

@ -180,7 +180,7 @@ template <> struct data_type_traits<const char*>
template <> struct data_type_traits<char*>
{
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<char*>
template <> struct data_type_traits<std::string>
{
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"; }
};

View File

@ -21,6 +21,7 @@ public:
~field_attributes() = default;
[[nodiscard]] size_t size() const;
void size(size_t size);
[[nodiscard]] constraints options() const;
private:

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<std::string> visitor;
std::visit(visitor, const_cast<any_type&>(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<std::string>( const std::string& name, size_t size )
template<>
[[maybe_unused]] column make_fk_column<std::string>(const std::string& name, size_t size, const std::string &ref_table, const std::string &ref_column)
{
return {name, data_type_traits<std::string>::builtin_type(size), ref_table, ref_column, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL}};
return {name, data_type_traits<std::string>::builtin_type(size), 0, ref_table, ref_column, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL}};
}
}

View File

@ -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<record> connection::fetch(const std::string &sql) const
{
auto rec = connection_->describe("person");

View File

@ -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<s
transition_to(state_t::QUERY_INTO);
query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::INTO) + " " + dialect_.prepare_identifier(table) + " ");
query_.table_name = table;
std::string result{"("};
if (column_names.size() < 2) {
@ -290,6 +294,7 @@ query_builder& query_builder::from(const std::string &table, const std::string &
transition_to(state_t::QUERY_FROM);
query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::FROM) + " " + dialect_.prepare_identifier(table) + (as.empty() ? "" : " " + as));
query_.table_name = table;
return *this;
}
@ -398,12 +403,11 @@ query_builder& query_builder::limit( size_t count ) {
return *this;
}
std::string query_builder::compile() {
std::string result;
query query_builder::compile() {
for (const auto &part : query_parts_) {
result.append(part);
query_.sql.append(part);
}
return result;
return query_;
}
void query_builder::transition_to(query_builder::state_t next)

View File

@ -2,145 +2,173 @@
#include "matador/sql/session.hpp"
namespace matador::sql {
session &query_intermediate::db()
{
return db_;
}
query_builder &query_intermediate::query()
{
return query_;
}
query_result<record> 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_result_impl> 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<std::string> 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<std::string> column_names)
{
return {db(), query().into(table, column_names)};
return {session_, builder_.into(table, column_names)};
}
std::pair<size_t, std::string> 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<any_type> 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<column> 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<key_value_pair> 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())
{}
}

View File

@ -8,46 +8,58 @@ namespace matador::sql {
session::session(connection_pool<connection> &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<std::string> 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<record> session::fetch(const std::string &sql) const
query_result<record> session::fetch(const query &q) const
{
auto res = call_fetch(sql);
auto proto = res->prototype();
return query_result<record>{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<record>{std::move(res), [it]() { return new record(it->second); }};
}
//query_result<record> session::fetch(const std::string &sql) const
//{
// return query_result<record>(std::unique_ptr());
//}
std::pair<size_t, std::string> 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<query_result_impl> session::call_fetch(const std::string &sql) const
{
auto c = pool_.acquire();

View File

@ -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_;

View File

@ -0,0 +1,78 @@
#include <catch2/catch_test_macros.hpp>
#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<std::string> 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<long> 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<double> 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);
}

View File

@ -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

63
test/ColumnTest.cpp Normal file
View File

@ -0,0 +1,63 @@
#include <catch2/catch_test_macros.hpp>
#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<std::string>() == "george");
c.set(7);
REQUIRE(c.type() == data_type_t::type_int);
REQUIRE(c.as<std::string>() == "7");
REQUIRE(c.as<long>() == 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<std::string>() == "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<std::string>() == "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<std::string>() == "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<std::string>().empty());
REQUIRE(c2.attributes().size() == 255);
}

View File

@ -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<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
}).compile();
dialect d;
query_builder query(d);
auto q = query.create().table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("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<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age"),
make_fk_column<unsigned long>("address", "address", "id")
}).compile();
q = query.create().table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age"),
make_fk_column<unsigned long>("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");
}

View File

@ -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<long long>() == 7);
REQUIRE(i.at(0).as<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>() == "george");
REQUIRE(i.at(1).as<std::string>() == "george");
REQUIRE(i.at(2).name() == "age");
REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int);
REQUIRE(i.at(2).value<int>() == 45);
REQUIRE(i.at(2).as<int>() == 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<long long>() == 7);
REQUIRE(i.at(0).as<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(1).as<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);
REQUIRE(i.at(2).as<int>() == 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<person>()
// 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<long long>() == george.id);
REQUIRE(i.at(0).as<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(1).as<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);
REQUIRE(i.at(2).as<long long>() == george.age);
}
// fetch person as record
result_record = s.select<person>()
.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<long long>() == george.id);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).type() == data_type_t::type_varchar);
REQUIRE(i.at(1).as<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).as<long long>() == george.age);
}
// fetch person as person