From 47848ff6749873ce125085e37fa0a22f410ed542 Mon Sep 17 00:00:00 2001 From: Sascha Kuehl Date: Mon, 29 Jan 2024 07:22:28 +0100 Subject: [PATCH] backend tests progress --- backends/mysql/CMakeLists.txt | 2 + backends/mysql/test/CMakeLists.txt | 42 ++++ backends/mysql/test/Connection.hpp.in | 8 + backends/postgres/src/postgres_connection.cpp | 5 +- backends/postgres/src/postgres_dialect.cpp | 2 +- backends/postgres/test/CMakeLists.txt | 20 +- backends/postgres/test/Connection.hpp.in | 2 - backends/sqlite/CMakeLists.txt | 2 + backends/sqlite/test/CMakeLists.txt | 42 ++++ backends/sqlite/test/Connection.hpp.in | 8 + backends/tests/ConnectionTest.cpp | 19 ++ backends/tests/QueryTest.cpp | 12 +- backends/tests/SessionRecordTest.cpp | 3 + backends/tests/SessionTest.cpp | 181 ++++++++++++++++++ include/matador/sql/connection_pool.hpp | 7 +- src/sql/query_result_reader.cpp | 3 +- test/CMakeLists.txt | 6 +- test/ConnectionPoolTest.cpp | 7 +- test/Databases.hpp | 8 +- test/DummyConnection.cpp | 28 +++ test/DummyConnection.hpp | 30 +++ 21 files changed, 410 insertions(+), 27 deletions(-) create mode 100644 backends/mysql/test/CMakeLists.txt create mode 100644 backends/mysql/test/Connection.hpp.in create mode 100644 backends/sqlite/test/CMakeLists.txt create mode 100644 backends/sqlite/test/Connection.hpp.in create mode 100644 backends/tests/ConnectionTest.cpp create mode 100644 backends/tests/SessionRecordTest.cpp create mode 100644 backends/tests/SessionTest.cpp create mode 100644 test/DummyConnection.cpp create mode 100644 test/DummyConnection.hpp diff --git a/backends/mysql/CMakeLists.txt b/backends/mysql/CMakeLists.txt index 10d63ba..57cf66c 100644 --- a/backends/mysql/CMakeLists.txt +++ b/backends/mysql/CMakeLists.txt @@ -20,6 +20,8 @@ set(SOURCES set(LIBRARY_TARGET matador-mysql) +add_subdirectory(test) + add_library(${LIBRARY_TARGET} MODULE ${SOURCES} ${HEADER}) set_target_properties(${LIBRARY_TARGET} diff --git a/backends/mysql/test/CMakeLists.txt b/backends/mysql/test/CMakeLists.txt new file mode 100644 index 0000000..378f04a --- /dev/null +++ b/backends/mysql/test/CMakeLists.txt @@ -0,0 +1,42 @@ +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.4.0 # or a later release +) + +FetchContent_MakeAvailable(Catch2) + +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +include(CTest) +include(Catch) + +set(MYSQL_CONNECTION_STRING "mysql://test:test123!@127.0.0.1:3306/testdb") + +configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/mysql/test/connection.hpp @ONLY IMMEDIATE) + +message(STATUS "mysql connection string: ${MYSQL_CONNECTION_STRING}") + +set(TEST_SOURCES + ../../tests/QueryTest.cpp + ../../tests/SessionTest.cpp + ../../tests/ConnectionTest.cpp + ../../tests/SessionRecordTest.cpp) + +set(LIBRARY_TEST_TARGET mysql_tests) + +add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES}) + +target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE + Catch2::Catch2WithMain + matador + ${CMAKE_DL_LIBS} + ${MySQL_LIBRARY}) + +target_include_directories(${LIBRARY_TEST_TARGET} + PUBLIC $/include + PRIVATE $/test + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (MySQL)") diff --git a/backends/mysql/test/Connection.hpp.in b/backends/mysql/test/Connection.hpp.in new file mode 100644 index 0000000..1d952f6 --- /dev/null +++ b/backends/mysql/test/Connection.hpp.in @@ -0,0 +1,8 @@ +#ifndef MYSQL_CONNECTION_HPP +#define MYSQL_CONNECTION_HPP + +namespace matador::test::connection { + const char* const dns = "@MYSQL_CONNECTION_STRING@"; +} + +#endif /*SQLITE_CONNECTION_HPP*/ \ No newline at end of file diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index 63e8972..1ebb321 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -25,9 +25,10 @@ void postgres_connection::open() conn_ = PQconnectdb(connection.c_str()); if (PQstatus(conn_) == CONNECTION_BAD) { - const auto msg = PQerrorMessage(conn_); + const std::string msg = PQerrorMessage(conn_); PQfinish(conn_); - throw_postgres_error(msg, "postgres"); + conn_ = nullptr; + throw_postgres_error(msg.c_str(), "postgres"); } } diff --git a/backends/postgres/src/postgres_dialect.cpp b/backends/postgres/src/postgres_dialect.cpp index 4520e71..affc1d4 100644 --- a/backends/postgres/src/postgres_dialect.cpp +++ b/backends/postgres/src/postgres_dialect.cpp @@ -11,7 +11,7 @@ return "$" + std::to_string(index); }) .with_token_replace_map({ - {dialect::token_t::BEGIN_BINARY_DATA, "E'\\\\"} + {dialect::token_t::BEGIN_BINARY_DATA, "E'\\"} }) .with_data_type_replace_map({ {data_type_t::type_blob, "BYTEA"} diff --git a/backends/postgres/test/CMakeLists.txt b/backends/postgres/test/CMakeLists.txt index 31414ef..827c2c0 100644 --- a/backends/postgres/test/CMakeLists.txt +++ b/backends/postgres/test/CMakeLists.txt @@ -8,14 +8,21 @@ FetchContent_Declare( FetchContent_MakeAvailable(Catch2) -SET(POSTGRES_CONNECTION_STRING "postgresql://test:test123@127.0.0.1/matador_test" CACHE STRING "postgresql connection string") +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +include(CTest) +include(Catch) -CONFIGURE_FILE(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE) +set(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:15432/test") -MESSAGE(STATUS "postgresql connection string: ${POSTGRES_CONNECTION_STRING}") -MESSAGE(STATUS "current binary dir: ${CMAKE_CURRENT_BINARY_DIR}") +configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE) -set(TEST_SOURCES ../../tests/QueryTest.cpp) +message(STATUS "postgresql connection string: ${POSTGRES_CONNECTION_STRING}") + +set(TEST_SOURCES + ../../tests/QueryTest.cpp + ../../tests/SessionTest.cpp + ../../tests/ConnectionTest.cpp + ../../tests/SessionRecordTest.cpp) set(LIBRARY_TEST_TARGET postgres_tests) @@ -29,6 +36,7 @@ target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE target_include_directories(${LIBRARY_TEST_TARGET} PUBLIC $/include + PRIVATE $/test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -add_test(NAME matador_postgres_tests COMMAND ${LIBRARY_TEST_TARGET}) +catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (PostgreSQL)") diff --git a/backends/postgres/test/Connection.hpp.in b/backends/postgres/test/Connection.hpp.in index 7e491c8..269e02f 100644 --- a/backends/postgres/test/Connection.hpp.in +++ b/backends/postgres/test/Connection.hpp.in @@ -1,8 +1,6 @@ #ifndef POSTGRES_CONNECTION_HPP #define POSTGRES_CONNECTION_HPP -#define MATADOR_DB_TYPE "Postgres" - namespace matador::test::connection { const char* const dns = "@POSTGRES_CONNECTION_STRING@"; } diff --git a/backends/sqlite/CMakeLists.txt b/backends/sqlite/CMakeLists.txt index a061a85..d1faf1e 100644 --- a/backends/sqlite/CMakeLists.txt +++ b/backends/sqlite/CMakeLists.txt @@ -20,6 +20,8 @@ set(SOURCES set(LIBRARY_TARGET matador-sqlite) +add_subdirectory(test) + add_library(${LIBRARY_TARGET} MODULE ${SOURCES} ${HEADER}) set_target_properties(${LIBRARY_TARGET} diff --git a/backends/sqlite/test/CMakeLists.txt b/backends/sqlite/test/CMakeLists.txt new file mode 100644 index 0000000..3c9a999 --- /dev/null +++ b/backends/sqlite/test/CMakeLists.txt @@ -0,0 +1,42 @@ +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.4.0 # or a later release +) + +FetchContent_MakeAvailable(Catch2) + +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +include(CTest) +include(Catch) + +set(SQLITE_CONNECTION_STRING "sqlite://test.db") + +configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/sqlite/test/connection.hpp @ONLY IMMEDIATE) + +message(STATUS "sqlite connection string: ${SQLITE_CONNECTION_STRING}") + +set(TEST_SOURCES + ../../tests/QueryTest.cpp + ../../tests/SessionTest.cpp + ../../tests/ConnectionTest.cpp + ../../tests/SessionRecordTest.cpp) + +set(LIBRARY_TEST_TARGET sqlite_tests) + +add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES}) + +target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE + Catch2::Catch2WithMain + matador + ${CMAKE_DL_LIBS} + ${SQLite3_LIBRARY}) + +target_include_directories(${LIBRARY_TEST_TARGET} + PUBLIC $/include + PRIVATE $/test + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (SQLite3)") diff --git a/backends/sqlite/test/Connection.hpp.in b/backends/sqlite/test/Connection.hpp.in new file mode 100644 index 0000000..577ac3e --- /dev/null +++ b/backends/sqlite/test/Connection.hpp.in @@ -0,0 +1,8 @@ +#ifndef SQLITE_CONNECTION_HPP +#define SQLITE_CONNECTION_HPP + +namespace matador::test::connection { + const char* const dns = "@SQLITE_CONNECTION_STRING@"; +} + +#endif /*SQLITE_CONNECTION_HPP*/ \ No newline at end of file diff --git a/backends/tests/ConnectionTest.cpp b/backends/tests/ConnectionTest.cpp new file mode 100644 index 0000000..bfc0c58 --- /dev/null +++ b/backends/tests/ConnectionTest.cpp @@ -0,0 +1,19 @@ +#include "catch2/catch_test_macros.hpp" + +#include "matador/sql/connection.hpp" + +#include "connection.hpp" + +using namespace matador::sql; + +TEST_CASE("Create connection test", "[connection]") { + + connection c(matador::test::connection::dns); + REQUIRE(!c.is_open()); + + c.open(); + REQUIRE(c.is_open()); + + c.close(); + REQUIRE(!c.is_open()); +} diff --git a/backends/tests/QueryTest.cpp b/backends/tests/QueryTest.cpp index 251bb14..239d635 100644 --- a/backends/tests/QueryTest.cpp +++ b/backends/tests/QueryTest.cpp @@ -2,6 +2,14 @@ #include -TEST_CASE(MATADOR_DB_TYPE " - Query test", "[query]") { - REQUIRE(std::string(matador::test::connection::dns) == "hallo welt"); +TEST_CASE("Query test", "[query]") { + SECTION("Create") { + REQUIRE(true); + } + SECTION("Insert") { + REQUIRE(true); + } + SECTION("Select") { + REQUIRE(true); + } } diff --git a/backends/tests/SessionRecordTest.cpp b/backends/tests/SessionRecordTest.cpp new file mode 100644 index 0000000..b0151aa --- /dev/null +++ b/backends/tests/SessionRecordTest.cpp @@ -0,0 +1,3 @@ +// +// Created by sascha on 28.01.24. +// diff --git a/backends/tests/SessionTest.cpp b/backends/tests/SessionTest.cpp new file mode 100644 index 0000000..a4acbc1 --- /dev/null +++ b/backends/tests/SessionTest.cpp @@ -0,0 +1,181 @@ +#include "catch2/catch_test_macros.hpp" + +#include "matador/sql/column.hpp" +#include "matador/sql/condition.hpp" +#include "matador/sql/session.hpp" + +#include "connection.hpp" + +#include "models/airplane.hpp" +#include "models/flight.hpp" +#include "models/person.hpp" + +using namespace matador::sql; +using namespace matador::test; + +class SessionFixture +{ +public: + SessionFixture() + : pool(matador::test::connection::dns, 4), ses(pool) + {} + ~SessionFixture() { + drop_table_if_exists("flight"); + drop_table_if_exists("airplane"); + drop_table_if_exists("person"); + } + +protected: + matador::sql::connection_pool pool; + matador::sql::session ses; + +private: + void drop_table_if_exists(const std::string &table_name) { + if (ses.table_exists(table_name)) { + ses.drop().table(table_name).execute(); + } + } +}; + +TEST_CASE_METHOD(SessionFixture, " Create table with foreign key relation", "[session]") { + ses.create() + .table("airplane") + .execute(); + + REQUIRE(ses.table_exists("airplane")); + + ses.create() + .table("flight") + .execute(); + + REQUIRE(ses.table_exists("flight")); + + ses.drop().table("flight").execute(); + ses.drop().table("airplane").execute(); + + REQUIRE(!ses.table_exists("flight")); + REQUIRE(!ses.table_exists("airplane")); +} + +TEST_CASE_METHOD(SessionFixture, " Execute select statement with where clause", "[session]") { + ses.create() + .table("person") + .execute(); + + person george{7, "george", 45}; + george.image.push_back(37); + + auto res = ses.insert() + .into("person", george) + .execute(); + REQUIRE(res == 1); + + // fetch person as record + auto result_record = ses.select() + .from("person") + .where("id"_col == 7) + .fetch_all(); + + for (const auto& i : result_record) { + REQUIRE(i.size() == 4); + REQUIRE(i.at(0).name() == "id"); + REQUIRE(i.at(0).type() == data_type_t::type_unsigned_long); + REQUIRE(i.at(0).as() == george.id); + REQUIRE(i.at(1).name() == "name"); + REQUIRE(i.at(1).type() == data_type_t::type_varchar); + REQUIRE(i.at(1).as() == george.name); + REQUIRE(i.at(2).name() == "age"); + REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_unsigned_int); + REQUIRE(i.at(2).as() == george.age); + } + + // fetch person as person + auto result_person = ses.select() + .from("person") + .where("id"_col == 7) + .fetch_all(); + + for (const auto& i : result_person) { + REQUIRE(i.id == 7); + REQUIRE(i.name == "george"); + REQUIRE(i.age == 45); + } + + ses.drop().table("person").execute(); +} + +TEST_CASE_METHOD(SessionFixture, " Execute insert statement", "[session]") { + ses.create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("color", 63) + }) + .execute(); + + auto res = ses.insert() + .into("person", {"id", "name", "color"}) + .values({7, "george", "green"}) + .execute(); + + REQUIRE(res == 1); + + // fetch person as record + auto result_record = ses.select({"id", "name", "color"}) + .from("person") + .where("id"_col == 7) + .fetch_all(); + + for (const auto& i : result_record) { + REQUIRE(i.size() == 3); + REQUIRE(i.at(0).name() == "id"); + REQUIRE(i.at(0).type() == data_type_t::type_long_long); + REQUIRE(i.at(0).as() == 7); + REQUIRE(i.at(1).name() == "name"); + REQUIRE(i.at(1).type() == data_type_t::type_varchar); + REQUIRE(i.at(1).as() == "george"); + REQUIRE(i.at(2).name() == "color"); + REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_varchar); + REQUIRE(i.at(2).as() == "green"); + } + + ses.drop().table("person").execute(); +} + +TEST_CASE_METHOD(SessionFixture, " Select statement with foreign key", "[session]") { + ses.create() + .table("airplane") + .execute(); + + ses.create() + .table("flight") + .execute(); + + std::vector> planes { + make_entity(1, "Airbus", "A380"), + make_entity(2, "Boeing", "707"), + make_entity(3, "Boeing", "747") + }; + + for (const auto &plane : planes) { + auto res = ses.insert().into("airplane").values(*plane).execute(); + REQUIRE(res == 1); + } + + auto count = ses.select({count_all()}).from("airplane").fetch_value(); + REQUIRE(count == 3); + + flight f4711{4, planes.at(1), "hans"}; + + auto res = ses.insert().into("flight").values(f4711).execute(); + REQUIRE(res == 1); + + auto f = *ses.select().from("flight").fetch_all().begin(); + REQUIRE(f.id == 4); + REQUIRE(f.pilot_name == "hans"); + REQUIRE(f.airplane.get() != nullptr); + REQUIRE(f.airplane->id == 2); + + ses.drop().table("flight").execute(); + ses.drop().table("airplane").execute(); +} \ No newline at end of file diff --git a/include/matador/sql/connection_pool.hpp b/include/matador/sql/connection_pool.hpp index dd05dcf..a02ae64 100644 --- a/include/matador/sql/connection_pool.hpp +++ b/include/matador/sql/connection_pool.hpp @@ -73,14 +73,15 @@ public: using connection_pointer = connection_ptr; public: - connection_pool(const std::string &dns, unsigned int count) + connection_pool(const std::string &dns, size_t count) : info_(connection_info::parse(dns)) { connection_repo_.reserve(count); - for (auto i = 0U; i < count; ++i) { - connection_repo_.emplace_back(i+1, info_); + while (count) { + connection_repo_.emplace_back(count, info_); auto &conn = connection_repo_.back(); idle_connections_.emplace(conn.first, &conn); conn.second.open(); + --count; } } diff --git a/src/sql/query_result_reader.cpp b/src/sql/query_result_reader.cpp index 371c244..cf27589 100644 --- a/src/sql/query_result_reader.cpp +++ b/src/sql/query_result_reader.cpp @@ -175,7 +175,8 @@ void query_result_reader::read_value(const char *id, size_t index, any_type &val break; } case sql::data_type_t::type_blob: { - throw std::logic_error("data type blob not supported"); + value = utils::blob{}; + break; } case sql::data_type_t::type_unknown: { value = std::string(column(index)); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a4a625f..dad6695 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,7 +33,9 @@ add_executable(tests QueryBuilderTest.cpp TypeTraitsTest.cpp Databases.hpp models/optional.hpp - ConvertTest.cpp) + ConvertTest.cpp + DummyConnection.hpp + DummyConnection.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain @@ -42,5 +44,3 @@ target_link_libraries(tests PRIVATE ${SQLite3_LIBRARIES} ${PostgreSQL_LIBRARY}) target_include_directories(tests PUBLIC $/include) - -add_test(NAME matador_tests COMMAND tests) diff --git a/test/ConnectionPoolTest.cpp b/test/ConnectionPoolTest.cpp index 6ef0537..b3b16e6 100644 --- a/test/ConnectionPoolTest.cpp +++ b/test/ConnectionPoolTest.cpp @@ -1,12 +1,13 @@ #include -#include #include +#include "DummyConnection.hpp" + using namespace matador::sql; TEST_CASE("Create connection pool", "[connection pool]") { - using pool_t = connection_pool; + using pool_t = connection_pool; pool_t pool("sqlite://sqlite.db", 4); @@ -50,7 +51,7 @@ TEST_CASE("Create connection pool", "[connection pool]") { } TEST_CASE("Acquire connection by id", "[connection pool]") { - using pool_t = connection_pool; + using pool_t = connection_pool; pool_t pool("sqlite://sqlite.db", 4); diff --git a/test/Databases.hpp b/test/Databases.hpp index 20d51e8..0b3de2d 100644 --- a/test/Databases.hpp +++ b/test/Databases.hpp @@ -3,8 +3,8 @@ struct Postgres { -// constexpr static const char *dns{"postgres://test:test123@127.0.0.1:15432/test"}; - constexpr static const char *dns{"postgres://test:test123@127.0.0.1:5432/matador_test"}; + constexpr static const char *dns{"postgres://test:test123@127.0.0.1:15432/test"}; +// constexpr static const char *dns{"postgres://test:test123@127.0.0.1:5432/matador_test"}; }; struct Sqlite @@ -14,8 +14,8 @@ struct Sqlite struct MySql { -// constexpr static const char *dns{"mysql://test:test123!@127.0.0.1:3306/testdb"}; - constexpr static const char *dns{"mysql://test:test123!@127.0.0.1:3306/matador_test"}; + constexpr static const char *dns{"mysql://test:test123!@127.0.0.1:3306/testdb"}; +// constexpr static const char *dns{"mysql://test:test123!@127.0.0.1:3306/matador_test"}; }; #endif //QUERY_DATABASES_HPP diff --git a/test/DummyConnection.cpp b/test/DummyConnection.cpp new file mode 100644 index 0000000..b4157e7 --- /dev/null +++ b/test/DummyConnection.cpp @@ -0,0 +1,28 @@ +#include "DummyConnection.hpp" + +namespace matador::test { + +DummyConnection::DummyConnection(sql::connection_info info) +: connection_info_(std::move(info)) +{} + +DummyConnection::DummyConnection(const std::string &dns) +: DummyConnection(sql::connection_info::parse(dns)) +{} + +void DummyConnection::open() +{ + is_open_ = true; +} + +void DummyConnection::close() +{ + is_open_ = false; +} + +bool DummyConnection::is_open() const +{ + return is_open_; +} + +} \ No newline at end of file diff --git a/test/DummyConnection.hpp b/test/DummyConnection.hpp new file mode 100644 index 0000000..2e11849 --- /dev/null +++ b/test/DummyConnection.hpp @@ -0,0 +1,30 @@ +#ifndef QUERY_DUMMYCONNECTION_HPP +#define QUERY_DUMMYCONNECTION_HPP + +#include "matador/sql/connection_info.hpp" + +namespace matador::test { + +class DummyConnection +{ +public: + explicit DummyConnection(sql::connection_info info); + explicit DummyConnection(const std::string& dns); + DummyConnection(const DummyConnection &x) = default; + DummyConnection& operator=(const DummyConnection &x) = default; + DummyConnection(DummyConnection &&x) noexcept = default; + DummyConnection& operator=(DummyConnection &&x) noexcept = default; + ~DummyConnection() = default; + + void open(); + void close(); + [[nodiscard]] bool is_open() const; + +private: + sql::connection_info connection_info_; + + bool is_open_{}; +}; + +} +#endif //QUERY_DUMMYCONNECTION_HPP