initial matador ng commit

This commit is contained in:
2025-02-02 20:37:12 +01:00
parent 19de5714f4
commit ded3daceb3
378 changed files with 14913 additions and 13431 deletions
-78
View File
@@ -1,78 +0,0 @@
#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);
}
-25
View File
@@ -1,25 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/connection_info.hpp"
#include "matador/sql/backend_provider.hpp"
#include "matador/utils/os.hpp"
using namespace matador::sql;
TEST_CASE("Load backend", "[backend provider]") {
auto path = matador::utils::os::getenv("MATADOR_BACKENDS_PATH");
REQUIRE(!path.empty());
if (path.back() != '\\') {
path.push_back('\\');
}
REQUIRE(!path.empty());
connection_info ci{};
const auto &d = backend_provider::instance().connection_dialect("noop");
auto *connection = backend_provider::instance().create_connection("noop", ci);
REQUIRE(connection != nullptr);
backend_provider::instance().destroy_connection("noop", connection);
}
+3 -55
View File
@@ -1,56 +1,4 @@
Include(FetchContent)
enable_testing()
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.4 # or a later release
)
FetchContent_MakeAvailable(Catch2)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
include(CTest)
include(Catch)
add_executable(tests
QueryBuilderTest.cpp
TableDefinitionTest.cpp
ConnectionPoolTest.cpp
BackendProviderTest.cpp
models/product.hpp
models/order.hpp
models/order_details.hpp
ColumnDefinitionGeneratorTest.cpp
ColumnGeneratorTest.cpp
ValueGeneratorTest.cpp
models/category.hpp
models/supplier.hpp
models/airplane.hpp
models/flight.hpp
models/person.hpp
AnyTypeToVisitorTest.cpp
ColumnTest.cpp
models/coordinate.hpp
models/location.hpp
models/optional.hpp
ConvertTest.cpp
EntityQueryBuilderTest.cpp
models/author.hpp
models/book.hpp
FieldTest.cpp
models/recipe.hpp
ValueTest.cpp
ResultTest.cpp
utils/auto_reset_event.hpp
utils/auto_reset_event.cpp
models/student.hpp)
target_link_libraries(tests PRIVATE
Catch2::Catch2WithMain
matador
${CMAKE_DL_LIBS}
${SQLite3_LIBRARIES}
${PostgreSQL_LIBRARY})
target_include_directories(tests PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/include)
catch_discover_tests(tests)
add_subdirectory(core)
add_subdirectory(orm)
-56
View File
@@ -1,56 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/column_definition_generator.hpp"
#include "matador/sql/schema.hpp"
#include "models/product.hpp"
#include "models/optional.hpp"
using namespace matador::sql;
using namespace matador::utils;
TEST_CASE("Generate column definitions from object", "[column][definition][generator]") {
schema repo("main");
auto columns = column_definition_generator::generate<matador::test::product>(repo);
const std::vector<column_definition> expected_columns = {
column_definition{"product_name", data_type_t::type_varchar, constraints::PRIMARY_KEY, null_option::NOT_NULL },
column_definition{"supplier_id", data_type_t::type_unsigned_long, constraints::FOREIGN_KEY, null_option::NOT_NULL },
column_definition{"category_id", data_type_t::type_unsigned_long, constraints::FOREIGN_KEY, null_option::NOT_NULL },
column_definition{"quantity_per_unit", data_type_t::type_varchar, null_attributes, null_option::NOT_NULL },
column_definition{"unit_price", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL },
column_definition{"units_in_stock", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL },
column_definition{"units_in_order", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL },
column_definition{"reorder_level", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL },
column_definition{"discontinued", data_type_t::type_bool, null_attributes, null_option::NOT_NULL }
};
REQUIRE(!columns.empty());
REQUIRE(columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].name() == columns[i].name());
REQUIRE(expected_columns[i].attributes().options() == columns[i].attributes().options() );
REQUIRE(expected_columns[i].type() == columns[i].type() );
}
}
TEST_CASE("Generate columns from object with nullable columns", "[column generator]") {
schema repo("main");
auto columns = column_definition_generator::generate<matador::test::optional>(repo);
const std::vector<column_definition> expected_columns = {
column_definition{"id", data_type_t::type_unsigned_long, constraints::PRIMARY_KEY, null_option::NOT_NULL },
column_definition{"name", data_type_t::type_varchar, null_attributes, null_option::NOT_NULL },
column_definition{"age", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL }
};
REQUIRE(!columns.empty());
REQUIRE(columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].name() == columns[i].name());
REQUIRE(expected_columns[i].attributes().options() == columns[i].attributes().options() );
REQUIRE(expected_columns[i].type() == columns[i].type() );
}
}
-98
View File
@@ -1,98 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/column_generator.hpp"
#include "matador/sql/schema.hpp"
#include "models/order.hpp"
#include "models/product.hpp"
#include "models/book.hpp"
#include "models/author.hpp"
using namespace matador::sql;
TEST_CASE("Generate columns from object", "[column][generator]") {
using namespace matador::test;
schema s("main");
s.attach<product>("product");
auto columns = column_generator::generate<product>(s);
const std::vector<std::string> expected_columns = {
"product_name",
"supplier_id",
"category_id",
"quantity_per_unit",
"unit_price",
"units_in_stock",
"units_in_order",
"reorder_level",
"discontinued"
};
REQUIRE(!columns.empty());
REQUIRE(columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i] == columns[i].name);
}
}
TEST_CASE("Generate columns for object with has many relation", "[column][generator][relation]") {
using namespace matador::test;
schema s("main");
s.attach<product>("product");
s.attach<order_details>("order_details");
s.attach<order>("order");
auto columns = column_generator::generate<order>(s);
const std::vector<column> expected_columns = {
{ "order", "order_id", "c01" },
{ "order", "order_date", "c02" },
{ "order", "required_date", "c03" },
{ "order", "shipped_date", "c04" },
{ "order", "ship_via", "c05" },
{ "order", "freight", "c06" },
{ "order", "ship_name", "c07" },
{ "order", "ship_address", "c08" },
{ "order", "ship_city", "c09" },
{ "order", "ship_region", "c10" },
{ "order", "ship_postal_code", "c11" },
{ "order", "ship_country", "c12" },
{ "order_details", "order_details_id", "c13" },
{ "order_details", "order_id", "c14" },
{ "order_details", "product_id", "c15" }
};
REQUIRE(!columns.empty());
REQUIRE(columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(columns[i]));
}
}
TEST_CASE("Generate columns for object with eager foreign key relation", "[column][generator][eager]") {
using namespace matador::test;
schema s("main");
s.attach<book>("books");
s.attach<author>("authors");
const std::vector<column> expected_columns {
{ "books", "id", "c01" },
{ "books", "title", "c02" },
{ "authors", "id", "c03" },
{ "authors", "first_name", "c04" },
{ "authors", "last_name", "c05" },
{ "authors", "date_of_birth", "c06" },
{ "authors", "year_of_birth", "c07" },
{ "authors", "distinguished", "c08" },
{ "books", "published_in", "c09" }
};
auto columns = column_generator::generate<book>(s);
REQUIRE(!columns.empty());
REQUIRE(columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(columns[i]));
}
}
-154
View File
@@ -1,154 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/connection_pool.hpp"
#include "matador/sql/noop_connection.hpp"
#include "utils/auto_reset_event.hpp"
using namespace matador::sql;
using namespace matador::test::utils;
TEST_CASE("Create connection pool", "[connection pool]") {
using pool_t = connection_pool<noop_connection>;
pool_t pool("noop://noop.db", 4);
REQUIRE(pool.size() == 4);
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
auto ptr = pool.acquire();
REQUIRE(ptr.valid());
REQUIRE(ptr.id().value() > 0);
REQUIRE(ptr->is_open());
REQUIRE(pool.idle() == 3);
REQUIRE(pool.inuse() == 1);
pool.release(ptr);
REQUIRE(!ptr.valid());
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
ptr = pool.acquire(3);
REQUIRE(ptr.valid());
REQUIRE(ptr.id() == 3);
REQUIRE(ptr->is_open());
{
auto ptr2 = pool.acquire();
REQUIRE(ptr2.valid());
REQUIRE(ptr2->is_open());
REQUIRE(pool.idle() == 2);
REQUIRE(pool.inuse() == 2);
}
REQUIRE(pool.idle() == 3);
REQUIRE(pool.inuse() == 1);
pool.release(ptr);
REQUIRE(!ptr.valid());
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
}
TEST_CASE("Acquire connection by id", "[connection pool]") {
using pool_t = connection_pool<noop_connection>;
pool_t pool("noop://noop.db", 4);
REQUIRE(pool.size() == 4);
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
auto ptr = pool.acquire();
REQUIRE(ptr.valid());
REQUIRE(ptr.id());
REQUIRE(ptr.id().value() > 0);
REQUIRE(ptr->is_open());
auto same_ptr = pool.acquire(ptr.id().value());
REQUIRE(!same_ptr.valid());
const auto connection_id = ptr.id().value();
pool.release(ptr);
REQUIRE(!ptr.valid());
same_ptr = pool.acquire(connection_id);
REQUIRE(same_ptr.valid());
REQUIRE(same_ptr.id() == connection_id);
}
TEST_CASE("Try acquire connection", "[connection pool][try acquire]") {
using pool_t = connection_pool<noop_connection>;
pool_t pool("noop://noop.db", 1);
REQUIRE(pool.size() == 1);
REQUIRE(pool.idle() == 1);
REQUIRE(pool.inuse() == 0);
auto ptr = pool.try_acquire();
REQUIRE(ptr.valid());
REQUIRE(ptr.id());
REQUIRE(ptr.id().value() > 0);
REQUIRE(ptr->is_open());
REQUIRE(pool.size() == 1);
REQUIRE(pool.idle() == 0);
REQUIRE(pool.inuse() == 1);
auto ptr2 = pool.try_acquire();
REQUIRE(!ptr2.valid());
pool.release(ptr);
REQUIRE(!ptr.valid());
REQUIRE(pool.size() == 1);
REQUIRE(pool.idle() == 1);
REQUIRE(pool.inuse() == 0);
ptr2 = pool.try_acquire();
REQUIRE(ptr2.valid());
REQUIRE(ptr2.id());
REQUIRE(ptr2.id().value() > 0);
REQUIRE(ptr2->is_open());
REQUIRE(pool.size() == 1);
REQUIRE(pool.idle() == 0);
REQUIRE(pool.inuse() == 1);
pool.release(ptr2);
auto_reset_event reset_main_event;
auto_reset_event reset_thread_event;
std::thread t([&reset_main_event, &reset_thread_event, &pool]() {
auto c1 = pool.acquire();
REQUIRE(c1.valid());
REQUIRE(c1.id());
REQUIRE(c1.id().value() > 0);
reset_main_event.set();
reset_thread_event.wait_one();
pool.release(c1);
REQUIRE(!c1.valid());
reset_main_event.set();
});
reset_main_event.wait_one();
ptr2 = pool.try_acquire();
REQUIRE(!ptr2.valid());
reset_thread_event.set();
reset_main_event.wait_one();
ptr2 = pool.try_acquire();
REQUIRE(ptr2.valid());
REQUIRE(ptr2.id());
REQUIRE(ptr2.id().value() > 0);
t.join();
}
-9
View File
@@ -1,9 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/convert.hpp"
using namespace matador::sql;
TEST_CASE("Test convert function", "[convert]") {
}
-258
View File
@@ -1,258 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include <matador/sql/connection.hpp>
#include <matador/sql/entity_query_builder.hpp>
#include "models/airplane.hpp"
#include "models/author.hpp"
#include "models/book.hpp"
#include "models/flight.hpp"
#include "models/recipe.hpp"
#include "models/order.hpp"
#include "models/student.hpp"
using namespace matador::sql;
TEST_CASE("Create sql query data for entity with eager has one", "[query][entity][builder]") {
using namespace matador::test;
connection db("noop://noop.db");
schema scm("noop");
scm.attach<airplane>("airplanes");
scm.attach<flight>("flights");
entity_query_builder eqb(scm);
auto data = eqb.build<flight>(17);
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "flights");
REQUIRE(data->joins.size() == 1);
const std::vector<column> expected_columns {
{ "flights", "id", "c01" },
{ "airplanes", "id", "c02" },
{ "airplanes", "brand", "c03" },
{ "airplanes", "model", "c04" },
{ "flights", "pilot_name", "c05" },
};
REQUIRE(data->columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(data->columns[i]));
}
std::vector<std::pair<std::string, std::string>> expected_join_data {
{ "airplanes", R"("flights"."airplane_id" = "airplanes"."id")"}
};
query_context qc;
size_t index{0};
for (const auto &jd : data->joins) {
REQUIRE(jd.join_table.name == expected_join_data[index].first);
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
++index;
}
REQUIRE(data->where_clause);
auto cond = data->where_clause->evaluate(db.dialect(), qc);
REQUIRE(cond == R"("flights"."id" = 17)");
}
TEST_CASE("Create sql query data for entity with eager belongs to", "[query][entity][builder]") {
using namespace matador::test;
connection db("noop://noop.db");
schema scm("noop");
scm.attach<author>("authors");
scm.attach<book>("books");
entity_query_builder eqb(scm);
auto data = eqb.build<book>(17);
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "books");
REQUIRE(data->joins.size() == 1);
const std::vector<column> expected_columns {
{ "books", "id", "c01" },
{ "books", "title", "c02" },
{ "authors", "id", "c03" },
{ "authors", "first_name", "c04" },
{ "authors", "last_name", "c05" },
{ "authors", "date_of_birth", "c06" },
{ "authors", "year_of_birth", "c07" },
{ "authors", "distinguished", "c08" },
{ "books", "published_in", "c09" }
};
REQUIRE(data->columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(data->columns[i]));
}
std::vector<std::pair<std::string, std::string>> expected_join_data {
{ "authors", R"("books"."author_id" = "authors"."id")"}
};
query_context qc;
size_t index{0};
for (const auto &jd : data->joins) {
REQUIRE(jd.join_table.name == expected_join_data[index].first);
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
++index;
}
REQUIRE(data->where_clause);
auto cond = data->where_clause->evaluate(db.dialect(), qc);
REQUIRE(cond == R"("books"."id" = 17)");
auto q = db.query(scm)
.select(data->columns)
.from(data->root_table_name);
for (auto &jd : data->joins) {
q.join_left(jd.join_table)
.on(std::move(jd.condition));
}
auto context = q
.where(std::move(data->where_clause))
.build();
}
TEST_CASE("Create sql query data for entity with eager has many belongs to", "[query][entity][builder]") {
using namespace matador::test;
connection db("noop://noop.db");
schema scm("noop");
scm.attach<product>("products");
scm.attach<order_details>("order_details");
scm.attach<order>("orders");
entity_query_builder eqb(scm);
auto data = eqb.build<order>(17);
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "orders");
REQUIRE(data->joins.size() == 1);
const std::vector<column> expected_columns = {
{ "orders", "order_id", "c01" },
{ "orders", "order_date", "c02" },
{ "orders", "required_date", "c03" },
{ "orders", "shipped_date", "c04" },
{ "orders", "ship_via", "c05" },
{ "orders", "freight", "c06" },
{ "orders", "ship_name", "c07" },
{ "orders", "ship_address", "c08" },
{ "orders", "ship_city", "c09" },
{ "orders", "ship_region", "c10" },
{ "orders", "ship_postal_code", "c11" },
{ "orders", "ship_country", "c12" },
{ "order_details", "order_details_id", "c13" },
{ "order_details", "order_id", "c14" },
{ "order_details", "product_id", "c15" }
};
REQUIRE(data->columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(data->columns[i]));
}
std::vector<std::pair<std::string, std::string>> expected_join_data {
{ "order_details", R"("orders"."order_id" = "order_details"."order_id")"}
};
query_context qc;
size_t index{0};
for (const auto &jd : data->joins) {
REQUIRE(jd.join_table.name == expected_join_data[index].first);
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
++index;
}
REQUIRE(data->where_clause);
auto cond = data->where_clause->evaluate(db.dialect(), qc);
REQUIRE(cond == R"("orders"."order_id" = 17)");
}
TEST_CASE("Create sql query data for entity with eager many to many", "[query][entity][builder]") {
using namespace matador::test;
connection db("noop://noop.db");
schema scm("noop");
scm.attach<recipe>("recipes");
scm.attach<ingredient>("ingredients");
scm.attach<recipe_ingredient>("recipe_ingredients");
entity_query_builder eqb(scm);
auto data = eqb.build<ingredient>(17);
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "ingredients");
REQUIRE(data->joins.size() == 2);
const std::vector<column> expected_columns {
{ "ingredients", "id", "c01" },
{ "ingredients", "name", "c02" },
{ "recipes", "id", "c03" },
{ "recipes", "name", "c04" }
};
REQUIRE(data->columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(data->columns[i]));
}
std::vector<std::pair<std::string, std::string>> expected_join_data {
{ "recipe_ingredients", R"("ingredients"."id" = "recipe_ingredients"."ingredient_id")"},
{ "recipes", R"("recipe_ingredients"."recipe_id" = "recipes"."id")"}
};
query_context qc;
size_t index{0};
for (const auto &jd : data->joins) {
REQUIRE(jd.join_table.name == expected_join_data[index].first);
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
++index;
}
REQUIRE(data->where_clause);
auto cond = data->where_clause->evaluate(db.dialect(), qc);
REQUIRE(cond == R"("ingredients"."id" = 17)");
}
TEST_CASE("Create sql query data for entity with eager many to many (inverse part)", "[query][entity][builder]") {
using namespace matador::test;
connection db("noop://noop.db");
schema scm("noop");
scm.attach<student>("students");
scm.attach<course>("courses");
scm.attach<student_course>("student_courses");
entity_query_builder eqb(scm);
auto data = eqb.build<course>(17);
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "courses");
REQUIRE(data->joins.size() == 2);
const std::vector<column> expected_columns {
{ "courses", "id", "c01" },
{ "courses", "title", "c02" },
{ "students", "id", "c03" },
{ "students", "name", "c04" }
};
REQUIRE(data->columns.size() == expected_columns.size());
for (size_t i = 0; i != expected_columns.size(); ++i) {
REQUIRE(expected_columns[i].equals(data->columns[i]));
}
std::vector<std::pair<std::string, std::string>> expected_join_data {
{ "student_courses", R"("courses"."id" = "student_courses"."course_id")"},
{ "students", R"("student_courses"."student_id" = "students"."id")"}
};
query_context qc;
size_t index{0};
for (const auto &jd : data->joins) {
REQUIRE(jd.join_table.name == expected_join_data[index].first);
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
++index;
}
REQUIRE(data->where_clause);
auto cond = data->where_clause->evaluate(db.dialect(), qc);
REQUIRE(cond == R"("courses"."id" = 17)");
}
-250
View File
@@ -1,250 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include <matador/sql/column_definition.hpp>
#include <matador/sql/condition.hpp>
#include <matador/sql/connection.hpp>
#include <matador/sql/dialect_builder.hpp>
#include <matador/sql/query.hpp>
using namespace matador::sql;
using namespace matador::utils;
TEST_CASE("Create table sql statement string", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
auto result = q.create().table({"person"}, {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
}).build();
REQUIRE(result.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##");
REQUIRE(result.table.name == "person");
result = q.create().table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", {255, constraints::UNIQUE}, null_option::NOT_NULL),
make_column<unsigned short>("age"),
make_fk_column<unsigned long>("address", "address", "id")
}).build();
REQUIRE(result.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Drop table sql statement string", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.drop().table("person").build();
REQUIRE(result.sql == R"(DROP TABLE "person")");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Select sql statement string", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.select({"id", "name", "age"}).from("person").build();
REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person")");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Insert sql statement string", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.insert().into("person", {
"id", "name", "age"
}).values({7UL, "george", 65U}).build();
REQUIRE(result.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Update sql statement string", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.update("person").set({
{"id", 7UL},
{"name", "george"},
{"age", 65U}
}).build();
REQUIRE(result.sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Update limit sql statement", "[query][update][limit]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.update("person")
.set({{"id", 7UL}, {"name", "george"}, {"age", 65U}})
.where("name"_col == "george")
.order_by("id"_col).asc()
.limit(2)
.build();
REQUIRE(result.sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Delete sql statement string", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.remove().from("person").build();
REQUIRE(result.sql == R"(DELETE FROM "person")");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Delete limit sql statement", "[query][delete][limit]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.remove()
.from("person")
.where("name"_col == "george")
.order_by("id"_col).asc()
.limit(2)
.build();
REQUIRE(result.sql == R"(DELETE FROM "person" WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Select sql statement string with where clause", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
auto result = q.select({"id", "name", "age"})
.from("person")
.where("id"_col == 8 && "age"_col > 50)
.build();
REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))");
REQUIRE(result.table.name == "person");
result = q.select({"id", "name", "age"})
.from("person")
.where("id"_col == _ && "age"_col > 50)
.build();
REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Insert sql statement with placeholder", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.insert().into("person", {
"id", "name", "age"
}).values({_, _, _}).build();
REQUIRE(result.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (?, ?, ?))");
REQUIRE(result.table.name == "person");
REQUIRE(result.bind_vars.size() == 3);
}
TEST_CASE("Select sql statement string with order by", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.select({"id", "name", "age"})
.from("person")
.order_by("name").asc()
.build();
REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Select sql statement string with group by", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.select({"id", "name", "age"})
.from("person")
.group_by("age")
.build();
REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Select sql statement string with offset and limit", "[query]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
const auto result = q.select({"id", "name", "age"})
.from("person")
.order_by("id"_col).asc()
.limit(20)
.offset(10)
.build();
REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "id" ASC LIMIT 20 OFFSET 10)");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Create, insert and select a blob column", "[query][blob]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
auto result = q.create().table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<blob>("data")
}).build();
REQUIRE(result.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "data" BLOB NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##");
REQUIRE(result.table.name == "person");
result = q.insert().into("person", {
"id", "name", "data"
}).values({7UL, "george", blob{1, 'A', 3, 4}}).build();
REQUIRE(result.sql == R"(INSERT INTO "person" ("id", "name", "data") VALUES (7, 'george', X'01410304'))");
REQUIRE(result.table.name == "person");
result = q.select({"id", "name", "data"}).from("person").build();
REQUIRE(result.sql == R"(SELECT "id", "name", "data" FROM "person")");
REQUIRE(result.table.name == "person");
}
TEST_CASE("Select statement with join_left", "[query][join_left]")
{
connection noop("noop://noop.db");
schema scm("noop");
query q(noop, scm);
auto result = q.select({"f.id", "ap.brand", "f.pilot_name"})
.from({"flight", "f"})
.join_left({"airplane", "ap"})
.on("f.airplane_id"_col == "ap.id"_col)
.build();
REQUIRE(result.sql == R"(SELECT "f"."id", "ap"."brand", "f"."pilot_name" FROM "flight" AS "f" INNER JOIN "airplane" AS "ap" ON "f"."airplane_id" = "ap"."id")");
REQUIRE(result.table.name == "flight");
}
-95
View File
@@ -1,95 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/result.hpp"
namespace matador::test {
enum class math_error : int32_t {
DIVISION_BY_ZERO = 1
};
utils::result<float, math_error>divide(int x, int y) {
if (y == 0) {
return utils::error(math_error::DIVISION_BY_ZERO);
}
return utils::ok(float(x) / y);
}
utils::result<float, math_error>multiply(int x, int y) {
return utils::ok(float(x) * y);
}
utils::result<float, math_error>plus(int x, int y) {
return utils::ok(float(x) + y);
}
utils::result<float, std::string>error_to_string(math_error err) {
switch (err) {
case math_error::DIVISION_BY_ZERO:
return utils::error(std::string("division by zero error"));
default:
return utils::error(std::string("unknown error"));
}
}
}
using namespace matador;
TEST_CASE("Result tests", "[result]") {
auto res = test::divide(4, 2);
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 2.0));
REQUIRE_THROWS(res.err());
res = test::divide(4, 0);
REQUIRE(!res);
REQUIRE(!res.is_ok());
REQUIRE(res.is_error());
REQUIRE((res.err() == test::math_error::DIVISION_BY_ZERO));
res = test::divide(4, 2)
.and_then([](const auto &val) { return test::multiply(val, 5); })
.and_then([](const auto &val) { return test::plus(val, 10); });
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 20.0));
res = test::divide(4, 0)
.and_then([](const auto &val) {
return test::multiply(val, 5);
});
REQUIRE(!res);
REQUIRE(!res.is_ok());
REQUIRE(res.is_error());
REQUIRE((res.err() == test::math_error::DIVISION_BY_ZERO));
auto res2 = test::divide(4, 0)
.or_else([](const auto &err) {
return test::error_to_string(err);
});
REQUIRE(!res2);
REQUIRE(!res2.is_ok());
REQUIRE(res2.is_error());
REQUIRE((res2.err() == "division by zero error"));
res = test::divide(4, 2)
.and_then([](const auto &val) { return test::multiply(val, 5); })
.transform([](const auto &val) { return val + 10; });
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 20.0));
}
-49
View File
@@ -1,49 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include <matador/sql/table_definition.hpp>
#include <list>
using namespace matador::sql;
TEST_CASE("Create record", "[record]") {
table_definition def({
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<std::string>("color", 255)
});
REQUIRE(def.size() == 3);
std::list<std::string> expected_columns = {"id", "name", "color"};
for(const auto &col : expected_columns) {
REQUIRE(def.find(col) != def.end());
}
for(const auto& col : def) {
expected_columns.remove(col.name());
}
REQUIRE(expected_columns.empty());
}
TEST_CASE("Append to record", "[record]") {
table_definition rec;
rec.append(make_pk_column<unsigned long>("id"));
rec.append<std::string>("name", 255);
rec.append<std::string>("color", 63);
REQUIRE(rec.size() == 3);
std::list<std::string> expected_columns = {"id", "name", "color"};
for(const auto &col : expected_columns) {
REQUIRE(rec.find(col) != rec.end());
}
for(const auto& col : rec) {
expected_columns.remove(col.name());
}
REQUIRE(expected_columns.empty());
}
-29
View File
@@ -1,29 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/value_extractor.hpp"
#include "models/product.hpp"
using namespace matador::sql;
TEST_CASE("Extract values object", "[value extractor]") {
matador::test::product p;
p.discontinued = false;
p.reorder_level = 1;
p.units_in_order = 2;
p.units_in_stock = 100;
p.unit_price = 49;
p.quantity_per_unit = "pcs";
p.category = make_entity<matador::test::category>();
p.category->id = 7;
p.supplier = make_entity<matador::test::supplier>();
p.supplier->id = 13;
p.product_name = "candle";
const std::vector<any_type> expected_values {
std::string{"candle"}, 13UL, 7UL, std::string{"pcs"}, 49U, 100U, 2U, 1U, false
};
auto values = value_extractor::extract(p);
REQUIRE(values == expected_values);
}
-70
View File
@@ -1,70 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/value.hpp"
#include "matador/utils/types.hpp"
using namespace matador::sql;
TEST_CASE("Test value class", "[value]") {
value v;
REQUIRE(v.is_unknown());
REQUIRE(v.type() == data_type_t::type_unknown);
REQUIRE(v.size() == 0);
v = 7;
REQUIRE(v.is_integer());
REQUIRE(v.type() == data_type_t::type_int);
REQUIRE(v.size() == 0);
REQUIRE(v.as<int>() == 7);
REQUIRE(v.as<long>() == 7);
v = "test";
REQUIRE(v.is_varchar());
REQUIRE(v.type() == data_type_t::type_char_pointer);
REQUIRE(v.size() == 4);
v = std::string{"hello"};
REQUIRE(v.is_varchar());
REQUIRE(v.type() == data_type_t::type_varchar);
REQUIRE(v.size() == 5);
v = 4.5;
REQUIRE(v.is_floating_point());
REQUIRE(v.type() == data_type_t::type_double);
REQUIRE(v.size() == 0);
v = 6.7f;
REQUIRE(v.is_floating_point());
REQUIRE(v.type() == data_type_t::type_float);
REQUIRE(v.size() == 0);
v = std::string();
REQUIRE(v.is_string());
REQUIRE(v.type() == data_type_t::type_text);
REQUIRE(v.size() == 0);
v = true;
REQUIRE(v.is_bool());
REQUIRE(v.type() == data_type_t::type_bool);
REQUIRE(v.size() == 0);
v = nullptr;
REQUIRE(v.is_null());
REQUIRE(v.type() == data_type_t::type_null);
REQUIRE(v.size() == 0);
v = matador::utils::blob{ 1, 2, 3, 4 };
REQUIRE(v.is_blob());
REQUIRE(v.type() == data_type_t::type_blob);
REQUIRE(v.size() == 4);
}
+23
View File
@@ -0,0 +1,23 @@
#include "ColorEnumTraits.hpp"
#include "matador/utils/attribute_writer.hpp"
#include "matador/utils/attribute_reader.hpp"
namespace matador::utils {
void data_type_traits<test::Color, void>::read_value(attribute_reader &reader, const char *id, size_t index,
test::Color &value)
{
std::string enum_string;
reader.read_value(id, index, enum_string, 64);
if (const auto enum_opt = color_enum.to_enum(enum_string)) {
value = enum_opt.value();
}
}
void data_type_traits<test::Color, void>::bind_value(attribute_writer &binder, const size_t index, const test::Color &value)
{
binder.write_value(index, color_enum.to_string(value));
}
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef MATADOR_COLOR_ENUM_TRAITS_HPP
#define MATADOR_COLOR_ENUM_TRAITS_HPP
#include "matador/utils/default_type_traits.hpp"
#include "matador/utils/enum_mapper.hpp"
#include "models/location.hpp"
static const matador::utils::enum_mapper<matador::test::Color> color_enum({
{matador::test::Color::Green, "green"},
{matador::test::Color::Red, "red"},
{matador::test::Color::Blue, "blue"},
{matador::test::Color::Yellow, "yellow"},
{matador::test::Color::Black, "black"},
{matador::test::Color::White, "white"},
{matador::test::Color::Brown, "brown"}
});
template<>
struct matador::utils::data_type_traits<matador::test::Color, void>
{
static basic_type type(const std::size_t size) { return data_type_traits<std::string>::type(size); }
static void read_value(attribute_reader &reader, const char *id, size_t index, test::Color &value);
static void bind_value(attribute_writer &binder, size_t index, const test::Color &value);
};
#endif //MATADOR_COLOR_ENUM_TRAITS_HPP
+20
View File
@@ -0,0 +1,20 @@
#include "catch2/catch_test_macros.hpp"
#include "matador/sql/connection.hpp"
#include "connection.hpp"
using namespace matador::sql;
TEST_CASE("Create connection test", "[connection]") {
const connection c(matador::test::connection::dns);
REQUIRE(!c.is_open());
auto result = c.open();
REQUIRE(result.is_ok());
REQUIRE(c.is_open());
result = c.close();
REQUIRE(result.is_ok());
REQUIRE(!c.is_open());
}
+551
View File
@@ -0,0 +1,551 @@
#include "catch2/catch_test_macros.hpp"
#include "catch2/matchers/catch_matchers_string.hpp"
#include "matador/sql/column_definition.hpp"
#include "matador/sql/connection.hpp"
#include "matador/query/condition.hpp"
#include "matador/query/query.hpp"
#include "matador/sql/schema.hpp"
#include "matador/utils/basic_types.hpp"
#include "matador/utils/string.hpp"
#include "models/types.hpp"
#include "QueryFixture.hpp"
using namespace matador::test;
using namespace matador::sql;
using namespace matador::query;
TEST_CASE_METHOD( QueryFixture, "Insert and select basic datatypes", "[query][datatypes]" ) {
schema.attach<types>("types");
auto res = query::create()
.table<types>("types", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("types");
float float_value = 2.44557f;
double double_value = 11111.23433345;
char cval = 'c';
short sval = (std::numeric_limits<short>::min)();
int ival = (std::numeric_limits<int>::min)();
long lval = (std::numeric_limits<long>::min)();
long long 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)();
unsigned long ulval = (std::numeric_limits<unsigned long>::max)();
unsigned long long ullval = (std::numeric_limits<unsigned long long>::max)();
if (db.type() == "sqlite" || db.type() == "postgres") {
ulval = (std::numeric_limits<long>::max)();
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, lval, llval,
ucval, usval, uival, ulval, ullval,
float_value, double_value,
bval,
"Armer schwarzer Kater",
strval, varcharval,
date_val, time_val,
blob_val
};
res = query::insert()
.into("types", matador::sql::column_generator::generate<types>(schema, true))
.values(t)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto result = query::select<types>(schema)
.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)->long_ == lval);
REQUIRE((*result)->long64_ == llval);
REQUIRE((*result)->unsigned_char_ == ucval);
REQUIRE((*result)->unsigned_short_ == usval);
REQUIRE((*result)->unsigned_int_ == uival);
REQUIRE((*result)->unsigned_long_ == ulval);
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::data_type> types = {matador::data_type::type_varchar, matador::data_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::START_QUOTE);
const auto end_quote = db.dialect().token_at(matador::sql::dialect_token::END_QUOTE);
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::data_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;
schema.attach<types>("types");
const auto res = query::create()
.table<types>("types", schema)
.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", "val_long_long", "val_unsigned_char",
"val_unsigned_short", "val_unsigned_int", "val_unsigned_long", "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 column_definition&)>> type_check = {
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_floating_point(); },
[](const column_definition &cf) { return cf.is_floating_point(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_integer(); },
[](const column_definition &cf) { return cf.is_bool(); },
[](const column_definition &cf) { return cf.is_varchar(); },
[](const column_definition &cf) { return cf.is_string(); },
[](const column_definition &cf) { return cf.is_varchar(); },
[](const column_definition &cf) { return cf.is_date(); },
[](const column_definition &cf) { return cf.is_time(); },
[](const column_definition &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);
}
unsigned long id{};
std::string name;
};
}
TEST_CASE_METHOD(QueryFixture, "Test primary key", "[query][primary key]") {
using namespace matador::test::temporary;
using namespace matador::sql;
schema.attach<pk>("pk");
auto res = query::create()
.table<pk>("pk", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("pk");
pk pk1{ 7, "george" };
res = query::insert()
.into("pk", column_generator::generate<pk>(schema))
.values(pk1)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto row = query::select<pk>(schema)
.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;
schema.attach<pk>("pk");
auto res = query::create()
.table<pk>("pk", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("pk");
pk pk1{ 7, "george" };
auto stmt = query::insert()
.into("pk", column_generator::generate<pk>(schema))
.values<pk>()
.prepare(db);
res = stmt.bind(pk1)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
stmt = query::select<pk>(schema)
.from("pk")
.prepare(db);
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;
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++]);
}
}
+52
View File
@@ -0,0 +1,52 @@
#include "QueryFixture.hpp"
#include "matador/sql/query.hpp"
#include "catch2/catch_test_macros.hpp"
namespace matador::test {
QueryFixture::QueryFixture()
: db(connection::dns)
, schema(db.dialect().default_schema_name())
{
db.open();
}
QueryFixture::~QueryFixture() {
while (!tables_to_drop.empty()) {
drop_table_if_exists(tables_to_drop.top());
tables_to_drop.pop();
}
}
void QueryFixture::check_table_exists(const std::string &table_name) const
{
auto result = db.exists(table_name);
REQUIRE(result.is_ok());
REQUIRE(*result);
}
void QueryFixture::check_table_not_exists(const std::string &table_name) const
{
auto result = db.exists(table_name);
REQUIRE(result.is_ok());
REQUIRE(!*result);
}
void QueryFixture::drop_table_if_exists(const std::string &table_name) const {
const auto result = db.exists(table_name).and_then([&table_name, this](bool exists) {
if (exists) {
if (sql::query::drop()
.table(table_name)
.execute(db).is_ok()) {
this->check_table_not_exists(table_name);
} else {
FAIL("Failed to drop table");
}
}
return utils::ok(true);
});
}
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef MATADOR_QUERY_FIXTURE_HPP
#define MATADOR_QUERY_FIXTURE_HPP
#include "matador/sql/connection.hpp"
#include "matador/sql/schema.hpp"
#include "connection.hpp"
#include <stack>
namespace matador::test {
class QueryFixture {
public:
QueryFixture();
~QueryFixture();
void check_table_exists(const std::string &table_name) const;
void check_table_not_exists(const std::string &table_name) const;
protected:
sql::connection db;
sql::schema schema;
std::stack <std::string> tables_to_drop;
private:
void drop_table_if_exists(const std::string &table_name) const;
};
}
#endif //MATADOR_QUERY_FIXTURE_HPP
+772
View File
@@ -0,0 +1,772 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/column.hpp"
#include "matador/sql/condition.hpp"
#include "matador/sql/connection.hpp"
#include "matador/sql/query.hpp"
#include "matador/utils/types.hpp"
#include "matador/utils/string.hpp"
#include "QueryFixture.hpp"
#include <list>
#include <algorithm>
using namespace matador::sql;
using namespace matador::test;
TEST_CASE_METHOD(QueryFixture, "Test all data types for record", "[query][record][data types]") {
check_table_not_exists("types");
auto res = query::create()
.table("types", {
make_pk_column<unsigned long>("id"),
make_column<char>("val_char"),
make_column<short>("val_short"),
make_column<int>("val_int"),
make_column<long>("val_long"),
make_column<long long>("val_long_long"),
make_column<unsigned char>("val_uchar"),
make_column<unsigned short>("val_ushort"),
make_column<unsigned int>("val_uint"),
make_column<unsigned long>("val_ulong"),
make_column<unsigned long long>("val_ulong_long"),
make_column<bool>("val_bool"),
make_column<float>("val_float"),
make_column<double>("val_double"),
make_column<std::string>("val_string"),
make_column<std::string>("val_varchar", 63),
make_column<matador::date>("val_date"),
make_column<matador::time>("val_time"),
make_column<matador::utils::blob>("val_blob"),
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_exists("types");
tables_to_drop.emplace("types");
auto cols = std::vector<std::string>{"id",
"val_char", "val_short", "val_int", "val_long", "val_long_long",
"val_uchar", "val_ushort", "val_uint", "val_ulong", "val_ulong_long",
"val_bool",
"val_float", "val_double",
"val_string", "val_varchar",
"val_date", "val_time", "val_blob"};
const auto fields = db.describe("types");
REQUIRE(fields.is_ok());
for (const auto &fld : *fields) {
REQUIRE(std::find(cols.begin(), cols.end(), fld.name()) != cols.end());
}
unsigned long id{1};
char c{-11};
short s{-256};
int i{-123456};
long l{-9876543};
long long ll{-987654321};
unsigned char uc{13};
unsigned short us{1024};
unsigned int ui{654321};
unsigned long ul{12345678};
unsigned long long ull{1234567890};
bool b{true};
float f{3.1415f};
double d{2.71828};
std::string str{"long text"};
std::string varchar{"good day"};
auto md{matador::date()};
auto mt{matador::time::now()};
matador::utils::blob bin{0x01,0x02,0x03,0x04};
res = query::insert()
.into("types", cols)
.values({id, c, s, i, l, ll, uc, us, ui, ul, ull, b, f, d, str, varchar, md, mt, bin})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
auto row = query::select(cols)
.from("types")
.fetch_one(db);
REQUIRE(row.is_ok());
REQUIRE(row.value().has_value());
REQUIRE(id == (*row)->at<unsigned long>("id"));
REQUIRE(c == (*row)->at<char>("val_char"));
REQUIRE(s == (*row)->at<short>("val_short"));
REQUIRE(i == (*row)->at<int>("val_int"));
REQUIRE(l == (*row)->at<long>("val_long"));
REQUIRE(ll == (*row)->at<long long>("val_long_long"));
REQUIRE(uc == (*row)->at<unsigned char>("val_uchar"));
REQUIRE(us == (*row)->at<unsigned short>("val_ushort"));
REQUIRE(ui == (*row)->at<unsigned int>("val_uint"));
REQUIRE(ul == (*row)->at<unsigned long>("val_ulong"));
REQUIRE(ull == (*row)->at<unsigned long long>("val_ulong_long"));
REQUIRE((*row)->at<bool>("val_bool"));
REQUIRE(f == (*row)->at<float>("val_float"));
REQUIRE(d == (*row)->at<double>("val_double"));
REQUIRE(str == (*row)->at<std::string>("val_string"));
REQUIRE(varchar == (*row)->at<std::string>("val_varchar"));
REQUIRE(md == (*row)->at<matador::date>("val_date"));
REQUIRE(mt == (*row)->at<matador::time>("val_time"));
REQUIRE(bin == (*row)->at<matador::utils::blob>("val_blob"));
}
TEST_CASE_METHOD(QueryFixture, "Create and drop table statement", "[query][record]")
{
check_table_not_exists("person");
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_exists("person");
tables_to_drop.emplace("person");
res = query::drop()
.table("person")
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_not_exists("person");
}
TEST_CASE_METHOD(QueryFixture, "Create and drop table statement with foreign key", "[query][record]")
{
auto res = query::create()
.table("airplane", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("brand", 255),
make_column<std::string>("model", 255),
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_exists("airplane");
tables_to_drop.emplace("airplane");
res = query::create()
.table("flight", {
make_pk_column<unsigned long>("id"),
make_fk_column<unsigned long>("airplane_id", "airplane", "id"),
make_column<std::string>("pilot_name", 255),
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_exists("flight");
tables_to_drop.emplace("flight");
res = query::drop()
.table("flight")
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_not_exists("flight");
res = query::drop()
.table("airplane")
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
check_table_not_exists("airplane");
}
TEST_CASE_METHOD(QueryFixture, "Execute insert record statement", "[query][record]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
tables_to_drop.emplace("person");
res = query::insert()
.into("person", {"id", "name", "age"})
.values({7, "george", 45})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
auto result = query::select({"id", "name", "age"})
.from("person")
.fetch_all(db);
REQUIRE(result.is_ok());
for (const auto &i: *result) {
REQUIRE(i.size() == 3);
REQUIRE(i.at(0).name() == "id");
REQUIRE(i.at(0).is_integer());
REQUIRE(i.at(0).template as<long long>() == 7);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).is_varchar());
REQUIRE(i.at(1).template as<std::string>() == "george");
REQUIRE(i.at(2).name() == "age");
REQUIRE(i.at(2).is_integer());
REQUIRE(i.at(2).template as<int>() == 45);
}
}
TEST_CASE_METHOD(QueryFixture, "Execute insert record statement with foreign key", "[query][record]")
{
auto res = query::create()
.table("airplane", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("brand", 255),
make_column<std::string>("model", 255),
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
tables_to_drop.emplace("airplane");
res = query::create()
.table("flight", {
make_pk_column<unsigned long>("id"),
make_fk_column<unsigned long>("airplane_id", "airplane", "id"),
make_column<std::string>("pilot_name", 255),
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
tables_to_drop.emplace("flight");
std::vector<std::vector<matador::utils::any_type>> values_list{
{1, "Airbus", "A380"},
{2, "Boeing", "707"},
{3, "Boeing", "747"}
};
for(auto &&values : values_list) {
res = query::insert()
.into("airplane", {"id", "brand", "model"})
.values(std::move(values))
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
}
auto count = query::select({count_all()})
.from("airplane")
.fetch_value<int>(db);
REQUIRE(count.is_ok());
REQUIRE(count->has_value());
REQUIRE(*count == 3);
res = query::insert()
.into("flight", {"id", "airplane_id", "pilot_name"})
.values({4, 1, "George"})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
}
TEST_CASE_METHOD(QueryFixture, "Execute update record statement", "[query][record]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
tables_to_drop.emplace("person");
res = query::insert()
.into("person", {"id", "name", "age"})
.values({7, "george", 45})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
res = query::update("person")
.set({{"id", 7},
{"name", "jane"},
{"age", 35}})
.where("id"_col == 7)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
auto result = query::select({"id", "name", "age"})
.from("person")
.fetch_all(db);
REQUIRE(result.is_ok());
for (const auto &i: *result) {
REQUIRE(i.size() == 3);
REQUIRE(i.at(0).name() == "id");
REQUIRE(i.at(0).is_integer());
REQUIRE(i.at(0).as<long long>() == 7);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).is_varchar());
REQUIRE(i.at(1).as<std::string>() == "jane");
REQUIRE(i.at(2).name() == "age");
REQUIRE(i.at(2).is_integer());
REQUIRE(i.at(2).as<int>() == 35);
}
}
TEST_CASE_METHOD(QueryFixture, "Execute select statement", "[query][record]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
tables_to_drop.emplace("person");
std::vector<std::vector<matador::utils::any_type>> values_list{
{1, "george", 45},
{2, "jane", 32},
{3, "michael", 67},
{4, "bob", 13}
};
for(auto &&values : values_list) {
res = query::insert()
.into("person", {"id", "name", "age"})
.values(std::move(values))
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
}
auto result = query::select({"id", "name", "age"})
.from("person")
.fetch_all(db);
REQUIRE(result.is_ok());
std::list<std::string> expected_names{"george", "jane", "michael", "bob"};
for (const auto &p: *result) {
REQUIRE(p.at(1).str() == expected_names.front());
expected_names.pop_front();
}
REQUIRE(expected_names.empty());
auto rec = query::select({"id", "name", "age"})
.from("person")
.fetch_one(db);
REQUIRE(rec.is_ok());
REQUIRE(rec->has_value());
REQUIRE((*rec)->at(1).str() == "george");
auto name = query::select({"name"})
.from("person")
.fetch_value<std::string>(db);
REQUIRE(name.is_ok());
REQUIRE(*name == "george");
}
TEST_CASE_METHOD(QueryFixture, "Execute select statement with order by", "[query][record]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 0);
tables_to_drop.emplace("person");
std::vector<std::vector<matador::utils::any_type>> values_list{
{1, "george", 45},
{2, "jane", 32},
{3, "michael", 67},
{4, "bob", 13}
};
for(auto &&values : values_list) {
res = query::insert()
.into("person", {"id", "name", "age"})
.values(std::move(values))
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
}
auto result = query::select({"id", "name", "age"})
.from("person")
.order_by("name").asc()
.fetch_all(db);
REQUIRE(result.is_ok());
std::list<std::string> expected_names{"bob", "george", "jane", "michael"};
for (const auto &p: *result) {
REQUIRE(p.at(1).str() == expected_names.front());
expected_names.pop_front();
}
REQUIRE(expected_names.empty());
}
TEST_CASE_METHOD(QueryFixture, "Execute select statement with group by and order by", "[query][record]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
std::vector<std::vector<matador::utils::any_type>> values_list{
{1, "george", 45},
{2, "jane", 45},
{3, "joe", 45},
{4, "michael", 13},
{5, "bob", 13},
{6, "charlie", 67}
};
for(auto &&values : values_list) {
res = query::insert()
.into("person", {"id", "name", "age"})
.values(std::move(values))
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(res.value() == 1);
}
auto result = query::select({count("age").as("age_count"), "age"})
.from("person")
.group_by("age")
.order_by("age_count").desc()
.fetch_all(db);
REQUIRE(result.is_ok());
std::list<std::pair<int, int>> expected_values{{3, 45},
{2, 13},
{1, 67}};
for (const auto &r: *result) {
const auto age_count_val = r.at<int>(0);
const auto age_val = r.at<int>(1);
REQUIRE(age_count_val == expected_values.front().first);
REQUIRE(age_val == expected_values.front().second);
expected_values.pop_front();
}
}
TEST_CASE_METHOD(QueryFixture, "Execute delete statement", "[query][record]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
}).execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
res = query::insert()
.into("person", {"id", "name", "age"})
.values({1, "george", 45})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
res = query::insert()
.into("person", {"id", "name", "age"})
.values({2, "jane", 45})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto count = query::select({count_all()})
.from("person")
.fetch_value<int>(db);
REQUIRE(count.is_ok());
REQUIRE(*count == 2);
res = query::remove()
.from("person")
.where("id"_col == 1)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
count = query::select({count_all()})
.from("person")
.fetch_value<int>(db);
REQUIRE(count.is_ok());
REQUIRE(*count == 1);
}
TEST_CASE_METHOD(QueryFixture, "Test quoted identifier record", "[query][record]") {
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> columns = { "from", "to"};
std::vector<matador::data_type> types = {
matador::data_type::type_varchar,
matador::data_type::type_varchar
};
auto fields = db.describe("quotes");
REQUIRE(fields.is_ok());
for (const auto &field : *fields) {
REQUIRE(field.name() == columns[field.index()]);
REQUIRE(field.type() == types[field.index()]);
}
res = query::insert()
.into("quotes", {"from", "to"})
.values({"Berlin", "London"})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto result = query::select({"from", "to"})
.from("quotes")
.fetch_one(db);
REQUIRE(result.is_ok());
REQUIRE(result->has_value());
REQUIRE("Berlin" == (*result)->at("from").str());
REQUIRE("London" == (*result)->at("to").str());
res = query::update("quotes")
.set({{"from", "Hamburg"}, {"to", "New York"}})
.where("from"_col == "Berlin")
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
result = query::select({"from", "to"})
.from("quotes")
.fetch_one(db);
REQUIRE(result.is_ok());
REQUIRE("Hamburg" == (*result)->at("from").str());
REQUIRE("New York" == (*result)->at("to").str());
}
TEST_CASE_METHOD(QueryFixture, "Test create record", "[query][record][create]") {
check_table_not_exists("person");
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
const std::vector<std::string> cols = {"id", "name", "age"};
const auto fields = db.describe("person");
REQUIRE(fields.is_ok());
for (const auto &fld : *fields) {
REQUIRE(std::find(cols.begin(), cols.end(), fld.name()) != cols.end());
}
}
TEST_CASE_METHOD(QueryFixture, "Test insert record", "[query][record][insert]") {
check_table_not_exists("person");
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
res = query::insert()
.into("person", {"id", "name", "age"})
.values({1, "hans", 45})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto row = query::select({"id", "name", "age"})
.from("person")
.fetch_one(db);
REQUIRE(row.is_ok());
REQUIRE(row->has_value());
REQUIRE((*row)->at("id").as<unsigned long>() == 1);
REQUIRE((*row)->at("name").as<std::string>() == "hans");
REQUIRE((*row)->at("age").as<unsigned short>() == 45);
}
TEST_CASE_METHOD(QueryFixture, "Test update record", "[query][record][update]") {
check_table_not_exists("person");
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
res = query::insert()
.into("person", {"id", "name", "age"})
.values({1, "hans", 45})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto row = query::select({"id", "name", "age"})
.from("person")
.fetch_one(db);
REQUIRE(row.is_ok());
REQUIRE(row->has_value());
REQUIRE((*row)->at("id").as<unsigned long>() == 1);
REQUIRE((*row)->at("name").as<std::string>() == "hans");
REQUIRE((*row)->at("age").as<unsigned short>() == 45);
res = query::update("person")
.set({{"name", "jane"}, {"age", 47}})
.where("name"_col == "hans")
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
row = query::select({"id", "name", "age"})
.from("person")
.fetch_one(db);
REQUIRE(row.is_ok());
REQUIRE(row->has_value());
REQUIRE((*row)->at("id").as<unsigned long>() == 1);
REQUIRE((*row)->at("name").as<std::string>() == "jane");
REQUIRE((*row)->at("age").as<unsigned short>() == 47);
}
TEST_CASE_METHOD(QueryFixture, "Test prepared record statement", "[query][record][prepared]") {
check_table_not_exists("person");
auto stmt = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
})
.prepare(db);
tables_to_drop.emplace("person");
auto res = stmt.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
check_table_exists("person");
const std::vector<std::string> cols = {"id", "name", "age"};
const auto fields = db.describe("person");
REQUIRE(fields.is_ok());
for (const auto &fld : *fields) {
REQUIRE(std::find(cols.begin(), cols.end(), fld.name()) != cols.end());
}
}
TEST_CASE_METHOD(QueryFixture, "Test scalar result", "[query][record][scalar][result]") {
check_table_not_exists("person");
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
check_table_exists("person");
tables_to_drop.emplace("person");
std::vector<unsigned long> ids({ 1,2,3,4 });
for(auto id : ids) {
res = query::insert()
.into("person", {"id"})
.values({id})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto stmt = query::select({"id"})
.from("person")
.order_by("id"_col).asc()
.prepare(db);
auto rows = stmt.fetch();
REQUIRE(rows.is_ok());
size_t index{0};
for (const auto &row : *rows) {
REQUIRE(row.at("id").as<unsigned long>() == ids[index]);
++index;
}
REQUIRE(index == 4);
stmt.reset();
rows = stmt.fetch();
REQUIRE(rows.is_ok());
index = 0;
for (const auto &row : *rows) {
REQUIRE(row.at("id").as<unsigned long>() == ids[index]);
++index;
}
REQUIRE(index == 4);
stmt.reset();
auto row = stmt.fetch_one();
REQUIRE(row.is_ok());
REQUIRE(row->has_value());
REQUIRE(row.value()->at("id").as<unsigned long>() == ids[0]);
}
+293
View File
@@ -0,0 +1,293 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/column.hpp"
#include "matador/sql/condition.hpp"
#include "matador/sql/connection.hpp"
#include "matador/sql/query.hpp"
#include "models/person.hpp"
#include "QueryFixture.hpp"
#include <algorithm>
#include <vector>
using namespace matador::sql;
using namespace matador::test;
TEST_CASE_METHOD(QueryFixture, "Test create statement", "[query][statement][create]") {
schema.attach<matador::test::person>("person");
auto stmt = query::create()
.table<matador::test::person>("person", schema)
.prepare(db);
auto res = stmt.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
const std::vector<std::string> cols = {"id", "name", "age", "image"};
const auto fields = db.describe("person");
REQUIRE(fields.is_ok());
for (const auto &fld : *fields) {
REQUIRE(std::find(cols.begin(), cols.end(), fld.name()) != cols.end());
}
}
TEST_CASE_METHOD(QueryFixture, "Test insert statement", "[query][statement][insert]") {
using namespace matador::test;
schema.attach<person>("person");
auto stmt = query::create()
.table<person>("person", schema)
.prepare(db);
auto res = stmt.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
person george{1, "george", 45, {1,2,3,4}};
stmt = query::insert()
.into<person>("person", schema)
.values<person>()
.prepare(db);
res = stmt.bind(george)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto row = query::select<person>(schema)
.from("person")
.fetch_one<person>(db);
REQUIRE(row.is_ok());
REQUIRE(*row != nullptr);
REQUIRE((*row)->id == 1);
REQUIRE((*row)->name == "george");
REQUIRE((*row)->age == 45);
REQUIRE((*row)->image == matador::utils::blob{1,2,3,4});
}
TEST_CASE_METHOD(QueryFixture, "Test update statement", "[query][statement][update]") {
using namespace matador::test;
using namespace matador::utils;
schema.attach<matador::test::person>("person");
auto stmt = query::create()
.table<matador::test::person>("person", schema)
.prepare(db);
auto res = stmt.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
person george{1, "george", 45, {1,2,3,4}};
stmt = query::insert()
.into<person>("person", schema)
.values<person>()
.prepare(db);
res = stmt.bind(george)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto row = query::select<person>(schema)
.from("person")
.fetch_one<person>(db);
REQUIRE(row.is_ok());
REQUIRE(*row != nullptr);
REQUIRE((*row)->id == 1);
REQUIRE((*row)->name == "george");
REQUIRE((*row)->age == 45);
REQUIRE((*row)->image == blob{1,2,3,4});
george.age = 36;
george.image = {5,6,7,8};
stmt = query::update("person")
.set<person>()
.where("id"_col == _)
.prepare(db);
res = stmt.bind(george)
.bind(4, george.id)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
row = query::select<person>(schema)
.from("person")
.fetch_one<person>(db);
REQUIRE(row.is_ok());
REQUIRE(*row != nullptr);
REQUIRE((*row)->id == 1);
REQUIRE((*row)->name == "george");
REQUIRE((*row)->age == 36);
REQUIRE((*row)->image == blob{5,6,7,8});
}
TEST_CASE_METHOD(QueryFixture, "Test delete statement", "[query][statement][delete]") {
using namespace matador::test;
schema.attach<matador::test::person>("person");
auto stmt = query::create()
.table<matador::test::person>("person", schema)
.prepare(db);
auto res = stmt.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
stmt = query::insert()
.into<person>("person", schema)
.values<person>()
.prepare(db);
std::vector<person> peoples {
{1,"george", 45, {1,2,3,4}},
{2,"jane", 36, {1,2,3,4}},
{3,"lukas", 68, {1,2,3,4}},
{4,"merlin", 99, {1,2,3,4}}
};
for (const auto &p : peoples) {
res = stmt.bind(p)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
stmt.reset();
}
auto select_stmt = query::select<person>(schema)
.from("person")
.where("name"_col == matador::utils::_)
.prepare(db);
auto rows = select_stmt
.bind(0, "jane")
.fetch<person>();
REQUIRE(rows.is_ok());
for (const auto &r : *rows) {
constexpr size_t index = 1;
REQUIRE(r.id == peoples[index].id);
REQUIRE(r.name == peoples[index].name);
REQUIRE(r.age == peoples[index].age);
REQUIRE(r.image == peoples[index].image);
}
stmt = query::remove()
.from("person")
.where("name"_col == matador::utils::_)
.prepare(db);
res = stmt.bind(0, "jane")
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
select_stmt.reset();
auto row = select_stmt
.bind(0, "jane")
.fetch_one<person>();
REQUIRE(row.is_ok());
REQUIRE(*row == nullptr);
stmt.reset();
res = stmt
.bind(0, "merlin")
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
select_stmt.reset();
row = select_stmt
.bind(0, "merlin")
.fetch_one<person>();
REQUIRE(row.is_ok());
REQUIRE(*row == nullptr);
}
TEST_CASE_METHOD(QueryFixture, "Test reuse prepared statement", "[query][statement][reuse]") {
using namespace matador::test;
schema.attach<matador::test::person>("person");
auto stmt = query::create()
.table<matador::test::person>("person", schema)
.prepare(db);
auto res = stmt.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
tables_to_drop.emplace("person");
check_table_exists("person");
stmt = query::insert()
.into<person>("person", schema)
.values<person>()
.prepare(db);
std::vector<person> peoples {
{1,"george", 45, {1,2,3,4}},
{2,"jane", 36, {1,2,3,4}},
{3,"lukas", 68, {1,2,3,4}},
{4,"merlin", 99, {1,2,3,4}}
};
for (const auto &p : peoples) {
res = stmt.bind(p)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
stmt.reset();
}
stmt = query::select<person>(schema)
.from("person")
.prepare(db);
auto rows = stmt.fetch<person>();
REQUIRE(rows.is_ok());
size_t index = 0;
for (const auto &r : *rows) {
REQUIRE(r.id == peoples[index].id);
REQUIRE(r.name == peoples[index].name);
REQUIRE(r.age == peoples[index].age);
REQUIRE(r.image == peoples[index].image);
++index;
}
stmt.reset();
rows = stmt.fetch<person>();
REQUIRE(rows.is_ok());
index = 0;
for (const auto &r : *rows) {
REQUIRE(r.id == peoples[index].id);
REQUIRE(r.name == peoples[index].name);
REQUIRE(r.age == peoples[index].age);
REQUIRE(r.image == peoples[index].image);
++index;
}
}
+532
View File
@@ -0,0 +1,532 @@
#include "catch2/catch_test_macros.hpp"
#include "matador/sql/column_definition.hpp"
#include "matador/sql/condition.hpp"
#include "matador/sql/query.hpp"
#include "QueryFixture.hpp"
#include "models/airplane.hpp"
#include "models/flight.hpp"
#include "models/person.hpp"
#include "models/recipe.hpp"
using namespace matador::sql;
using namespace matador::test;
TEST_CASE_METHOD(QueryFixture, "Create table with foreign key relation", "[query][foreign][relation]")
{
schema.attach<airplane>("airplane");
schema.attach<flight>("flight");
auto res = query::create()
.table<airplane>("airplane", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
check_table_exists("airplane");
tables_to_drop.emplace("airplane");
res = query::create()
.table<flight>("flight", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
check_table_exists("flight");
tables_to_drop.emplace("flight");
}
TEST_CASE_METHOD(QueryFixture, "Execute select statement with where clause", "[query][where]")
{
schema.attach<person>("person");
auto res = query::create()
.table<person>("person", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
check_table_exists("person");
tables_to_drop.emplace("person");
person george{7, "george", 45};
george.image.emplace_back(37);
res = query::insert()
.into("person", column_generator::generate<person>(schema, true))
.values(george)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
// fetch person as record
auto result_record = query::select(column_generator::generate<person>(schema, true))
.from("person")
.where("id"_col == 7)
.fetch_all(db);
REQUIRE(result_record.is_ok());
for (const auto &i: *result_record) {
REQUIRE(i.size() == 4);
REQUIRE(i.at(0).name() == "id");
REQUIRE(i.at(0).is_integer());
REQUIRE(i.at(0).as<long long>() == george.id);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).is_varchar());
REQUIRE(i.at(1).as<std::string>() == george.name);
REQUIRE(i.at(2).name() == "age");
REQUIRE(i.at(2).is_integer());
REQUIRE(i.at(2).as<long long>() == george.age);
}
// fetch person as person
auto result_person = query::select(column_generator::generate<person>(schema, true))
.from("person")
.where("id"_col == 7)
.fetch_all<person>(db);
REQUIRE(result_person.is_ok());
for (const auto &i: *result_person) {
REQUIRE(i.id == 7);
REQUIRE(i.name == "george");
REQUIRE(i.age == 45);
}
}
TEST_CASE_METHOD(QueryFixture, "Execute insert statement", "[query][insert]")
{
auto res = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<std::string>("color", 63)
})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("person"));
tables_to_drop.emplace("person");
res = query::insert()
.into("person", {{"", "id", ""}, {"", "name", ""}, {"", "color", ""}})
.values({7, "george", "green"})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
// fetch person as record
auto result_record = query::select({"id", "name", "color"})
.from("person")
.where("id"_col == 7)
.fetch_all(db);
REQUIRE(result_record.is_ok());
for (const auto &i: *result_record) {
REQUIRE(i.size() == 3);
REQUIRE(i.at(0).name() == "id");
REQUIRE(i.at(0).is_integer());
REQUIRE(i.at(0).as<unsigned long>() == 7);
REQUIRE(i.at(1).name() == "name");
REQUIRE(i.at(1).is_varchar());
REQUIRE(i.at(1).as<std::string>() == "george");
REQUIRE(i.at(2).name() == "color");
REQUIRE(i.at(2).is_varchar());
REQUIRE(i.at(2).as<std::string>() == "green");
}
}
TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key", "[query][foreign]")
{
schema.attach<airplane>("airplane");
schema.attach<flight>("flight");
auto res = query::create()
.table<airplane>("airplane", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("airplane"));
tables_to_drop.emplace("airplane");
res = query::create()
.table<flight>("flight", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("flight"));
tables_to_drop.emplace("flight");
std::vector<matador::object_ptr<airplane>> planes{
matador::object_ptr<airplane>(new airplane{1, "Airbus", "A380"}),
matador::object_ptr<airplane>(new airplane{2, "Boeing", "707"}),
matador::object_ptr<airplane>(new airplane{3, "Boeing", "747"})
};
for (const auto &plane: planes) {
res = query::insert()
.into("airplane", column_generator::generate<airplane>(schema, true))
.values(*plane)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto count = query::select({count_all()})
.from("airplane")
.fetch_value<int>(db);
REQUIRE(count.is_ok());
REQUIRE(*count == 3);
flight f4711{4, planes.at(1), "hans"};
res = query::insert()
.into("flight", column_generator::generate<flight>(schema, true))
.values(f4711)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto f = query::select(column_generator::generate<flight>(schema, true))
.from("flight")
.fetch_one(db);
REQUIRE(f.is_ok());
REQUIRE(f.value()->at(0).as<unsigned long>() == 4);
REQUIRE(f.value()->at(1).as<unsigned long>() == 2);
REQUIRE(f.value()->at(2).as<std::string>() == "hans");
}
TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and join_left", "[query][foreign][join_left]")
{
schema.attach<airplane>("airplane");
schema.attach<flight>("flight");
auto res = query::create()
.table<airplane>("airplane", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("airplane"));
tables_to_drop.emplace("airplane");
res = query::create()
.table<flight>("flight", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("flight"));
tables_to_drop.emplace("flight");
std::vector<matador::object_ptr<airplane>> planes{
matador::object_ptr<airplane>(new airplane{1, "Airbus", "A380"}),
matador::object_ptr<airplane>(new airplane{2, "Boeing", "707"}),
matador::object_ptr<airplane>(new airplane{3, "Boeing", "747"})
};
for (const auto &plane: planes) {
res = query::insert()
.into("airplane", column_generator::generate<airplane>(schema, true))
.values(*plane)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto count = query::select({count_all()})
.from("airplane")
.fetch_value<int>(db).value();
REQUIRE(count == 3);
std::vector<matador::object_ptr<flight>> flights{
matador::object_ptr<flight>(new flight{4, planes.at(0), "hans"}),
matador::object_ptr<flight>(new flight{5, planes.at(0), "otto"}),
matador::object_ptr<flight>(new flight{6, planes.at(1), "george"}),
matador::object_ptr<flight>(new flight{7, planes.at(2), "paul"})
};
for (const auto &f: flights) {
res = query::insert()
.into("flight", {"id", "airplane_id", "pilot_name"})
.values(*f)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto f = query::select(column_generator::generate<flight>(schema, true))
.from("flight")
.fetch_one(db);
REQUIRE(f.is_ok());
REQUIRE(f->has_value());
REQUIRE(f.value()->at(0).as<unsigned long>() == 4);
REQUIRE(f.value()->at(1).as<unsigned long>() == 1);
REQUIRE(f.value()->at(2).as<std::string>() == "hans");
auto result = query::select({"f.id", "ap.brand", "ap.model", "f.pilot_name"})
.from({"flight", "f"})
.join_left({"airplane", "ap"})
.on("f.airplane_id"_col == "ap.id"_col)
.order_by("f.id").asc()
.fetch_all(db);
REQUIRE(result.is_ok());
std::vector<std::pair<unsigned long, std::string>> expected_result {
{4, "hans"},
{5, "otto"},
{6, "george"},
{7, "paul"}
};
size_t index{0};
for (const auto &r: *result) {
REQUIRE(r.size() == 4);
REQUIRE(r.at(0).as<unsigned long>() == expected_result[index].first);
REQUIRE(r.at(3).as<std::string>() == expected_result[index++].second);
}
}
TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and for single entity", "[query][join_left][find]") {
schema.attach<airplane>("airplane");
schema.attach<flight>("flight");
auto res = query::create()
.table<airplane>("airplane", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("airplane"));
tables_to_drop.emplace("airplane");
res = query::create()
.table<flight>("flight", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("flight"));
tables_to_drop.emplace("flight");
std::vector<matador::object_ptr<airplane>> planes{
matador::object_ptr<airplane>(new airplane{1, "Airbus", "A380"}),
matador::object_ptr<airplane>(new airplane{2, "Boeing", "707"}),
matador::object_ptr<airplane>(new airplane{3, "Boeing", "747"})
};
for (const auto &plane: planes) {
res = query::insert()
.into("airplane", column_generator::generate<airplane>(schema, true))
.values(*plane)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto count = query::select({count_all()})
.from("airplane")
.fetch_value<int>(db);
REQUIRE(count.is_ok());
REQUIRE(count->has_value());
REQUIRE(count->value() == 3);
std::vector<matador::object_ptr<flight>> flights{
matador::object_ptr<flight>(new flight{4, planes.at(0), "hans"}),
matador::object_ptr<flight>(new flight{5, planes.at(0), "otto"}),
matador::object_ptr<flight>(new flight{6, planes.at(1), "george"}),
matador::object_ptr<flight>(new flight{7, planes.at(2), "paul"})
};
for (const auto &f: flights) {
res = query::insert()
.into("flight", column_generator::generate<flight>(schema, true))
.values(*f)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto f = query::select(column_generator::generate<flight>(schema, true))
.from("flight")
.fetch_one(db);
REQUIRE(f.is_ok());
REQUIRE(f->has_value());
REQUIRE(f.value()->at(0).as<unsigned long>() == 4);
REQUIRE(f.value()->at(1).as<unsigned long>() == 1);
REQUIRE(f.value()->at(2).as<std::string>() == "hans");
auto result = query::select({"f.id", "f.airplane_id", "ap.brand", "ap.model", "f.pilot_name"})
.from({"flight", "f"})
.join_left({"airplane", "ap"})
.on("f.airplane_id"_col == "ap.id"_col)
.where("f.id"_col == 4)
.fetch_one<flight>(db);
auto expected_flight = flights[0];
REQUIRE(result.is_ok());
REQUIRE(*result);
REQUIRE(result.value()->id == expected_flight->id);
REQUIRE(result.value()->pilot_name == expected_flight->pilot_name);
REQUIRE(result.value()->airplane.get());
REQUIRE(result.value()->airplane->id == 1);
REQUIRE(result.value()->airplane->model == "A380");
REQUIRE(result.value()->airplane->brand == "Airbus");
}
TEST_CASE_METHOD(QueryFixture, "Select statement with many to many relationship", "[query][join][many_to_many]") {
schema.attach<recipe>("recipes");
schema.attach<ingredient>("ingredients");
schema.attach<recipe_ingredient>("recipe_ingredients");
auto res = query::create()
.table<recipe>("recipes", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("recipes"));
tables_to_drop.emplace("recipes");
res = query::create()
.table<ingredient>("ingredients", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("ingredients"));
tables_to_drop.emplace("ingredients");
res = query::create()
.table<recipe_ingredient>("recipe_ingredients", schema)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 0);
REQUIRE(db.exists("recipe_ingredients"));
tables_to_drop.emplace("recipe_ingredients");
std::vector<ingredient> ingredients {
{1, "Apple"},
{2, "Strawberry"},
{3, "Pineapple"},
{4, "Sugar"},
{5, "Flour"},
{6, "Butter"},
{7, "Beans"}
};
for (const auto &i: ingredients) {
res = query::insert()
.into("ingredients", column_generator::generate<ingredient>(schema, true))
.values(i)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
std::vector<recipe> recipes{
{7, "Apple Crumble"},
{8, "Beans Chili"},
{9, "Fruit Salad"}
};
for (const auto &r: recipes) {
res = query::insert()
.into("recipes", column_generator::generate<recipe>(schema, true))
.values(r)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
std::vector<std::pair<int, int>> recipe_ingredients {
{ 7, 1 },
{ 7, 4 },
{ 7, 5 },
{ 8, 6 },
{ 8, 7 },
{ 9, 1 },
{ 9, 2 },
{ 9, 3 }
};
for (const auto &ri: recipe_ingredients) {
res = query::insert()
.into("recipe_ingredients", column_generator::generate<recipe_ingredient>(schema, true))
.values({ri.first, ri.second})
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto result = query::select({"r.id", "r.name", "ri.ingredient_id"})
.from({"recipes", "r"})
.join_left({"recipe_ingredients", "ri"})
.on("r.id"_col == "ri.recipe_id"_col)
.fetch_all(db);
REQUIRE(result.is_ok());
std::vector<std::tuple<unsigned long, std::string, unsigned long>> expected_result_one_join {
{7, "Apple Crumble", 1},
{7, "Apple Crumble", 4},
{7, "Apple Crumble", 5},
{8, "Beans Chili", 6},
{8, "Beans Chili", 7},
{9, "Fruit Salad", 1},
{9, "Fruit Salad", 2},
{9, "Fruit Salad", 3}
};
size_t index{0};
for (const auto &r: *result) {
REQUIRE(r.size() == 3);
REQUIRE(r.at(0).as<unsigned long>().value() == std::get<0>(expected_result_one_join[index]));
REQUIRE(r.at(1).as<std::string>().value() == std::get<1>(expected_result_one_join[index]));
REQUIRE(r.at(2).as<unsigned long>().value() == std::get<2>(expected_result_one_join[index]));
++index;
}
result = query::select({"r.id", "r.name", "ri.ingredient_id", "i.name"})
.from({"recipes", "r"})
.join_left({"recipe_ingredients", "ri"}).on("r.id"_col == "ri.recipe_id"_col)
.join_left({"ingredients", "i"}).on("ri.ingredient_id"_col == "i.id"_col)
.fetch_all(db);
REQUIRE(result.is_ok());
std::vector<std::tuple<unsigned long, std::string, unsigned long, std::string>> expected_result_two_joins {
{7, "Apple Crumble", 1, "Apple"},
{7, "Apple Crumble", 4, "Sugar"},
{7, "Apple Crumble", 5, "Flour"},
{8, "Beans Chili", 6, "Butter"},
{8, "Beans Chili", 7, "Beans"},
{9, "Fruit Salad", 1, "Apple"},
{9, "Fruit Salad", 2, "Strawberry"},
{9, "Fruit Salad", 3, "Pineapple"}
};
index = 0;
for (const auto &r: *result) {
REQUIRE(r.size() == 4);
REQUIRE(r.at(0).as<unsigned long>().value() == std::get<0>(expected_result_two_joins[index]));
REQUIRE(r.at(1).as<std::string>().value() == std::get<1>(expected_result_two_joins[index]));
REQUIRE(r.at(2).as<unsigned long>().value() == std::get<2>(expected_result_two_joins[index]));
REQUIRE(r.at(3).as<std::string>().value() == std::get<3>(expected_result_two_joins[index]));
++index;
}
result = query::select({"r.id", "r.name", "ri.ingredient_id", "i.name"})
.from({"recipes", "r"})
.join_left({"recipe_ingredients", "ri"}).on("r.id"_col == "ri.recipe_id"_col)
.join_left({"ingredients", "i"}).on("ri.ingredient_id"_col == "i.id"_col)
.where("r.id"_col == 8)
.fetch_all(db);
REQUIRE(result.is_ok());
index = 3;
for (const auto &r: *result) {
REQUIRE(r.size() == 4);
REQUIRE(r.at(0).as<unsigned long>().value() == std::get<0>(expected_result_two_joins[index]));
REQUIRE(r.at(1).as<std::string>().value() == std::get<1>(expected_result_two_joins[index]));
REQUIRE(r.at(2).as<unsigned long>().value() == std::get<2>(expected_result_two_joins[index]));
REQUIRE(r.at(3).as<std::string>().value() == std::get<3>(expected_result_two_joins[index]));
++index;
}
}
+21
View File
@@ -0,0 +1,21 @@
#include "SessionFixture.hpp"
namespace matador::test {
SessionFixture::SessionFixture()
: pool(connection::dns, 4), ses(pool) {}
SessionFixture::~SessionFixture() {
while (!tables_to_drop.empty()) {
drop_table_if_exists(tables_to_drop.top());
tables_to_drop.pop();
}
}
void SessionFixture::drop_table_if_exists(const std::string &table_name) const {
if (ses.table_exists(table_name)) {
ses.drop_table(table_name);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef MATADOR_SESSION_FIXTURE_HPP
#define MATADOR_SESSION_FIXTURE_HPP
#include "matador/sql/session.hpp"
#include "connection.hpp"
#include <stack>
namespace matador::test {
class SessionFixture {
public:
SessionFixture();
~SessionFixture();
protected:
sql::connection_pool<sql::connection> pool;
sql::session ses;
std::stack <std::string> tables_to_drop;
private:
void drop_table_if_exists(const std::string &table_name) const;
};
}
#endif //MATADOR_SESSION_FIXTURE_HPP
+136
View File
@@ -0,0 +1,136 @@
#include "catch2/catch_test_macros.hpp"
#include "SessionFixture.hpp"
#include "models/airplane.hpp"
#include "models/author.hpp"
#include "models/book.hpp"
#include "models/flight.hpp"
using namespace matador;
using namespace matador::test;
TEST_CASE_METHOD(SessionFixture, "Session relation test", "[session][relation]") {
ses.attach<airplane>("airplanes");
ses.attach<flight>("flights");
ses.create_schema();
tables_to_drop.emplace("airplanes");
tables_to_drop.emplace("flights");
auto plane = ses.insert<airplane>(1, "Boeing", "A380");
auto f = ses.insert<flight>(2, plane, "sully");
const auto result = ses.find<flight>(2);
REQUIRE(result.is_ok());
REQUIRE(result->get()->id == f->id);
REQUIRE(result->get()->pilot_name == f->pilot_name);
REQUIRE(result->get()->airplane);
REQUIRE(result->get()->airplane->id == plane->id);
REQUIRE(result->get()->airplane->brand == plane->brand);
REQUIRE(result->get()->airplane->model == plane->model);
}
TEST_CASE_METHOD(SessionFixture, "Use session to find object with id", "[session][find]") {
ses.attach<airplane>("airplanes");
ses.create_schema();
tables_to_drop.emplace("airplanes");
auto a380 = ses.insert<airplane>(1, "Boeing", "A380");
auto result = ses.find<airplane>(2);
REQUIRE(!result.is_ok());
REQUIRE((result.err().ec() == sql::session_error_code::FailedToFindObject));
result = ses.find<airplane>(1);
REQUIRE(result);
auto read_a380 = result.value();
REQUIRE(a380->id == read_a380->id);
}
TEST_CASE_METHOD(SessionFixture, "Use session to find all objects", "[session][find]") {
ses.attach<airplane>("airplanes");
ses.create_schema();
tables_to_drop.emplace("airplanes");
std::vector<std::unique_ptr<airplane>> planes;
planes.emplace_back(new airplane(1, "Airbus", "A380"));
planes.emplace_back(new airplane(2, "Boeing", "707"));
planes.emplace_back(new airplane(3, "Boeing", "747"));
for (auto &&plane: planes) {
ses.insert(plane.release());
}
auto result = ses.find<airplane>();
std::vector<std::tuple<unsigned long, std::string, std::string>> expected_result {
{1, "Airbus", "A380"},
{2, "Boeing", "707"},
{3, "Boeing", "747"}
};
REQUIRE(result);
auto all_planes = result.release();
size_t index {0};
for (const auto &i: all_planes) {
REQUIRE(i.id == std::get<0>(expected_result[index]));
REQUIRE(i.brand == std::get<1>(expected_result[index]));
REQUIRE(i.model == std::get<2>(expected_result[index]));
++index;
}
}
TEST_CASE_METHOD(SessionFixture, "Use session to find all objects with one-to-many relation", "[session][find][one-to-many]") {
ses.attach<author>("authors");
ses.attach<book>("books");
tables_to_drop.emplace("authors");
tables_to_drop.emplace("books");
ses.create_schema();
std::vector<std::unique_ptr<author>> authors;
authors.emplace_back(new author{1, "Michael", "Crichton", "23.10.1942", 1975, true, {}});
authors.emplace_back(new author{ 2, "Steven", "King", "21.9.1947", 1956, false, {}});
for (auto &&a: authors) {
ses.insert(a.release());
}
auto result = ses.find<author>();
REQUIRE(result.is_ok());
auto all_authors = result.release();
std::vector<object_ptr<author>> author_repo;
for (auto it = all_authors.begin(); it != all_authors.end(); ++it) {
std::cout << "author: " << it->first_name << " (books: " << it->books.size() << ")\n";
author_repo.emplace_back(it.release());
}
REQUIRE(author_repo.size() == 2);
std::vector<std::unique_ptr<book>> books;
books.emplace_back( new book{3, "Jurassic Park", author_repo[0], 1990} );
books.emplace_back( new book{4, "Timeline", author_repo[0], 1999} );
books.emplace_back( new book{5, "The Andromeda Strain", author_repo[0], 1969} );
books.emplace_back( new book{6, "Congo", author_repo[0], 1980} );
books.emplace_back( new book{7, "Prey", author_repo[0], 2002} );
books.emplace_back( new book{8, "Carrie", author_repo[1], 1974} );
books.emplace_back( new book{9, "The Shining", author_repo[1], 1977} );
books.emplace_back( new book{10, "It", author_repo[1], 1986} );
books.emplace_back( new book{11, "Misery", author_repo[1], 1987} );
books.emplace_back( new book{12, "The Dark Tower: The Gunslinger", author_repo[1], 1982} );
for (auto &&b: books) {
ses.insert(b.release());
}
result = ses.find<author>();
REQUIRE(result);
all_authors = result.release();
for (auto it = all_authors.begin(); it != all_authors.end(); ++it) {
std::cout << "author: " << it->first_name << " (books: " << it->books.size() << ")\n";
}
}
+34
View File
@@ -0,0 +1,34 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/connection_info.hpp"
#include "matador/sql/connection_pool.hpp"
#include "matador/sql/session.hpp"
#include "matador/sql/statement_cache.hpp"
#include "connection.hpp"
#include "matador/sql/dialect_builder.hpp"
using namespace matador;
class StatementCacheFixture
{
public:
StatementCacheFixture()
: pool(matador::test::connection::dns, 4), ses(pool)
{}
~StatementCacheFixture() = default;
protected:
matador::sql::connection_pool<matador::sql::connection> pool;
matador::sql::session ses;
};
TEST_CASE_METHOD(StatementCacheFixture, "Acquire prepared statement", "[statement cache]") {
// const auto d = sql::dialect_builder::builder().create().build();
// sql::statement_cache cache(pool, d);
// auto conn = pool.acquire();
std::string sql = R"(SELECT * FROM person WHERE name = 'george')";
// auto &stmt = cache.acquire(sql, *conn);
}
+114
View File
@@ -0,0 +1,114 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/sql/column_definition.hpp"
#include "matador/sql/condition.hpp"
#include "matador/sql/connection.hpp"
#include "matador/sql/query.hpp"
#include "matador/object/object_ptr.hpp"
#include "QueryFixture.hpp"
#include "models/airplane.hpp"
using namespace matador::sql;
using namespace matador::test;
namespace matador::test::detail {
template<class Type, typename... Args>
[[maybe_unused]] object_ptr<Type> make_object_ptr(Args&&... args)
{
return object_ptr(new Type(std::forward<Args>(args)...));
}
}
class StatementTestFixture : public QueryFixture
{
public:
StatementTestFixture()
{
const auto res = query::create()
.table<airplane>("airplane", schema)
.execute(db);
REQUIRE(res.is_ok());
tables_to_drop.emplace("airplane");
}
protected:
std::vector<matador::object_ptr<airplane>> planes{
matador::test::detail::make_object_ptr<airplane>(1, "Airbus", "A380"),
matador::test::detail::make_object_ptr<airplane>(2, "Boeing", "707"),
matador::test::detail::make_object_ptr<airplane>(3, "Boeing", "747")
};
};
TEST_CASE_METHOD(StatementTestFixture, "Create prepared statement", "[statement]")
{
using namespace matador::utils;
schema.attach<airplane>("airplane");
table ap{"airplane"};
SECTION("Insert with prepared statement and placeholder") {
auto stmt = query::insert()
.into("airplane", column_generator::generate<airplane>(schema, true))
.values<airplane>()
.prepare(db);
for (const auto &plane: planes) {
auto res = stmt.bind(*plane).execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
stmt.reset();
}
auto result = query::select(column_generator::generate<airplane>(schema, true))
.from(ap)
.fetch_all<airplane>(db);
REQUIRE(result.is_ok());
size_t index{0};
for (const auto &i: *result) {
REQUIRE(i.id == planes[index]->id);
REQUIRE(i.brand == planes[index]->brand);
REQUIRE(i.model == planes[index++]->model);
}
}
SECTION("Select with prepared statement") {
for (const auto &plane: planes) {
auto res = query::insert()
.into("airplane", column_generator::generate<airplane>(schema, true))
.values(*plane)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
}
auto stmt = query::select(column_generator::generate<airplane>(schema, true))
.from(ap)
.where("brand"_col == _)
.prepare(db);
auto result = stmt.bind(0, "Airbus")
.fetch<airplane>();
REQUIRE(result.is_ok());
for (const auto &i: *result) {
REQUIRE(i.id == planes[0]->id);
REQUIRE(i.brand == planes[0]->brand);
REQUIRE(i.model == planes[0]->model);
}
stmt.reset();
result = stmt.bind(0, "Boeing")
.fetch<airplane>();
size_t index{1};
REQUIRE(result.is_ok());
for (const auto &i: *result) {
REQUIRE(i.id == planes[index]->id);
REQUIRE(i.brand == planes[index]->brand);
REQUIRE(i.model == planes[index++]->model);
}
}
}
+79
View File
@@ -0,0 +1,79 @@
#include <catch2/catch_test_macros.hpp>
#include "ColorEnumTraits.hpp"
#include "matador/sql/connection.hpp"
#include "matador/sql/column_generator.hpp"
#include "matador/sql/query.hpp"
#include "QueryFixture.hpp"
#include "models/location.hpp"
using namespace matador::sql;
using namespace matador::test;
class TypeTraitsTestFixture : public QueryFixture
{
public:
TypeTraitsTestFixture()
{
db.open();
const auto res = query::create()
.table<location>("location", schema)
.execute(db);
tables_to_drop.emplace("location");
}
};
TEST_CASE_METHOD(TypeTraitsTestFixture, "Special handling of attributes with type traits", "[typetraits]")
{
schema.attach<location>("location");
SECTION("Insert and select with direct execution") {
location loc{1, "center", {1, 2, 3}, Color::Black};
auto res = query::insert()
.into("location", column_generator::generate<location>(schema, true))
.values(loc)
.execute(db);
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto result = query::select(column_generator::generate<location>(schema, true))
.from("location")
.fetch_one<location>(db);
REQUIRE(result.is_ok());
REQUIRE((*result)->name == "center");
REQUIRE((*result)->color == Color::Black);
REQUIRE((*result)->coord.x == 1);
REQUIRE((*result)->coord.y == 2);
REQUIRE((*result)->coord.z == 3);
}
SECTION("Insert and select with prepared statement") {
location loc{1, "center", {1, 2, 3}, Color::Black};
auto stmt = query::insert()
.into("location", column_generator::generate<location>(schema, true))
.values<location>()
.prepare(db);
auto res = stmt
.bind(loc)
.execute();
REQUIRE(res.is_ok());
REQUIRE(*res == 1);
auto result = query::select(column_generator::generate<location>(schema, true))
.from("location")
.fetch_one<location>(db);
REQUIRE(result.is_ok());
REQUIRE((*result)->name == "center");
REQUIRE((*result)->color == Color::Black);
REQUIRE((*result)->coord.x == 1);
REQUIRE((*result)->coord.y == 2);
REQUIRE((*result)->coord.z == 3);
}
}
+25
View File
@@ -0,0 +1,25 @@
CPMAddPackage("gh:catchorg/Catch2@3.7.1")
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
add_executable(CoreTests
utils/BasicTypeToVisitorTest.cpp
utils/ConvertTest.cpp
utils/DefaultTypeTraitsTest.cpp
utils/DependencyInjectionTest.cpp
utils/IdentifierTest.cpp
utils/ResultTest.cpp
utils/FieldAttributeTest.cpp
utils/VersionTest.cpp
utils/StringTest.cpp
object/PrototypeTreeTest.cpp
)
target_link_libraries(CoreTests matador-core Catch2::Catch2WithMain)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(CoreTests PRIVATE -coverage)
target_link_options(CoreTests PRIVATE -coverage)
endif ()
add_test(NAME CoreTests COMMAND CoreTests)
+35
View File
@@ -0,0 +1,35 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/object/schema.hpp"
struct node {};
using namespace matador;
struct person {
virtual ~person() = default;
};
struct student final : person {};
struct teacher final : person {};
TEST_CASE("Test empty prototype tree", "[prototype_tree][empty]") {
const object::schema tree;
REQUIRE( tree.empty() );
}
TEST_CASE("Test add type to prototype tree", "[prototype_tree][add]") {
object::schema tree;
REQUIRE( tree.empty() );
auto res = tree.attach<person>("person");
REQUIRE( res.is_ok() );
res = tree.attach<student, person>("student");
REQUIRE( res.is_ok() );
res = tree.attach<teacher, person>("teacher");
REQUIRE( res.is_ok() );
REQUIRE( tree.size() == 3 );
}
@@ -0,0 +1,66 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/types.hpp"
#include "matador/utils/basic_type_converter.hpp"
using namespace matador::utils;
TEST_CASE("Test convert any type to string", "[any type visitor]") {
basic_type_converter<std::string> to_string_visitor;
database_type value = 6;
auto res = basic_type_converter<std::string>::convert_value(value);
REQUIRE(res.is_ok());
REQUIRE(*res == "6");
value = 2.5;
res = basic_type_converter<std::string>::convert_value(value);
REQUIRE(res.is_ok());
REQUIRE(*res == "2.5");
value = true;
res = basic_type_converter<std::string>::convert_value(value);
REQUIRE(res.is_ok());
REQUIRE(*res == "true");
value = "hello";
res = basic_type_converter<std::string>::convert_value(value);
REQUIRE(res.is_ok());
REQUIRE(*res == "hello");
value = std::string{"world"};
res = basic_type_converter<std::string>::convert_value(value);
REQUIRE(res.is_ok());
REQUIRE(*res == "world");}
TEST_CASE("Test convert any type to integral", "[any type visitor]") {
std::vector<std::pair<database_type, int>> values = {
{6, 6},
{2.5, 2},
{true, 1},
{"hello", 0},
{std::string{"world"}, 0}
};
for (const auto &value : values) {
const auto res = basic_type_converter<long>::convert_value(value.first);
REQUIRE(res.is_ok());
REQUIRE(*res == value.second);
}
}
TEST_CASE("Test convert any type to floating point", "[any type visitor]") {
std::vector<std::pair<database_type, double>> values = {
{6, 6},
{2.5, 2.5},
{true, 1},
{"hello", 0},
{std::string{"world"}, 0}
};
for (const auto &value : values) {
const auto res = basic_type_converter<double>::convert_value(value.first);
REQUIRE(res.is_ok());
REQUIRE(*res == value.second);
}
}
+160
View File
@@ -0,0 +1,160 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/convert.hpp"
//#include "matador/utils/date.hpp"
//#include "matador/utils/time.hpp"
#include "matador/utils/types.hpp"
using namespace matador::utils;
template<typename From, typename To>
void validate_conversion(From from)
{
auto res = to<To>(from);
REQUIRE(res.is_ok());
REQUIRE(static_cast<To>(from) == *res);
}
template<typename From, typename To>
void validate_conversion(const From &from, To expected_to)
{
auto res = to<To>(from);
REQUIRE(res.is_ok());
REQUIRE(*res == expected_to);
}
template<typename From>
void validate_conversion(const From &from, const std::string &expected_to)
{
auto res = to<std::string>(from);
REQUIRE(res.is_ok());
REQUIRE(*res == expected_to);
}
template<typename From>
void validate_conversion(const From &from, const char *expected_to)
{
validate_conversion<From>(from, std::string{expected_to});
}
template<typename From>
void validate_integral_conversion(From from) {
validate_conversion<From, int8_t>(from);
validate_conversion<From, int16_t>(from);
validate_conversion<From, int32_t>(from);
validate_conversion<From, int64_t>(from);
}
TEST_CASE("Test integral conversion", "[convert][integral]") {
validate_integral_conversion<int8_t>(-56);
validate_integral_conversion<int16_t>(-127);
validate_integral_conversion<int32_t>(-9876543);
validate_integral_conversion<int64_t>(-123456790);
validate_integral_conversion<uint8_t>(56);
validate_integral_conversion<uint16_t>(127);
validate_integral_conversion<uint32_t>(9876543);
validate_integral_conversion<uint64_t>(123456790);
validate_conversion<int16_t, int8_t>(513, 1);
validate_conversion<int32_t, int8_t>(515, 3);
validate_conversion<int64_t, int8_t>(516, 4);
}
TEST_CASE("Test floating point conversion", "[convert][floating_point]") {
validate_conversion<float, float>(-0.1f);
validate_conversion<float, double>(-0.1f);
validate_conversion<double, double>(-0.44444);
validate_conversion<double, float>(-0.44444);
}
TEST_CASE("Test integral to string conversion", "[convert][integral][string]") {
validate_conversion<int8_t>(-56, "-56");
validate_conversion<int16_t>(-127, "-127");
validate_conversion<int32_t>(-9876543, "-9876543");
validate_conversion<int64_t>(-123456790, "-123456790");
validate_conversion<uint8_t>(56, "56");
validate_conversion<uint16_t>(127, "127");
validate_conversion<uint32_t>(9876543, "9876543");
validate_conversion<uint64_t>(123456790, "123456790");
}
TEST_CASE("Test floating point to string conversion", "[convert][floating_point][string]") {
validate_conversion<float>(-56.1234f, "-56.1234");
validate_conversion<double>(-127.444449, "-127.444449");
}
TEST_CASE("Test string to integral conversion", "[convert][string][integral]") {
validate_conversion<std::string, int8_t>("-56", -56);
validate_conversion<std::string, int16_t>("-127", -127);
validate_conversion<std::string, int32_t>("-9876543", -9876543);
validate_conversion<std::string, int64_t>("-123456790", -123456790);
validate_conversion<std::string, uint8_t>("56", 56);
validate_conversion<std::string, uint16_t>("127", 127);
validate_conversion<std::string, uint32_t>("9876543", 9876543);
validate_conversion<std::string, uint64_t>("123456790", 123456790);
validate_conversion<const char*, int8_t>("-56", -56);
validate_conversion<const char*, int16_t>("-127", -127);
validate_conversion<const char*, int32_t>("-9876543", -9876543);
validate_conversion<const char*, int64_t>("-123456790", -123456790);
validate_conversion<const char*, uint8_t>("56", 56);
validate_conversion<const char*, uint16_t>("127", 127);
validate_conversion<const char*, uint32_t>("9876543", 9876543);
validate_conversion<const char*, uint64_t>("123456790", 123456790);
}
TEST_CASE("Test string to floating point conversion", "[convert][string][floating_point]") {
validate_conversion<std::string, float>("-56.1234", -56.1234f);
validate_conversion<std::string, double>("-127.444449", -127.444449);
validate_conversion<const char*, float>("-56.1234", -56.1234f);
validate_conversion<const char*, double>("-127.444449", -127.444449);
}
TEST_CASE("Test blob to blob conversion", "[convert][blob]") {
blob from{1, 2, 3, 4};
const auto res = to<blob>(from);
REQUIRE(res.is_ok());
REQUIRE(from == *res);
}
// TEST_CASE("Validate date to string conversion", "[convert][date][string]") {
// matador::date today;
// const auto expected_string = matador::utils::to_string(today);
// std::string to;
//
// convert(to, today);
//
// REQUIRE(expected_string == to);
// }
//
// TEST_CASE("Validate date conversion leads to an exception", "[convert][date][exception]") {
// matador::date today;
// int to{};
// convert(to, today);
// REQUIRE(to == today.julian_date());
// }
//
// TEST_CASE("Validate time to string conversion", "[convert][time][string]") {
// matador::time now;
// const auto expected_string = matador::utils::to_string(now);
// std::string to;
//
// convert(to, now);
//
// REQUIRE(expected_string == to);
// }
//
// TEST_CASE("Validate time conversion leads to an exception", "[convert][time][exception]") {
// matador::time now;
// int to;
// convert(to, now);
// REQUIRE(to == now.get_time_info().seconds_since_epoch);
// }
+28
View File
@@ -0,0 +1,28 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/default_type_traits.hpp"
using namespace matador::utils;
TEST_CASE("Test default data types", "[data_types][type]") {
REQUIRE(data_type_traits<int8_t>::type() == basic_type::type_int8);
REQUIRE(data_type_traits<int16_t>::type() == basic_type::type_int16);
REQUIRE(data_type_traits<int32_t>::type() == basic_type::type_int32);
REQUIRE(data_type_traits<int64_t>::type() == basic_type::type_int64);
REQUIRE(data_type_traits<uint8_t>::type() == basic_type::type_uint8);
REQUIRE(data_type_traits<uint16_t>::type() == basic_type::type_uint16);
REQUIRE(data_type_traits<uint32_t>::type() == basic_type::type_uint32);
REQUIRE(data_type_traits<uint64_t>::type() == basic_type::type_uint64);
REQUIRE(data_type_traits<bool>::type() == basic_type::type_bool);
REQUIRE(data_type_traits<float>::type() == basic_type::type_float);
REQUIRE(data_type_traits<double>::type() == basic_type::type_double);
REQUIRE(data_type_traits<const char*>::type(32) == basic_type::type_varchar);
REQUIRE(data_type_traits<const char*>::type(0) == basic_type::type_text);
REQUIRE(data_type_traits<char*>::type(32) == basic_type::type_varchar);
REQUIRE(data_type_traits<char*>::type(0) == basic_type::type_text);
REQUIRE(data_type_traits<char[]>::type(32) == basic_type::type_varchar);
REQUIRE(data_type_traits<char[]>::type(0) == basic_type::type_text);
REQUIRE(data_type_traits<std::string>::type(32) == basic_type::type_varchar);
REQUIRE(data_type_traits<std::string>::type(0) == basic_type::type_text);
REQUIRE(data_type_traits<blob>::type(0) == basic_type::type_blob);
}
+113
View File
@@ -0,0 +1,113 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/di.hpp"
#include <iostream>
namespace detail {
class greeter {
public:
virtual ~greeter() = default;
[[nodiscard]] virtual std::string greet() const = 0;
};
class smart_greeter final : public greeter {
public:
[[nodiscard]] std::string greet() const override { return "hey dude"; }
};
class hello_greeter final : public greeter {
public:
[[nodiscard]] std::string greet() const override { return "hello"; }
};
class vehicle
{
public:
virtual ~vehicle() = default;
[[nodiscard]] virtual long id() const = 0;
};
class truck final : public vehicle
{
public:
truck() : id_(++id_counter_) {}
[[nodiscard]] long id() const override { return id_; }
private:
static long id_counter_;
long id_{};
};
long truck::id_counter_ = 0;
class unknown {};
class per_thread {
public:
virtual ~per_thread() = default;
virtual void dump() = 0;
};
class per_thread_dumper final : public per_thread
{
public:
explicit per_thread_dumper(std::string name ) : name_(std::move(name)) {}
void dump() override {
std::cout << name_ << ": thread id " << std::this_thread::get_id() << "\n";
}
private:
std::string name_;
};
}
using namespace matador::utils;
TEST_CASE("Test dependency injection", "[dependency-injection]") {
di::install_module([](di::module &module) {
module.bind<detail::greeter>()->to_singleton<detail::smart_greeter>();
});
di::inject<detail::greeter> g1;
REQUIRE(g1);
auto g2 = g1;
REQUIRE(g2);
REQUIRE(g1 == g2);
g2 = std::move(g1);
REQUIRE(!g1);
REQUIRE(g2);
REQUIRE(g1 != g2);
auto g3(std::move(g2));
REQUIRE(!g2);
REQUIRE(g3);
REQUIRE(g3 != g2);
di::module m;
m.bind<detail::greeter>()->to_singleton<detail::hello_greeter>();
m.bind<detail::greeter>("smart")->to_singleton<detail::smart_greeter>();
di::inject<detail::greeter> g4(m, "smart");
REQUIRE(g4);
REQUIRE(g4->greet() == "hey dude");
di::inject<detail::greeter> g5(m);
REQUIRE(g5);
REQUIRE(g5->greet() == "hello");
REQUIRE(g4 != g5);
// UNIT_ASSERT_EXCEPTION(matador::di::inject<detail::unknown> u, std::logic_error, "unknown type");
}
+32
View File
@@ -0,0 +1,32 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/field_attributes.hpp"
using namespace matador::utils;
TEST_CASE("Test field attribute", "[field-attribute]") {
field_attributes attr;
REQUIRE(attr.size() == 0);
REQUIRE(attr.options() == constraints::NONE);
attr = 255;
REQUIRE(attr.size() == 255);
REQUIRE(attr.options() == constraints::NONE);
attr = constraints::INDEX;
REQUIRE(attr.size() == 0);
REQUIRE(attr.options() == constraints::INDEX);
attr = { 255, constraints::DEFAULT };
REQUIRE(attr.size() == 255);
REQUIRE(attr.options() == constraints::DEFAULT);
field_attributes attr2{255};
REQUIRE(attr2.size() == 255);
REQUIRE(attr2.options() == constraints::NONE);
field_attributes attr3{constraints::UNIQUE};
REQUIRE(attr3.size() == 0);
REQUIRE(attr3.options() == constraints::UNIQUE);
}
+118
View File
@@ -0,0 +1,118 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/identifier.hpp"
#include "matador/utils/default_type_traits.hpp"
using namespace matador::utils;
TEST_CASE("Test create identifier", "[identifier][create]") {
const identifier id;
REQUIRE(id.is_null());
REQUIRE(!id.is_integer());
REQUIRE(!id.is_floating_point());
REQUIRE(!id.is_bool());
REQUIRE(!id.is_varchar());
REQUIRE(!id.is_date());
REQUIRE(!id.is_time());
REQUIRE(!id.is_blob());
REQUIRE(!id.is_valid());
REQUIRE(id.str() == "null");
}
TEST_CASE("Test assign value to identifier", "[identifier][assign]") {
identifier id;
REQUIRE(id.is_null());
REQUIRE(!id.is_valid());
REQUIRE(id.str() == "null");
id = 7;
REQUIRE(!id.is_null());
REQUIRE(id.is_valid());
REQUIRE(id.is_integer());
REQUIRE(id.str() == "7");
REQUIRE(id.type() == basic_type::type_int32);
REQUIRE(id.type_index() == std::type_index(typeid(int)));
id = std::string{"UniqueId"};
REQUIRE(!id.is_null());
REQUIRE(id.is_valid());
REQUIRE(id.is_varchar());
REQUIRE(id.str() == "UniqueId");
id = "UniqueId";
REQUIRE(!id.is_null());
REQUIRE(id.is_valid());
REQUIRE(id.is_varchar());
// REQUIRE(id == identifier{"UniqueId"});
}
TEST_CASE("Test compare identifier", "[identifier][compare]") {
identifier id1{6}, id2{7};
REQUIRE(id1 != id2);
REQUIRE(id1 < id2);
REQUIRE(id1 <= id2);
REQUIRE(id2 > id1);
REQUIRE(id2 >= id1);
REQUIRE(id1.hash() != id2.hash());
id2 = 6;
REQUIRE(id1 == id2);
REQUIRE(id1.hash() == id2.hash());
}
identifier create(const int id) {
return identifier{id};
}
TEST_CASE("Test copy identifier" "[identifier][copy]") {
identifier id1{"Unique"};
REQUIRE(id1.is_valid());
REQUIRE(id1.str() == "Unique");
const identifier id2(id1);
REQUIRE(id1 == id2);
REQUIRE(id1.hash() == id2.hash());
id1.clear();
REQUIRE(id1.is_null());
identifier id3 = id1;
REQUIRE(id1 == id3);
REQUIRE(id1 < id3);
REQUIRE(id3.is_null());
REQUIRE(id1.hash() == id3.hash());
id3 = create(9);
id3 = id1;
}
TEST_CASE("Test move identifier", "[identifier][move]") {
identifier id1{6};
REQUIRE(id1.is_integer());
REQUIRE(!id1.is_null());
const auto id2 = std::move(id1);
REQUIRE(id1.is_null());
REQUIRE(id2.is_integer());
}
TEST_CASE("Test share identifier", "[identifier][share]") {
const identifier id1{6};
REQUIRE(id1.use_count() == 1);
auto id2 = id1.share();
REQUIRE(id1 == id2);
REQUIRE(id1.use_count() == 2);
}
+176
View File
@@ -0,0 +1,176 @@
#include <catch2/catch_test_macros.hpp>
#include <utility>
#include "matador/utils/result.hpp"
namespace matador::test {
enum class math_error : int32_t {
OK = 0,
DIVISION_BY_ZERO = 1,
FAILURE = 2
};
utils::result<float, math_error>divide(const float x, const float y) {
if (y == 0) {
return utils::failure(math_error::DIVISION_BY_ZERO);
}
return utils::ok(x / y);
}
utils::result<float, math_error>multiply(const float x, const float y) {
return utils::ok(x * y);
}
utils::result<float, math_error>plus(const float x, const float y) {
return utils::ok(x + y);
}
utils::result<void, math_error>action_on_greater_42(const float i) {
if (i > 42) {
return utils::ok<void>();
}
return utils::failure(math_error::FAILURE);
}
}
using namespace matador;
TEST_CASE("Test result", "[result]") {
auto res = test::divide(4, 2);
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 2.0));
REQUIRE_THROWS(res.err());
res = test::divide(4, 0);
REQUIRE(!res);
REQUIRE(!res.is_ok());
REQUIRE(res.is_error());
REQUIRE((res.err() == test::math_error::DIVISION_BY_ZERO));
res = test::divide(4, 2)
.and_then([](const auto &val) { return test::multiply(val, 5); })
.and_then([](const auto &val) { return test::plus(val, 10); });
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 20.0));
res = test::divide(4, 0)
.and_then([](const auto &val) {
return test::multiply(val, 5);
});
REQUIRE(!res);
REQUIRE(!res.is_ok());
REQUIRE(res.is_error());
REQUIRE((res.err() == test::math_error::DIVISION_BY_ZERO));
auto res2 = test::divide(4, 0)
.or_else([](const auto &err) {
switch (err) {
case test::math_error::DIVISION_BY_ZERO:
return utils::failure(std::string("division by zero error"));
default:
return utils::failure(std::string("unknown error"));
}
});
REQUIRE(!res2);
REQUIRE(!res2.is_ok());
REQUIRE(res2.is_error());
const auto e = res2.err();
// REQUIRE(res2.err() == "division by zero error");
res = test::divide(4, 2)
.and_then([](const auto &val) { return test::multiply(val, 5); })
.map([](const auto &val) { return val + 10; });
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 20.0));
auto res_void = test::action_on_greater_42(43);
REQUIRE(res_void);
REQUIRE(res_void.is_ok());
REQUIRE(!res_void.is_error());
res_void = test::action_on_greater_42(41);
REQUIRE(!res_void);
REQUIRE(!res_void.is_ok());
REQUIRE(res_void.is_error());
auto res_float = test::divide(4, 2)
.and_then([](const auto &val) { return test::action_on_greater_42(val); });
REQUIRE(!res_float);
REQUIRE(!res_float.is_ok());
REQUIRE(res_float.is_error());
res_void = test::divide(120, 2)
.and_then([](const auto &val) { return test::action_on_greater_42(val); });
REQUIRE(res_void);
REQUIRE(res_void.is_ok());
REQUIRE(!res_void.is_error());
}
namespace matador::test {
class CustomError {
public:
CustomError( const math_error err, std::string msg)
: error_(err), message_(std::move(msg)) {}
[[nodiscard]] math_error error() const { return error_; }
[[nodiscard]] std::string message() const { return message_; }
private:
math_error error_{};
std::string message_;
};
utils::result<float, CustomError> custom_divide(const float x, const float y) {
utils::result<float, math_error> res = divide(x, y);
if (res.is_ok()) {
return utils::ok(res.value());
}
return utils::failure<CustomError>({res.err(), "ERROR"});
}
}
TEST_CASE("Test result with custom error", "[result][custom]") {
auto res = test::custom_divide(4, 2);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
res = test::custom_divide(4, 0);
REQUIRE(!res.is_ok());
REQUIRE(res.is_error());
const auto err = res.err();
REQUIRE(err.error() == test::math_error::DIVISION_BY_ZERO);
REQUIRE(err.message() == "ERROR");
}
TEST_CASE("Test result with void type", "[result][void]") {
auto res = test::action_on_greater_42(43);
REQUIRE(res.is_ok());
res = test::action_on_greater_42(41);
REQUIRE(res.is_error());
REQUIRE(res.err() == test::math_error::FAILURE);
}
+28
View File
@@ -0,0 +1,28 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/string.hpp"
using namespace matador::utils;
TEST_CASE("Test splitting of string", "[string][split]") {
const std::string str("1,2,3,5,6");
std::vector<std::string> string_vec;
const size_t count = split(str, ',', string_vec);
REQUIRE(count == 5);
REQUIRE(string_vec.size() == 5);
}
TEST_CASE("Test trimming of string", "[string][trim]") {
std::string str(" middle ");
std::string result = trim(str);
REQUIRE(result == "middle");
result = trim(str, "-");
REQUIRE(result == str);
}
+62
View File
@@ -0,0 +1,62 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/utils/version.hpp"
using namespace matador::utils;
TEST_CASE("Test version interface", "[version][interface]") {
version v0;
REQUIRE(v0.major() == 0);
REQUIRE(v0.minor() == 0);
REQUIRE(v0.patch() == 0);
REQUIRE(v0.str() == "0.0.0");
v0.major(1);
v0.minor(2);
v0.patch(3);
REQUIRE(v0.major() == 1);
REQUIRE(v0.minor() == 2);
REQUIRE(v0.patch() == 3);
REQUIRE(v0.str() == "1.2.3");
version v02;
REQUIRE(v02 < v0);
REQUIRE(v02 <= v0);
REQUIRE(v0 > v02);
REQUIRE(v0 >= v02);
REQUIRE(v02 != v0);
version v1 = v0;
REQUIRE(v1 == v0);
version v2(v1);
REQUIRE(v2 == v1);
}
TEST_CASE("Test version parsing", "[version][parse]") {
const auto version_str{"13.67.34"};
const auto v1 = version::from_string(version_str);
REQUIRE(v1.is_ok());
REQUIRE(v1->major() == 13);
REQUIRE(v1->minor() == 67);
REQUIRE(v1->patch() == 34);
REQUIRE(v1->str() == version_str);
const auto v2 = version::from_string("01.02.03");
REQUIRE(v2.is_ok());
REQUIRE(v2->major() == 1);
REQUIRE(v2->minor() == 2);
REQUIRE(v2->patch() == 3);
REQUIRE(v2->str() == "1.2.3");
const auto v3 = version::from_string("a.b.c");
REQUIRE(v3.is_error());
REQUIRE(v3.err().message() == "Invalid version string");
}
-40
View File
@@ -1,40 +0,0 @@
#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/entity.hpp"
#include <string>
namespace matador::test {
struct airplane
{
airplane() = default;
airplane(unsigned long id, std::string b, std::string m)
: id(id)
, brand(std::move(b))
, model(std::move(m)) {}
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
-40
View File
@@ -1,40 +0,0 @@
#ifndef QUERY_AUTHOR_HPP
#define QUERY_AUTHOR_HPP
#include "matador/utils/access.hpp"
#include "matador/sql/entity.hpp"
#include <string>
namespace matador::test {
struct book;
struct author
{
unsigned long id{};
std::string first_name;
std::string last_name;
std::string date_of_birth;
unsigned short year_of_birth{};
bool distinguished{false};
std::vector<matador::sql::entity<book>> books;
template<typename Operator>
void process(Operator &op)
{
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "first_name", first_name, 63);
field::attribute(op, "last_name", last_name, 63);
field::attribute(op, "date_of_birth", date_of_birth, 31);
field::attribute(op, "year_of_birth", year_of_birth);
field::attribute(op, "distinguished", distinguished);
field::has_many(op, books, "author_id", utils::fetch_type::LAZY);
}
};
}
#endif //QUERY_AUTHOR_HPP
-34
View File
@@ -1,34 +0,0 @@
#ifndef QUERY_BOOK_HPP
#define QUERY_BOOK_HPP
#include "matador/sql/entity.hpp"
#include "matador/utils/access.hpp"
#include "matador/utils/foreign_attributes.hpp"
#include <string>
namespace matador::test {
struct author;
struct book
{
unsigned long id{};
matador::sql::entity<author> book_author;
std::string title;
unsigned short published_in{};
template<typename Operator>
void process(Operator &op)
{
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "title", title, 511);
field::belongs_to(op, "author_id", book_author, utils::fetch_type::EAGER);
field::attribute(op, "published_in", published_in);
}
};
}
#endif //QUERY_BOOK_HPP
-25
View File
@@ -1,25 +0,0 @@
#ifndef QUERY_CATEGORY_HPP
#define QUERY_CATEGORY_HPP
#include "matador/utils/access.hpp"
#include <string>
namespace matador::test {
struct category
{
unsigned long id{};
std::string name;
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);
}
};
}
#endif //QUERY_CATEGORY_HPP
+3 -2
View File
@@ -2,6 +2,7 @@
#define QUERY_COORDINATE_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/field_attributes.hpp"
namespace matador::test {
struct coordinate
@@ -13,10 +14,10 @@ struct coordinate
}
namespace matador::utils::access {
namespace matador::access {
template<class Operator>
void attribute(Operator &op, const char *id, test::coordinate &value, const field_attributes &attr = null_attributes) {
void attribute(Operator &op, const char *id, test::coordinate &value, const utils::field_attributes &attr = utils::null_attributes) {
attribute(op, (std::string(id) + "_x").c_str(), value.x, attr);
attribute(op, (std::string(id) + "_y").c_str(), value.y, attr);
attribute(op, (std::string(id) + "_z").c_str(), value.z, attr);
-40
View File
@@ -1,40 +0,0 @@
#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/fetch_type.hpp"
#include "matador/utils/field_attributes.hpp"
#include "matador/sql/entity.hpp"
#include <string>
#include <utility>
namespace matador::test {
struct flight
{
flight() = default;
flight(unsigned long id, const sql::entity<airplane> &plane, std::string name)
: id(id), airplane(plane), pilot_name(std::move(name)) {}
unsigned long id{};
sql::entity<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, utils::fetch_type::EAGER});
field::attribute(op, "pilot_name", pilot_name, 255);
}
};
}
#endif //QUERY_FLIGHT_HPP
+3 -1
View File
@@ -5,6 +5,8 @@
#include "coordinate.hpp"
#include <cstdint>
namespace matador::test {
enum class Color : uint8_t {
@@ -21,7 +23,7 @@ struct location
template < class Operator >
void process(Operator &op)
{
namespace field = matador::utils::access;
namespace field = matador::access;
field::primary_key(op, "id", id);
field::attribute(op, "name", name, 255);
field::attribute(op, "coordinate", coord);
-30
View File
@@ -1,30 +0,0 @@
#ifndef QUERY_OPTIONAL_HPP
#define QUERY_OPTIONAL_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/field_attributes.hpp"
#include <optional>
#include <string>
namespace matador::test {
struct optional
{
unsigned long id{};
std::optional<std::string> name;
std::optional<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_OPTIONAL_HPP
-50
View File
@@ -1,50 +0,0 @@
#ifndef QUERY_ORDER_HPP
#define QUERY_ORDER_HPP
#include "order_details.hpp"
#include "matador/utils/access.hpp"
#include "matador/sql/entity.hpp"
#include <vector>
namespace matador::test {
struct order
{
unsigned long order_id{};
std::string order_date;
std::string required_date;
std::string shipped_date;
unsigned int ship_via{};
unsigned int freight{};
std::string ship_name;
std::string ship_address;
std::string ship_city;
std::string ship_region;
std::string ship_postal_code;
std::string ship_country;
std::vector<sql::entity<order_details>> order_details_;
template<class Operator>
void process(Operator &op) {
namespace field = matador::utils::access;
field::primary_key(op, "order_id", order_id);
field::attribute(op, "order_date", order_date, 255);
field::attribute(op, "required_date", required_date, 255);
field::attribute(op, "shipped_date", shipped_date, 255);
field::attribute(op, "ship_via", ship_via);
field::attribute(op, "freight", freight);
field::attribute(op, "ship_name", ship_name, 255);
field::attribute(op, "ship_address", ship_address, 255);
field::attribute(op, "ship_city", ship_city, 255);
field::attribute(op, "ship_region", ship_region, 255);
field::attribute(op, "ship_postal_code", ship_postal_code, 255);
field::attribute(op, "ship_country", ship_country, 255);
field::has_many(op, order_details_, "order_id", utils::fetch_type::EAGER);
}
};
}
#endif //QUERY_ORDER_HPP
-28
View File
@@ -1,28 +0,0 @@
#ifndef QUERY_ORDER_DETAILS_HPP
#define QUERY_ORDER_DETAILS_HPP
#include "product.hpp"
#include "matador/sql/entity.hpp"
namespace matador::test {
struct order;
struct order_details
{
unsigned long order_details_id;
sql::entity<order> order_;
sql::entity<product> product_;
template<class Operator>
void process(Operator &op) {
namespace field = matador::utils::access;
field::primary_key(op, "order_details_id", order_details_id);
field::belongs_to(op, "order_id", order_, utils::default_foreign_attributes);
field::has_one(op, "product_id", product_, utils::default_foreign_attributes);
}
};
}
#endif //QUERY_ORDER_DETAILS_HPP
-31
View File
@@ -1,31 +0,0 @@
#ifndef QUERY_PERSON_HPP
#define QUERY_PERSON_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/field_attributes.hpp"
#include "matador/utils/types.hpp"
#include <string>
namespace matador::test {
struct person
{
unsigned long id{};
std::string name;
unsigned int age{};
utils::blob image;
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);
field::attribute(op, "image", image);
}
};
}
#endif //QUERY_PERSON_HPP
-46
View File
@@ -1,46 +0,0 @@
#ifndef QUERY_PRODUCT_HPP
#define QUERY_PRODUCT_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/entity.hpp"
#include <string>
namespace matador::test {
struct product
{
std::string product_name;
sql::entity<test::supplier> supplier;
sql::entity<test::category> category;
std::string quantity_per_unit;
unsigned int unit_price;
unsigned int units_in_stock;
unsigned int units_in_order;
unsigned int reorder_level;
bool discontinued;
template<class Operator>
void process(Operator &op) {
namespace field = matador::utils::access;
using namespace matador::utils;
field::primary_key(op, "product_name", product_name, 255);
field::has_one(op, "supplier_id", supplier, utils::cascade_type::ALL);
field::has_one(op, "category_id", category, utils::cascade_type::ALL);
field::attribute(op, "quantity_per_unit", quantity_per_unit, 255);
field::attribute(op, "unit_price", unit_price);
field::attribute(op, "units_in_stock", units_in_stock);
field::attribute(op, "units_in_order", units_in_order);
field::attribute(op, "reorder_level", reorder_level);
field::attribute(op, "discontinued", discontinued);
}
};
}
#endif //QUERY_PRODUCT_HPP
-55
View File
@@ -1,55 +0,0 @@
#ifndef QUERY_RECIPE_HPP
#define QUERY_RECIPE_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/field_attributes.hpp"
#include "matador/utils/foreign_attributes.hpp"
#include "matador/sql/entity.hpp"
#include "matador/sql/has_many_to_many_relation.hpp"
#include <string>
namespace matador::test {
struct recipe;
struct ingredient
{
unsigned long id{};
std::string name;
std::vector<matador::sql::entity<recipe>> recipes;
template<class Operator>
void process(Operator &op) {
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "name", name, 255);
field::has_many_to_many(op, "recipe_ingredients", recipes, "ingredient_id", "recipe_id", utils::fetch_type::EAGER);
}
};
struct recipe
{
unsigned long id{};
std::string name;
std::vector<matador::sql::entity<ingredient>> ingredients;
template<class Operator>
void process(Operator &op) {
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "name", name, 255);
field::has_many_to_many(op, "recipe_ingredients", ingredients, utils::fetch_type::LAZY);
}
};
class recipe_ingredient : public sql::has_many_to_many_relation<recipe, ingredient>
{
public:
recipe_ingredient()
: has_many_to_many_relation("recipe_id", "ingredient_id") {}
};
}
#endif //QUERY_RECIPE_HPP
-68
View File
@@ -1,68 +0,0 @@
#ifndef QUERY_STUDENT_HPP
#define QUERY_STUDENT_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/foreign_attributes.hpp"
#include "matador/sql/entity.hpp"
#include "matador/sql/has_many_to_many_relation.hpp"
#include <utility>
#include <vector>
namespace matador::test {
class course;
struct student
{
unsigned long id{};
std::string name;
std::vector<matador::sql::entity<course>> courses;
student() = default;
explicit student(unsigned long id, std::string name)
: id(id)
, name(std::move(name)) {}
template < class Operator >
void process(Operator &op)
{
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "name", name, 255);
field::has_many_to_many(op, "student_courses", courses, "student_id", "course_id", utils::fetch_type::LAZY);
}
};
struct course
{
unsigned long id{};
std::string title;
std::vector<matador::sql::entity<student>> students;
course() = default;
explicit course(unsigned long id, std::string title)
: id(id)
, title(std::move(title)) {}
template < class Operator >
void process(Operator &op)
{
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "title", title, 255);
field::has_many_to_many(op, "student_courses", students, utils::fetch_type::EAGER);
}
};
class student_course : public sql::has_many_to_many_relation<student, course>
{
public:
student_course()
: has_many_to_many_relation("student_id", "course_id") {}
};
}
#endif //QUERY_STUDENT_HPP
-25
View File
@@ -1,25 +0,0 @@
#ifndef QUERY_SUPPLIER_HPP
#define QUERY_SUPPLIER_HPP
#include "matador/utils/access.hpp"
#include <string>
namespace matador::test {
struct supplier
{
unsigned long id{};
std::string name;
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);
}
};
}
#endif //QUERY_SUPPLIER_HPP
+64
View File
@@ -0,0 +1,64 @@
#ifndef QUERY_TYPES_HPP
#define QUERY_TYPES_HPP
#include "matador/utils/access.hpp"
#include "matador/utils/types.hpp"
namespace matador::test {
struct types
{
enum { CSTR_LEN=255 };
unsigned long id_ = 0;
char char_ = 'c';
short short_ = -127;
int int_ = -65000;
long long_ = -128000;
long long long64_ = -1234567890;
unsigned char unsigned_char_ = 'H';
unsigned short unsigned_short_ = 128;
unsigned int unsigned_int_ = 65000;
unsigned long unsigned_long_ = 128000;
unsigned long long unsigned_long64_ = 1234567890;
float float_ = 3.1415f;
double double_ = 1.1414;
bool bool_ = true;
char cstr_[CSTR_LEN]{};
std::string string_ = "Welt";
std::string varchar_ = "Erde";
matador::date date_;
matador::time time_;
utils::blob binary_{ 1, 2, 3, 4 };
template < class Operator >
void process(Operator &op)
{
namespace field = matador::access;
using namespace matador::utils;
field::primary_key(op, "id", id_);
field::attribute(op, "val_char", char_);
field::attribute(op, "val_float", float_);
field::attribute(op, "val_double", double_);
field::attribute(op, "val_short", short_);
field::attribute(op, "val_int", int_);
field::attribute(op, "val_long", long_);
field::attribute(op, "val_long_long", long64_);
field::attribute(op, "val_unsigned_char", unsigned_char_);
field::attribute(op, "val_unsigned_short", unsigned_short_);
field::attribute(op, "val_unsigned_int", unsigned_int_);
field::attribute(op, "val_unsigned_long", unsigned_long_);
field::attribute(op, "val_unsigned_long_long", unsigned_long64_);
field::attribute(op, "val_bool", bool_);
field::attribute(op, "val_cstr", cstr_, CSTR_LEN);
field::attribute(op, "val_string", string_);
field::attribute(op, "val_varchar", varchar_, 63);
field::attribute(op, "val_date", date_);
field::attribute(op, "val_time", time_);
field::attribute(op, "val_binary", binary_);
}
};
}
#endif // QUERY_TYPES_HPP
+30
View File
@@ -0,0 +1,30 @@
CPMAddPackage("gh:catchorg/Catch2@3.7.1")
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
add_executable(OrmTests
backend/test_backend_service.cpp
backend/test_backend_service.hpp
backend/test_connection.cpp
backend/test_connection.hpp
query/ConditionTests.cpp
query/QueryBuilderTest.cpp
query/QueryFixture.cpp
query/QueryFixture.hpp
sql/ColumnTest.cpp
sql/FieldTest.cpp
backend/test_result_reader.cpp
backend/test_result_reader.hpp
backend/test_statement.cpp
backend/test_statement.hpp
backend/test_parameter_binder.cpp
backend/test_parameter_binder.hpp
query/QueryTest.cpp
)
target_link_libraries(OrmTests matador-orm matador-core Catch2::Catch2WithMain)
target_compile_options(OrmTests PRIVATE -coverage)
target_link_options(OrmTests PRIVATE -coverage)
add_test(NAME OrmTests COMMAND OrmTests)
+31
View File
@@ -0,0 +1,31 @@
#include "test_backend_service.hpp"
#include "test_connection.hpp"
#include "matador/sql/dialect_builder.hpp"
#include <algorithm>
namespace matador::test::orm {
sql::connection_impl *test_backend_service::create(const sql::connection_info &info)
{
return noop_connections_.insert(std::make_unique<test_connection>(info)).first->get();
}
void test_backend_service::destroy(sql::connection_impl *impl)
{
auto it = std::find_if(noop_connections_.begin(), noop_connections_.end(), [impl](const auto &item) {
return impl == item.get();
});
if (it != noop_connections_.end()) {
noop_connections_.erase(it);
}
}
const sql::dialect *test_backend_service::dialect() const
{
static sql::dialect dialect_ = sql::dialect_builder::builder().create().build();
return &dialect_;
}
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef NOOP_BACKEND_SERVICE_HPP
#define NOOP_BACKEND_SERVICE_HPP
#include "matador/sql/backend_provider.hpp"
#include <unordered_set>
namespace matador::test::orm {
class test_backend_service final : public sql::backend_provider::basic_backend_service {
public:
sql::connection_impl *create(const sql::connection_info &info) override;
void destroy(sql::connection_impl *impl) override;
[[nodiscard]] const sql::dialect *dialect() const override;
private:
std::unordered_set<std::unique_ptr<sql::connection_impl>> noop_connections_;
};
}
#endif //NOOP_BACKEND_SERVICE_HPP
+80
View File
@@ -0,0 +1,80 @@
#include "test_connection.hpp"
#include "test_result_reader.hpp"
#include "matador/sql/query_context.hpp"
#include "matador/sql/record.hpp"
#include "matador/sql/internal/query_result_impl.hpp"
#include "matador/sql/interface/statement_impl.hpp"
#include "matador/utils/string.hpp"
#include <string>
#include <memory>
namespace matador::test::orm {
test_connection::test_connection(const sql::connection_info &info)
: connection_impl(info) {}
utils::result<void, utils::error> test_connection::open()
{
is_open_ = true;
return utils::ok<void>();
}
utils::result<void, utils::error> test_connection::close()
{
is_open_ = false;
return utils::ok<void>();
}
utils::result<bool, utils::error> test_connection::is_open() const
{
return utils::ok(is_open_);
}
utils::result<bool, utils::error> test_connection::is_valid() const
{
return is_open();
}
utils::result<utils::version, utils::error> test_connection::client_version() const {
return utils::ok(utils::version{1, 2, 3});
}
utils::result<utils::version, utils::error> test_connection::server_version() const {
return utils::ok(utils::version{3, 2, 1});
}
utils::result<size_t, utils::error> test_connection::execute(const std::string &/*stmt*/)
{
return utils::ok(static_cast<size_t>(4));
}
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> test_connection::fetch(const sql::query_context &context)
{
return utils::ok(std::make_unique<sql::query_result_impl>(std::make_unique<test_result_reader>(), context.prototype, context.prototype.size()));
}
utils::result<std::unique_ptr<sql::statement_impl>, utils::error> test_connection::prepare(const sql::query_context &/*context*/)
{
return utils::ok(std::unique_ptr<sql::statement_impl>{});
}
utils::result<std::vector<sql::column_definition>, utils::error> test_connection::describe(const std::string &/*table*/)
{
return utils::ok(std::vector<sql::column_definition>{});
}
utils::result<bool, utils::error> test_connection::exists(const std::string &/*schema_name*/, const std::string &/*table_name*/)
{
return utils::ok(false);
}
std::string test_connection::to_escaped_string(const utils::blob& value) const
{
return utils::to_string(value);
}
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef QUERY_NOOP_CONNECTION_HPP
#define QUERY_NOOP_CONNECTION_HPP
#include "matador/sql/interface/connection_impl.hpp"
namespace matador::test::orm {
class test_connection final : public sql::connection_impl
{
public:
explicit test_connection(const sql::connection_info &info);
utils::result<void, utils::error> open() override;
utils::result<void, utils::error> close() override;
[[nodiscard]] utils::result<bool, utils::error> is_open() const override;
[[nodiscard]] utils::result<bool, utils::error> is_valid() const override;
[[nodiscard]] utils::result<utils::version, utils::error> client_version() const override;
[[nodiscard]] utils::result<utils::version, utils::error> server_version() const override;
utils::result<size_t, utils::error> execute(const std::string &stmt) override;
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> fetch(const sql::query_context &context) override;
utils::result<std::unique_ptr<sql::statement_impl>, utils::error> prepare(const sql::query_context &context) override;
utils::result<std::vector<sql::column_definition>, utils::error> describe(const std::string &table) override;
utils::result<bool, utils::error> exists(const std::string &schema_name, const std::string &table_name) override;
[[nodiscard]] std::string to_escaped_string( const utils::blob& value ) const override;
private:
bool is_open_{false};
};
}
#endif //QUERY_NOOP_CONNECTION_HPP
@@ -0,0 +1,23 @@
#include "test_parameter_binder.hpp"
namespace matador::test::orm {
void test_parameter_binder::write_value(size_t pos, const int8_t &x) {}
void test_parameter_binder::write_value(size_t pos, const int16_t &x) {}
void test_parameter_binder::write_value(size_t pos, const int32_t &x) {}
void test_parameter_binder::write_value(size_t pos, const int64_t &x) {}
void test_parameter_binder::write_value(size_t pos, const uint8_t &x) {}
void test_parameter_binder::write_value(size_t pos, const uint16_t &x) {}
void test_parameter_binder::write_value(size_t pos, const uint32_t &x) {}
void test_parameter_binder::write_value(size_t pos, const uint64_t &x) {}
void test_parameter_binder::write_value(size_t pos, const bool &x) {}
void test_parameter_binder::write_value(size_t pos, const float &x) {}
void test_parameter_binder::write_value(size_t pos, const double &x) {}
void test_parameter_binder::write_value(size_t pos, const time &x) {}
void test_parameter_binder::write_value(size_t pos, const date &x) {}
void test_parameter_binder::write_value(size_t pos, const char *x) {}
void test_parameter_binder::write_value(size_t pos, const char *x, size_t size) {}
void test_parameter_binder::write_value(size_t pos, const std::string &x) {}
void test_parameter_binder::write_value(size_t pos, const std::string &x, size_t size) {}
void test_parameter_binder::write_value(size_t pos, const utils::blob &x) {}
void test_parameter_binder::write_value(size_t pos, const utils::value &x, size_t size) {}
}
@@ -0,0 +1,33 @@
#ifndef TEST_PARAMETER_BINDER_HPP
#define TEST_PARAMETER_BINDER_HPP
#include "matador/utils/attribute_writer.hpp"
namespace matador::test::orm {
class test_parameter_binder final : public utils::attribute_writer {
public:
void write_value(size_t pos, const int8_t &x) override;
void write_value(size_t pos, const int16_t &x) override;
void write_value(size_t pos, const int32_t &x) override;
void write_value(size_t pos, const int64_t &x) override;
void write_value(size_t pos, const uint8_t &x) override;
void write_value(size_t pos, const uint16_t &x) override;
void write_value(size_t pos, const uint32_t &x) override;
void write_value(size_t pos, const uint64_t &x) override;
void write_value(size_t pos, const bool &x) override;
void write_value(size_t pos, const float &x) override;
void write_value(size_t pos, const double &x) override;
void write_value(size_t pos, const time &x) override;
void write_value(size_t pos, const date &x) override;
void write_value(size_t pos, const char *x) override;
void write_value(size_t pos, const char *x, size_t size) override;
void write_value(size_t pos, const std::string &x) override;
void write_value(size_t pos, const std::string &x, size_t size) override;
void write_value(size_t pos, const utils::blob &x) override;
void write_value(size_t pos, const utils::value &x, size_t size) override;
};
}
#endif //TEST_PARAMETER_BINDER_HPP
+92
View File
@@ -0,0 +1,92 @@
#include "test_result_reader.hpp"
#include "matador/utils/value.hpp"
namespace matador::test::orm {
size_t test_result_reader::column_count() const {
return 10;
}
const char *test_result_reader::column(size_t index) const {
return "";
}
utils::result<bool, utils::error> test_result_reader::fetch() { return utils::ok(rows_-- > 0); }
size_t test_result_reader::start_column_index() const {
return 0;
}
void test_result_reader::read_value(const char *id, const size_t index, int8_t &value) {
value = -8;
}
void test_result_reader::read_value(const char *id, const size_t index, int16_t &value) {
value = -16;
}
void test_result_reader::read_value(const char *id, const size_t index, int32_t &value) {
value = -32;
}
void test_result_reader::read_value(const char *id, const size_t index, int64_t &value) {
value = -64;
}
void test_result_reader::read_value(const char *id, const size_t index, uint8_t &value) {
value = 8;
}
void test_result_reader::read_value(const char *id, const size_t index, uint16_t &value) {
value = 16;
}
void test_result_reader::read_value(const char *id, const size_t index, uint32_t &value) {
value = 32;
}
void test_result_reader::read_value(const char *id, const size_t index, uint64_t &value) {
value = 64;
}
void test_result_reader::read_value(const char *id, const size_t index, bool &value) {
value = true;
}
void test_result_reader::read_value(const char *id, const size_t index, float &value) {
value = 3.141572f;
}
void test_result_reader::read_value(const char *id, const size_t index, double &value) {
value = 2.14159265358979323846;
}
void test_result_reader::read_value(const char *id, const size_t index, matador::time &value) {
}
void test_result_reader::read_value(const char *id, const size_t index, matador::date &value) {
}
void test_result_reader::read_value(const char *id, const size_t index, char *value, const size_t size) {
}
void test_result_reader::read_value(const char *id, const size_t index, std::string &value) {
value = "Lorem ipsum";
}
void test_result_reader::read_value(const char *id, const size_t index, std::string &value, const size_t size) {
value = "Hello world";
}
void test_result_reader::read_value(const char *id, const size_t index, utils::blob &value) {
value = {'b', 'l', 'o', 'b'};
}
void test_result_reader::read_value(const char *id, const size_t index, utils::value &val, const size_t size) {
val = "value";
}
utils::attribute_reader &test_result_reader::result_binder() {
return query_result_reader::result_binder();
}
} // namespace matador::test::orm
+43
View File
@@ -0,0 +1,43 @@
#ifndef TEST_RESULT_READER_HPP
#define TEST_RESULT_READER_HPP
#include "matador/sql/interface/query_result_reader.hpp"
namespace matador::test::orm {
class test_result_reader final : public sql::query_result_reader {
public:
[[nodiscard]] size_t column_count() const override;
[[nodiscard]] const char *column(size_t index) const override;
[[nodiscard]] utils::result<bool, utils::error> fetch() override;
[[nodiscard]] size_t start_column_index() const override;
void read_value(const char *id, size_t index, int8_t &value) override;
void read_value(const char *id, size_t index, int16_t &value) override;
void read_value(const char *id, size_t index, int32_t &value) override;
void read_value(const char *id, size_t index, int64_t &value) override;
void read_value(const char *id, size_t index, uint8_t &value) override;
void read_value(const char *id, size_t index, uint16_t &value) override;
void read_value(const char *id, size_t index, uint32_t &value) override;
void read_value(const char *id, size_t index, uint64_t &value) override;
void read_value(const char *id, size_t index, bool &value) override;
void read_value(const char *id, size_t index, float &value) override;
void read_value(const char *id, size_t index, double &value) override;
void read_value(const char *id, size_t index, matador::time &value) override;
void read_value(const char *id, size_t index, matador::date &value) override;
void read_value(const char *id, size_t index, char *value, size_t size) override;
void read_value(const char *id, size_t index, std::string &value) override;
void read_value(const char *id, size_t index, std::string &value, size_t size) override;
void read_value(const char *id, size_t index, utils::blob &value) override;
void read_value(const char *id, size_t index, utils::value &val, size_t size) override;
protected:
attribute_reader &result_binder() override;
private:
uint8_t rows_{5};
};
}
#endif //TEST_RESULT_READER_HPP
+22
View File
@@ -0,0 +1,22 @@
#include "test_statement.hpp"
#include "test_result_reader.hpp"
namespace matador::test::orm {
test_statement::test_statement(const sql::query_context &query)
: statement_impl(query) {}
utils::result<size_t, utils::error> test_statement::execute() {
return utils::ok(static_cast<size_t>(8));
}
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> test_statement::fetch() {
return utils::ok(std::make_unique<sql::query_result_impl>(std::make_unique<test_result_reader>(), query_.prototype, query_.prototype.size()));
}
void test_statement::reset() {}
utils::attribute_writer &test_statement::binder() {
return binder_;
}
} // namespace matador::test::orm
+26
View File
@@ -0,0 +1,26 @@
#ifndef TEST_STATEMENT_HPP
#define TEST_STATEMENT_HPP
#include "test_parameter_binder.hpp"
#include "matador/sql/interface/statement_impl.hpp"
namespace matador::test::orm {
class test_statement final : public sql::statement_impl {
public:
explicit test_statement(const sql::query_context &query);
utils::result<size_t, utils::error> execute() override;
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> fetch() override;
void reset() override;
protected:
utils::attribute_writer &binder() override;
private:
test_parameter_binder binder_;
};
}
#endif //TEST_STATEMENT_HPP
+112
View File
@@ -0,0 +1,112 @@
#include <catch2/catch_test_macros.hpp>
#include "matador/query/condition.hpp"
#include "matador/sql/dialect_builder.hpp"
using namespace matador::sql;
using namespace matador::query;
class ConditionFixture {
protected:
dialect dlc = dialect_builder::builder()
.create()
.build();
query_context ctx;
};
TEST_CASE_METHOD(ConditionFixture, "Test column user defined literal", "[column][literal]") {
const auto col = "name"_col;
REQUIRE(col.name == "name");
}
TEST_CASE_METHOD(ConditionFixture, "Test logical condition", "[condition][logical]") {
const auto name_col = "name"_col;
REQUIRE(name_col.name == "name");
auto cond1 = name_col != "george";
auto clause = cond1.evaluate(dlc, ctx);
REQUIRE(clause == "\"name\" <> 'george'");
auto cond2 = "age"_col != 9;
clause = cond2.evaluate(dlc, ctx);
REQUIRE(clause == "\"age\" <> 9");
}
TEST_CASE_METHOD(ConditionFixture, "Test and condition", "[condition][bin][and]") {
const auto name_col = "name"_col;
REQUIRE(name_col.name == "name");
const auto cond = name_col == "Hans" && name_col == "Dieter";
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "(\"name\" = 'Hans' AND \"name\" = 'Dieter')");
}
TEST_CASE_METHOD(ConditionFixture, "Test or condition", "[condition][bin][or]") {
const auto name_col = "name"_col;
REQUIRE(name_col.name == "name");
const auto cond = name_col == "Hans" || name_col == "Dieter";
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "\"name\" = 'Hans' OR \"name\" = 'Dieter'");
}
TEST_CASE_METHOD(ConditionFixture, "Test not condition", "[condition][not]") {
const auto name_col = "name"_col;
REQUIRE(name_col.name == "name");
const auto cond = !(name_col != "Hans");
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "NOT (\"name\" <> 'Hans')");
}
TEST_CASE_METHOD(ConditionFixture, "Test in condition", "[condition][in]") {
const auto age_col = "age"_col;
REQUIRE(age_col.name == "age");
auto cond = age_col != 7 && in(age_col, {7,5,5,8});
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "(\"age\" <> 7 AND \"age\" IN (7, 5, 5, 8))");
cond = age_col != 7 && in(age_col, {7});
clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "(\"age\" <> 7 AND \"age\" IN (7))");
}
TEST_CASE_METHOD(ConditionFixture, "Test in query condition", "[condition][in query]") {
const auto age_col = "age"_col;
const auto name_col = "name"_col;
query_context sub_ctx;
sub_ctx.sql = R"(SELECT "name" FROM "test")";
auto cond = age_col != 7 && in(name_col, std::move(sub_ctx));
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "(\"age\" <> 7 AND \"name\" IN (SELECT \"name\" FROM \"test\"))");
}
TEST_CASE_METHOD(ConditionFixture, "Test between condition", "[condition][between]") {
const auto age_col = "age"_col;
auto cond = age_col != 7 && between(age_col, 21, 30);
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "(\"age\" <> 7 AND \"age\" BETWEEN 21 AND 30)");
}
TEST_CASE_METHOD(ConditionFixture, "Test like condition", "[condition][like]") {
const auto name_col = "name"_col;
auto cond = like(name_col, "%er%");
auto clause = cond.evaluate(dlc, ctx);
REQUIRE(clause == "\"name\" LIKE '%er%'");
}
+235
View File
@@ -0,0 +1,235 @@
#include <catch2/catch_test_macros.hpp>
#include "QueryFixture.hpp"
#include <matador/query/condition.hpp>
#include <matador/query/query.hpp>
#include <matador/sql/column_definition.hpp>
#include <matador/sql/connection.hpp>
#include <matador/sql/table.hpp>
#include <matador/utils/placeholder.hpp>
// #include "models/author.hpp"
// #include "models/book.hpp"
using namespace matador::test;
using namespace matador::sql;
using namespace matador::query;
using namespace matador::utils;
TEST_CASE_METHOD(QueryFixture, "Test create table sql statement string", "[query]") {
auto result = query::create()
.table({"person"}, {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("age")
}).str(*db);
REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##");
result = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", {255, constraints::UNIQUE}, null_option::NOT_NULL),
make_column<unsigned short>("age"),
make_fk_column<unsigned long>("address", "address", "id")
}).str(*db);
REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##");
}
TEST_CASE_METHOD(QueryFixture, "Test drop table sql statement string", "[query]") {
const auto result = query::drop()
.table("person")
.str(*db);
REQUIRE(result == R"(DROP TABLE "person")");
}
TEST_CASE_METHOD(QueryFixture, "Test select sql statement string", "[query]") {
const auto result = query::select({"id", "name", "age"})
.from("person")
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person")");
}
TEST_CASE_METHOD(QueryFixture, "Test insert sql statement string", "[query]") {
const auto result = query::insert()
.into("person", {
"id", "name", "age"
})
.values({7UL, "george", 65U})
.str(*db);
REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))");
}
TEST_CASE_METHOD(QueryFixture, "Test update sql statement string", "[query]") {
auto result = query::update("person")
.set({
{"id", 7UL},
{"name", "george"},
{"age", 65U}
})
.str(*db);
REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)");
result = query::update("person")
.set({
{"id", 7UL},
{"name", "george"},
{"age", 65U}
})
.where("id"_col > 9)
.order_by("id").asc()
.limit(3)
.offset(2)
.str(*db);
REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "id" > 9 ORDER BY "id" ASC LIMIT 3 OFFSET 2)");
}
TEST_CASE_METHOD(QueryFixture, "Test update limit sql statement", "[query][update][limit]") {
const auto result = query::update("person")
.set({{"id", 7UL}, {"name", "george"}, {"age", 65U}})
.where("name"_col == "george")
.order_by("id"_col).asc()
.limit(2)
.str(*db);
REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)");
}
TEST_CASE_METHOD(QueryFixture, "Test delete sql statement string", "[query]") {
const auto result = query::remove().from("person").str(*db);
REQUIRE(result == R"(DELETE FROM "person")");
}
TEST_CASE_METHOD(QueryFixture, "Test delete limit sql statement", "[query][delete][limit]") {
const auto result = query::remove()
.from("person")
.where("name"_col == "george")
.order_by("id"_col).asc()
.limit(2)
.str(*db);
REQUIRE(result == R"(DELETE FROM "person" WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)");
}
TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with where clause", "[query]") {
auto result = query::select({"id", "name", "age"})
.from("person")
.where("id"_col == 8 && "age"_col > 50)
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))");
result = query::select({"id", "name", "age"})
.from("person")
.where("id"_col == _ && "age"_col > 50)
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))");
}
TEST_CASE_METHOD(QueryFixture, "Test insert sql statement with placeholder", "[query]") {
auto result = query::insert()
.into("person", {"id", "name", "age"})
.values({_, _, _})
.str(*db);
REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (?, ?, ?))");
result = query::insert()
.into("person", {"id", "name", "age"})
.values({9, "george", _})
.str(*db);
REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (9, 'george', ?))");
}
TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with order by", "[query]") {
const auto result = query::select({"id", "name", "age"})
.from("person")
.order_by("name"_col).asc()
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)");
}
TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with group by", "[query]") {
const auto result = query::select({"id", "name", "age"})
.from("person")
.group_by("age"_col)
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")");
}
TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with offset and limit", "[query]") {
const auto result = query::select({"id", "name", "age"})
.from("person")
.order_by("id"_col).asc()
.limit(20)
.offset(10)
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "id" ASC LIMIT 20 OFFSET 10)");
}
TEST_CASE_METHOD(QueryFixture, "Test create, insert and select a blob column", "[query][blob]") {
auto result = query::create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<blob>("data")
})
.str(*db);
REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "data" BLOB NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##");
result = query::insert()
.into("person", {"id", "name", "data"})
.values({7UL, "george", blob{1, 'A', 3, 4}})
.str(*db);
REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "data") VALUES (7, 'george', X'01410304'))");
result = query::select({"id", "name", "data"})
.from("person")
.str(*db);
REQUIRE(result == R"(SELECT "id", "name", "data" FROM "person")");
}
TEST_CASE_METHOD(QueryFixture, "Test select statement with join_left", "[query][statement][join_left]") {
const auto result = query::select({"f.id", "ap.brand", "f.pilot_name"})
.from({"flight", "f"})
.join_left({"airplane", "ap"})
.on("f.airplane_id"_col == "ap.id"_col)
.str(*db);
REQUIRE(result == R"(SELECT "f"."id", "ap"."brand", "f"."pilot_name" FROM "flight" "f" LEFT JOIN "airplane" "ap" ON "f"."airplane_id" = "ap"."id")");
}
// TEST_CASE_METHOD(QueryFixture, "Select statement with aliased columns", "[query][select][alias]") {
// using namespace matador::test;
// connection noop("noop://noop.db");
// schema scm("noop");
// scm.attach<author>("authors");
// scm.attach<book>("books");
//
//
// const auto result = query::select<author>(scm)
// .from("authors"_tab.as("T01"))
// .str(*db);
//
// const auto expected_sql = R"(SELECT "T01"."id", "T01"."first_name", "T01"."last_name", "T01"."date_of_birth", "T01"."year_of_birth", "T01"."distinguished" FROM "authors" "T01")";
//
// REQUIRE(result == expected_sql);
// }
+15
View File
@@ -0,0 +1,15 @@
#include "QueryFixture.hpp"
#include "../backend/test_backend_service.hpp"
#include "matador/sql/interface/connection_impl.hpp"
namespace matador::test {
QueryFixture::QueryFixture() {
sql::backend_provider::instance().register_backend("noop", std::make_unique<orm::test_backend_service>());
db = std::make_unique<sql::connection>("noop://noop.db");
}
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef QUERY_FIXTURE_HPP
#define QUERY_FIXTURE_HPP
#include "matador/sql/connection.hpp"
#include <memory>
namespace matador::test {
class QueryFixture {
public:
QueryFixture();
~QueryFixture() = default;
protected:
std::unique_ptr<sql::connection> db;
};
}
#endif //QUERY_FIXTURE_HPP
+36
View File
@@ -0,0 +1,36 @@
#include <catch2/catch_test_macros.hpp>
#include <matador/query/query.hpp>
#include "QueryFixture.hpp"
using namespace matador::test;
using namespace matador::query;
TEST_CASE_METHOD(QueryFixture, "Test simple query", "[query][fetch]") {
auto result = query::select({"id", "name", "age"})
.from("person")
.fetch_all(*db);
REQUIRE(result.is_ok());
for (const auto& row : *result) {
REQUIRE(row.size() == 3);
}
auto single = query::select({"id", "name", "age"})
.from("person")
.fetch_one(*db);
REQUIRE(single.is_ok());
REQUIRE(single.value().has_value());
REQUIRE(single.value().value().size() == 3);
auto val = query::select({"id", "name", "age"})
.from("person")
.fetch_value<int>(*db);
REQUIRE(val.is_ok());
REQUIRE(val.value().has_value());
// REQUIRE(val.value().value() == -1);
}
@@ -3,35 +3,36 @@
#include "matador/sql/column_definition.hpp"
using namespace matador::sql;
using namespace matador::utils;
TEST_CASE("Create empty column", "[column]") {
TEST_CASE("Test create empty column", "[column]") {
column_definition c("name");
REQUIRE(c.name() == "name");
REQUIRE(c.index() == -1);
REQUIRE(c.type() == data_type_t::type_unknown);
REQUIRE(c.type() == basic_type::type_null);
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.type() == basic_type::type_varchar);
REQUIRE(c.as<std::string>() == "george");
c.set(7);
REQUIRE(c.type() == data_type_t::type_int);
REQUIRE(c.type() == basic_type::type_int32);
REQUIRE(c.as<std::string>() == "7");
REQUIRE(c.as<long>() == 7);
REQUIRE(c.str() == "7");
}
TEST_CASE("Copy and move column", "[column]") {
TEST_CASE("Test copy and move column", "[column]") {
column_definition c("name");
c.set(std::string{"george"}, 255);
REQUIRE(c.name() == "name");
REQUIRE(c.index() == -1);
REQUIRE(c.ref_table().empty());
REQUIRE(c.ref_column().empty());
REQUIRE(c.type() == data_type_t::type_varchar);
REQUIRE(c.type() == basic_type::type_varchar);
REQUIRE(c.as<std::string>() == "george");
REQUIRE(c.attributes().size() == 255);
@@ -40,7 +41,7 @@ TEST_CASE("Copy and move column", "[column]") {
REQUIRE(c2.index() == -1);
REQUIRE(c2.ref_table().empty());
REQUIRE(c2.ref_column().empty());
REQUIRE(c2.type() == data_type_t::type_varchar);
REQUIRE(c2.type() == basic_type::type_varchar);
REQUIRE(c2.as<std::string>() == "george");
REQUIRE(c2.attributes().size() == 255);
@@ -49,7 +50,7 @@ TEST_CASE("Copy and move column", "[column]") {
REQUIRE(c3.index() == -1);
REQUIRE(c3.ref_table().empty());
REQUIRE(c3.ref_column().empty());
REQUIRE(c3.type() == data_type_t::type_varchar);
REQUIRE(c3.type() == basic_type::type_varchar);
REQUIRE(c3.as<std::string>() == "george");
REQUIRE(c3.attributes().size() == 255);
@@ -57,7 +58,7 @@ TEST_CASE("Copy and move column", "[column]") {
REQUIRE(c2.index() == -1);
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.type() == basic_type::type_null);
// REQUIRE(!c2.as<std::string>().has_value());
REQUIRE(c2.attributes().size() == 255);
}
@@ -4,12 +4,11 @@
using namespace matador;
TEST_CASE("Field test", "[field]") {
TEST_CASE("Test field", "[field]") {
sql::field f("name");
REQUIRE(f.name() == "name");
REQUIRE(f.index() == -1);
REQUIRE(!f.is_unknown());
REQUIRE(f.is_null());
REQUIRE(!f.is_integer());
REQUIRE(!f.is_floating_point());
@@ -51,5 +50,5 @@ TEST_CASE("Field test", "[field]") {
REQUIRE(blob_val.has_value());
REQUIRE(blob_val.value() == utils::blob{ 7,8,6,5,4,3 });
REQUIRE_THROWS_AS(f.as<std::string>(), std::logic_error);
REQUIRE(!f.as<std::string>().has_value());
}
-26
View File
@@ -1,26 +0,0 @@
#include "auto_reset_event.hpp"
namespace matador::test::utils {
auto_reset_event::auto_reset_event() : state(false) {}
void auto_reset_event::wait_one()
{
std::unique_lock<std::mutex> lock(sync);
underlying.wait(lock, [this](){return state.load();});
state = false;
}
void auto_reset_event::set()
{
std::unique_lock<std::mutex> lock(sync);
state = true;
underlying.notify_one();
}
void auto_reset_event::reset()
{
std::unique_lock<std::mutex> lock(sync);
state = false;
}
}
-29
View File
@@ -1,29 +0,0 @@
#ifndef QUERY_AUTO_RESET_EVENT_HPP
#define QUERY_AUTO_RESET_EVENT_HPP
#include <atomic>
#include <condition_variable>
#include <mutex>
namespace matador::test::utils {
class auto_reset_event
{
public:
auto_reset_event();
auto_reset_event(const auto_reset_event& other) = delete;
void wait_one();
void set();
void reset();
private:
std::condition_variable underlying;
std::mutex sync;
std::atomic<bool> state;
};
}
#endif //QUERY_AUTO_RESET_EVENT_HPP