555 lines
18 KiB
C++
555 lines
18 KiB
C++
#include "catch2/catch_test_macros.hpp"
|
|
#include "catch2/matchers/catch_matchers_string.hpp"
|
|
|
|
#include "matador/object/attribute.hpp"
|
|
#include "matador/object/repository.hpp"
|
|
|
|
#include "matador/sql/connection.hpp"
|
|
#include "matador/sql/dialect.hpp"
|
|
|
|
#include "matador/query/criteria.hpp"
|
|
#include "matador/query/generator.hpp"
|
|
#include "matador/query/query.hpp"
|
|
|
|
#include "matador/utils/basic_types.hpp"
|
|
#include "matador/utils/string.hpp"
|
|
|
|
#include "models/types.hpp"
|
|
|
|
#include "QueryFixture.hpp"
|
|
|
|
#include <iostream>
|
|
|
|
using namespace matador::test;
|
|
using namespace matador::sql;
|
|
using namespace matador::object;
|
|
using namespace matador::query;
|
|
|
|
TEST_CASE_METHOD( QueryFixture, "Insert and select basic datatypes", "[query][datatypes]" ) {
|
|
REQUIRE(repo.attach<types>("types"));
|
|
auto res = query::create()
|
|
.table<types>("types", repo)
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
tables_to_drop.emplace("types");
|
|
|
|
float float_value = 2.44557f;
|
|
double double_value = 11111.23433345;
|
|
int8_t cval = 'c';
|
|
short sval = (std::numeric_limits<short>::min)();
|
|
int ival = (std::numeric_limits<int>::min)();
|
|
int64_t llval = (std::numeric_limits<long long>::max)();
|
|
unsigned char ucval = (std::numeric_limits<unsigned char>::max)();
|
|
unsigned short usval = (std::numeric_limits<unsigned short>::max)();
|
|
unsigned int uival = (std::numeric_limits<unsigned int>::max)();
|
|
uint64_t ullval = (std::numeric_limits<unsigned long long>::max)();
|
|
if (db.type() == "sqlite" || db.type() == "postgres") {
|
|
ullval = (std::numeric_limits<long long>::max)();
|
|
}
|
|
bool bval = true;
|
|
const char *cstr("Armer schwarzer Kater");
|
|
std::string varcharval("hallo welt");
|
|
std::string strval = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
|
|
"nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, "
|
|
"sed diam voluptua. At vero eos et accusam et justo duo dolores et ea "
|
|
"rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. "
|
|
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy "
|
|
"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. "
|
|
"At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd "
|
|
"gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
|
|
// matador::date date_val(15, 3, 2015);
|
|
// auto time_val = matador::time(2015, 3, 15, 13, 56, 23, 123);
|
|
matador::utils::blob blob_val {1,2,3,4,5,6,7,8};
|
|
|
|
types t {
|
|
1,
|
|
cval, sval, ival, llval,
|
|
ucval, usval, uival, ullval,
|
|
float_value, double_value,
|
|
bval,
|
|
// "Armer schwarzer Kater",
|
|
strval, varcharval,
|
|
// date_val, time_val,
|
|
blob_val
|
|
};
|
|
|
|
res = query::insert()
|
|
.into("types", generator::columns<types>(repo))
|
|
.values(t)
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
auto result = query::select<types>(repo)
|
|
.from("types")
|
|
.fetch_one<types>(db);
|
|
REQUIRE(result.is_ok());
|
|
REQUIRE(*result != nullptr);
|
|
|
|
REQUIRE((*result)->id_ == 1);
|
|
REQUIRE((*result)->char_ == cval);
|
|
REQUIRE((*result)->short_ == sval);
|
|
REQUIRE((*result)->int_ == ival);
|
|
REQUIRE((*result)->long64_ == llval);
|
|
REQUIRE((*result)->unsigned_char_ == ucval);
|
|
REQUIRE((*result)->unsigned_short_ == usval);
|
|
REQUIRE((*result)->unsigned_int_ == uival);
|
|
REQUIRE((*result)->unsigned_long64_ == ullval);
|
|
REQUIRE((*result)->float_ == float_value);
|
|
REQUIRE((*result)->double_ == double_value);
|
|
// REQUIRE(strcmp((*result)->cstr_, cstr) == 0);
|
|
REQUIRE((*result)->bool_ == bval);
|
|
REQUIRE((*result)->varchar_ == varcharval);
|
|
REQUIRE((*result)->string_ == strval);
|
|
// REQUIRE((*result)->date_ == date_val);
|
|
// REQUIRE((*result)->time_ == time_val);
|
|
REQUIRE((*result)->binary_ == blob_val);
|
|
}
|
|
|
|
TEST_CASE_METHOD( QueryFixture, "Test quoted identifier", "[query][quotes][identifier]" ) {
|
|
using namespace matador::sql;
|
|
|
|
auto res = query::create()
|
|
.table("quotes", {
|
|
make_column<std::string>("from", 255),
|
|
make_column<std::string>("to", 255)
|
|
})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
tables_to_drop.emplace("quotes");
|
|
|
|
// check table description
|
|
std::vector<std::string> column_names = { "from", "to"};
|
|
std::vector<matador::utils::basic_type> types = {matador::utils::basic_type::type_varchar, matador::utils::basic_type::type_varchar};
|
|
const auto columns = db.describe("quotes");
|
|
REQUIRE(columns.is_ok());
|
|
|
|
for (const auto &col : *columns) {
|
|
REQUIRE(col.name() == column_names[col.index()]);
|
|
REQUIRE(col.type() == types[col.index()]);
|
|
}
|
|
|
|
res = query::insert()
|
|
.into("quotes", {"from", "to"})
|
|
.values({"Berlin", "London"})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
auto row = query::select({"from", "to"})
|
|
.from("quotes")
|
|
.fetch_one(db);
|
|
REQUIRE(row.is_ok());
|
|
|
|
REQUIRE(row->has_value());
|
|
REQUIRE(row.value()->at("from").as<std::string>() == "Berlin");
|
|
REQUIRE(row.value()->at("to").as<std::string>() == "London");
|
|
|
|
res = query::update("quotes")
|
|
.set({{"from", "Hamburg"}, {"to", "New York"}})
|
|
.where("from"_col == "Berlin")
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
row = query::select({"from", "to"})
|
|
.from("quotes")
|
|
.fetch_one(db);
|
|
REQUIRE(row.is_ok());
|
|
|
|
REQUIRE(row->has_value());
|
|
REQUIRE(row.value()->at("from").as<std::string>() == "Hamburg");
|
|
REQUIRE(row.value()->at("to").as<std::string>() == "New York");
|
|
}
|
|
|
|
TEST_CASE_METHOD( QueryFixture, "Test quoted column names", "[query][quotes][column]" ) {
|
|
using namespace matador::sql;
|
|
|
|
const auto start_quote = db.dialect().token_at(matador::sql::dialect_token::StartQuote);
|
|
const auto end_quote = db.dialect().token_at(matador::sql::dialect_token::EndQuote);
|
|
|
|
const std::string column_name = "name_with_" + start_quote + "open_close_quotes" + end_quote + "_in_backend_ctx";
|
|
|
|
std::vector<std::string> column_names = {
|
|
"normal_name",
|
|
column_name,
|
|
"name_with_'string'_\"literal\"_quotes",
|
|
"name_with_`identifier_quotes`_in_backend_ctx",
|
|
"from"
|
|
};
|
|
tables_to_drop.emplace("quotes");
|
|
|
|
for (const auto &name : column_names) {
|
|
auto res = query::create()
|
|
.table("quotes", {
|
|
make_column<std::string>(name, 255),
|
|
})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
|
|
const auto columns = db.describe("quotes");
|
|
REQUIRE(columns.is_ok());
|
|
|
|
for (const auto &col : *columns) {
|
|
REQUIRE(col.name() == name);
|
|
REQUIRE(col.type() == matador::utils::basic_type::type_varchar);
|
|
}
|
|
|
|
res = query::drop()
|
|
.table("quotes")
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(QueryFixture, "Test quoted literals", "[query][quotes][literals]") {
|
|
using namespace matador::sql;
|
|
|
|
auto res = query::create()
|
|
.table("escapes", {
|
|
make_column<std::string>("name", 255),
|
|
})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
tables_to_drop.emplace("escapes");
|
|
|
|
res = query::insert()
|
|
.into("escapes", {"name"})
|
|
.values({"text"})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
auto row = query::select({"name"})
|
|
.from("escapes")
|
|
.fetch_one(db);
|
|
REQUIRE(row.is_ok());
|
|
|
|
REQUIRE(row->has_value());
|
|
REQUIRE(row.value()->at("name").as<std::string>() == "text");
|
|
|
|
res = query::update("escapes")
|
|
.set({{"name", "text'd"}})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
row = query::select({"name"})
|
|
.from("escapes")
|
|
.fetch_one(db);
|
|
REQUIRE(row.is_ok());
|
|
|
|
REQUIRE(row->has_value());
|
|
REQUIRE(row.value()->at("name").as<std::string>() == "text'd");
|
|
|
|
res = query::update("escapes")
|
|
.set({{"name", "text\nhello\tworld"}})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
row = query::select({"name"})
|
|
.from("escapes")
|
|
.fetch_one(db);
|
|
REQUIRE(row.is_ok());
|
|
|
|
REQUIRE(row->has_value());
|
|
REQUIRE(row.value()->at("name").as<std::string>() == "text\nhello\tworld");
|
|
|
|
res = query::update("escapes")
|
|
.set({{"name", "text \"text\""}})
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
row = query::select({"name"})
|
|
.from("escapes")
|
|
.fetch_one(db);
|
|
REQUIRE(row.is_ok());
|
|
|
|
REQUIRE(row->has_value());
|
|
REQUIRE(row.value()->at("name").as<std::string>() == "text \"text\"");
|
|
}
|
|
|
|
TEST_CASE_METHOD(QueryFixture, "Test describe table", "[query][describe][table]") {
|
|
using namespace matador::sql;
|
|
|
|
REQUIRE(repo.attach<types>("types"));
|
|
const auto res = query::create()
|
|
.table<types>("types", repo)
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
tables_to_drop.emplace("types");
|
|
|
|
const auto columns = db.describe("types");
|
|
REQUIRE(columns.is_ok());
|
|
|
|
std::vector<std::string> column_names = { "id",
|
|
"val_char", "val_float", "val_double", "val_short",
|
|
"val_int", "val_long_long", "val_unsigned_char",
|
|
"val_unsigned_short", "val_unsigned_int", "val_unsigned_long_long",
|
|
"val_bool", /*"val_cstr", */"val_string", "val_varchar",
|
|
// "val_date", "val_time",
|
|
"val_binary"};
|
|
const std::vector<std::function<bool (const attribute&)>> type_check = {
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_floating_point(); },
|
|
[](const attribute &cf) { return cf.is_floating_point(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
// [](const attribute_definition &cf) { return cf.is_integer(); },
|
|
// [](const attribute_definition &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_integer(); },
|
|
[](const attribute &cf) { return cf.is_bool(); },
|
|
// [](const attribute_definition &cf) { return cf.is_varchar(); },
|
|
[](const attribute &cf) { return cf.is_string(); },
|
|
[](const attribute &cf) { return cf.is_varchar(); },
|
|
// [](const attribute_definition &cf) { return cf.is_date(); },
|
|
// [](const attribute_definition &cf) { return cf.is_time(); },
|
|
[](const attribute &cf) { return cf.is_blob(); }
|
|
};
|
|
|
|
const auto &cols = columns.value();
|
|
for (const auto &col : cols) {
|
|
REQUIRE(col.name() == column_names[col.index()]);
|
|
REQUIRE(type_check[col.index()](col));
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(QueryFixture, "Test unknown table", "[query][table]") {
|
|
const auto result = query::select({"name"})
|
|
.from("person")
|
|
.fetch_all(db);
|
|
|
|
REQUIRE(result.is_error());
|
|
}
|
|
|
|
namespace matador::test::temporary {
|
|
struct pk {
|
|
template<class Operator>
|
|
void process(Operator &op) {
|
|
matador::access::primary_key(op, "id", id);
|
|
matador::access::attribute(op, "name", name, 255);
|
|
}
|
|
|
|
uint32_t id{};
|
|
std::string name;
|
|
};
|
|
}
|
|
|
|
TEST_CASE_METHOD(QueryFixture, "Test primary key", "[query][primary key]") {
|
|
using namespace matador::test::temporary;
|
|
using namespace matador::sql;
|
|
|
|
REQUIRE(repo.attach<pk>("pk"));
|
|
auto res = query::create()
|
|
.table<pk>("pk", repo)
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
tables_to_drop.emplace("pk");
|
|
|
|
pk pk1{ 7, "george" };
|
|
|
|
res = query::insert()
|
|
.into("pk", generator::columns<pk>(repo))
|
|
.values(pk1)
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
auto row = query::select<pk>(repo)
|
|
.from("pk")
|
|
.fetch_one<pk>(db);
|
|
REQUIRE(row.is_ok());
|
|
REQUIRE(*row != nullptr);
|
|
REQUIRE(row.value()->id > 0);
|
|
}
|
|
|
|
TEST_CASE_METHOD(QueryFixture, "Test primary key prepared", "[query][primary key][prepared]") {
|
|
using namespace matador::test::temporary;
|
|
using namespace matador::sql;
|
|
|
|
REQUIRE(repo.attach<pk>("pk"));
|
|
auto res = query::create()
|
|
.table<pk>("pk", repo)
|
|
.execute(db);
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 0);
|
|
tables_to_drop.emplace("pk");
|
|
|
|
pk pk1{ 7, "george" };
|
|
|
|
auto stmt = query::insert()
|
|
.into("pk", generator::columns<pk>(repo))
|
|
.values(generator::placeholders<pk>())
|
|
.prepare(db);
|
|
REQUIRE(stmt);
|
|
|
|
res = stmt->bind(pk1)
|
|
.execute();
|
|
REQUIRE(res.is_ok());
|
|
REQUIRE(*res == 1);
|
|
|
|
stmt = query::select<pk>(repo)
|
|
.from("pk")
|
|
.prepare(db);
|
|
REQUIRE(stmt);
|
|
|
|
auto row = stmt->fetch_one<pk>();
|
|
REQUIRE(row.is_ok());
|
|
REQUIRE(*row != nullptr);
|
|
REQUIRE(row.value()->id > 0);
|
|
REQUIRE(row.value()->name == "george");
|
|
}
|
|
|
|
// namespace matador::test::temporary {
|
|
// struct appointment
|
|
// {
|
|
// unsigned long id{};
|
|
// std::string name;
|
|
// matador::time time_point{};
|
|
// matador::date date_point{};
|
|
//
|
|
// template < class Operator >
|
|
// void process(Operator &op)
|
|
// {
|
|
// matador::access::primary_key(op, "id", id);
|
|
// matador::access::attribute(op, "name", name, 255);
|
|
// matador::access::attribute(op, "time_point", time_point);
|
|
// matador::access::attribute(op, "date_point", date_point);
|
|
// }
|
|
// };
|
|
//
|
|
// }
|
|
// TEST_CASE_METHOD(QueryFixture, "Test select time and date", "[query][select][time]") {
|
|
// using namespace matador::test::temporary;
|
|
// using namespace matador::sql;
|
|
// REQUIRE(schema.attach<appointment>("appointment"));
|
|
// auto res = query::create()
|
|
// .table<appointment>("appointment", schema)
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 0);
|
|
// tables_to_drop.emplace("appointment");
|
|
//
|
|
// auto dinner = appointment{ 1, "dinner" };
|
|
// auto time_str = matador::utils::to_string(dinner.time_point);
|
|
// auto date_str = matador::utils::to_string(dinner.date_point);
|
|
//
|
|
// res = query::insert()
|
|
// .into("appointment", column_generator::generate<appointment>(schema))
|
|
// .values(dinner)
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 1);
|
|
//
|
|
// auto row = query::select<appointment>(schema)
|
|
// .from("appointment")
|
|
// .fetch_one<appointment>(db);
|
|
// REQUIRE(row.is_ok());
|
|
//
|
|
// REQUIRE(*row != nullptr);
|
|
// REQUIRE(matador::utils::to_string(row.value()->time_point) == time_str);
|
|
// REQUIRE(matador::utils::to_string(row.value()->date_point) == date_str);
|
|
// }
|
|
//
|
|
// TEST_CASE_METHOD(QueryFixture, "Test null column", "[query][select][null]") {
|
|
// using namespace matador::sql;
|
|
//
|
|
// auto res = query::create()
|
|
// .table("person", {
|
|
// make_pk_column<unsigned long>("id"),
|
|
// make_column<std::string>("first_name", 255, null_option::NULLABLE),
|
|
// make_column<std::string>("last_name", 255, null_option::NULLABLE)
|
|
// })
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 0);
|
|
// tables_to_drop.emplace("person");
|
|
//
|
|
// res = query::insert()
|
|
// .into("person", {"id", "first_name"})
|
|
// .values({1, "george"})
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 1);
|
|
//
|
|
// res = query::insert()
|
|
// .into("person", {"id", "last_name"})
|
|
// .values({2, "clooney"})
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 1);
|
|
//
|
|
// auto result = query::select({"id", "first_name", "last_name"})
|
|
// .from("person")
|
|
// .fetch_all(db);
|
|
// REQUIRE(result.is_ok());
|
|
//
|
|
// std::vector<std::string> expected_first_names{"george", ""};
|
|
// std::vector<std::string> expected_last_names{"", "clooney"};
|
|
// size_t index{0};
|
|
// for (const auto& row : *result) {
|
|
// auto first_name = row.at<std::string>("first_name");
|
|
// auto last_name = row.at<std::string>("last_name");
|
|
// std::cout << "first name " << first_name.value() << " last name " << last_name.value() << std::endl;
|
|
// REQUIRE(first_name == expected_first_names[index]);
|
|
// REQUIRE(last_name == expected_last_names[index++]);
|
|
// }
|
|
// }
|
|
//
|
|
// TEST_CASE_METHOD(QueryFixture, "Test null column prepared", "[query][select][null][prepared]") {
|
|
// using namespace matador::sql;
|
|
//
|
|
// auto res = query::create()
|
|
// .table("person", {
|
|
// make_pk_column<unsigned long>("id"),
|
|
// make_column<std::string>("first_name", 255, null_option::NULLABLE),
|
|
// make_column<std::string>("last_name", 255, null_option::NULLABLE)
|
|
// })
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 0);
|
|
// tables_to_drop.emplace("person");
|
|
//
|
|
// res = query::insert()
|
|
// .into("person", {"id", "first_name"})
|
|
// .values({1, "george"})
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 1);
|
|
//
|
|
// res = query::insert()
|
|
// .into("person", {"id", "last_name"})
|
|
// .values({2, "clooney"})
|
|
// .execute(db);
|
|
// REQUIRE(res.is_ok());
|
|
// REQUIRE(*res == 1);
|
|
//
|
|
// auto result = query::select({"id", "first_name", "last_name"})
|
|
// .from("person")
|
|
// .fetch_all(db);
|
|
// REQUIRE(result.is_ok());
|
|
//
|
|
// std::vector<std::string> expected_first_names{"george", ""};
|
|
// std::vector<std::string> expected_last_names{"", "clooney"};
|
|
// size_t index{0};
|
|
// for (const auto& row : *result) {
|
|
// auto first_name = row.at("first_name").as<std::string>();
|
|
// auto last_name = row.at("last_name").as<std::string>();
|
|
// REQUIRE(first_name == expected_first_names[index]);
|
|
// REQUIRE(last_name == expected_last_names[index++]);
|
|
// }
|
|
// }
|