diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index ccc9faa..7c2c35f 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -7,6 +7,8 @@ #include "matador/sql/query_result.hpp" #include "matador/sql/record.hpp" +#include "matador/utils/logger.hpp" + #include namespace matador::sql { @@ -32,21 +34,13 @@ public: [[nodiscard]] record describe(const std::string &table_name) const; [[nodiscard]] bool exists(const std::string &table_name) const; - template - query_result fetch(const std::string &sql) - { - return query_result(connection_->fetch(sql)); - } + [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; [[nodiscard]] std::pair execute(const std::string &sql) const; -private: - friend class session; - - [[nodiscard]] std::unique_ptr call_fetch(const std::string &sql) const; - private: connection_info connection_info_; std::unique_ptr connection_; + utils::logger logger_; }; } diff --git a/include/matador/sql/query_builder.hpp b/include/matador/sql/query_builder.hpp index e224604..acf90f2 100644 --- a/include/matador/sql/query_builder.hpp +++ b/include/matador/sql/query_builder.hpp @@ -53,6 +53,7 @@ struct any_type_to_string_visitor column alias(const std::string &column, const std::string &as); column alias(column &&col, const std::string &as); column count(const std::string &column); +column count_all(); enum class join_type_t { INNER, OUTER, LEFT, RIGHT diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 007b56b..50871f8 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -54,8 +54,7 @@ public: template Type fetch_value() { - auto result = fetch_all(); - return {}; + return fetch_one().at(0).as(); } private: diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index 64c2128..44b1bf5 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -48,7 +48,7 @@ public: private: friend class query_select_finish; - [[nodiscard]] std::unique_ptr call_fetch(const std::string &sql) const; + [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; private: connection_pool &pool_; diff --git a/include/matador/utils/logger.hpp b/include/matador/utils/logger.hpp new file mode 100644 index 0000000..86e1217 --- /dev/null +++ b/include/matador/utils/logger.hpp @@ -0,0 +1,88 @@ +#ifndef QUERY_LOGGER_HPP +#define QUERY_LOGGER_HPP + +#include +#include + +namespace matador::utils { + +enum class log_level +{ + LVL_FATAL, /**< If a serious error occurred use FATAL level */ + LVL_ERROR, /**< On error use ERROR level */ + LVL_WARN, /**< Warnings should use WARN level */ + LVL_INFO, /**< Information should go with INFO level */ + LVL_DEBUG, /**< Debug output should use DEBUG level */ + LVL_TRACE, /**< Trace information should use TRACE level */ + LVL_ALL /**< This level represents all log levels and should be used for logging */ +}; + +/** + * Simple file logger + */ +class logger +{ +public: + logger(const std::string &path, std::string source); + logger(FILE *file, std::string source); + logger(const logger &x); + logger& operator=(const logger &x); + logger(logger &&x) noexcept; + logger& operator=(logger &&x) noexcept; + + template + void info(const std::string &what, Args const &... args) const { info(what.c_str(), args...); } + template + void info(const char *what, Args const &... args) const { log(log_level::LVL_INFO, what, args...); } + + template + void debug(const std::string &what, Args const &... args) const { debug(what.c_str(), args...); } + template + void debug(const char *what, Args const &... args) const { log(log_level::LVL_DEBUG, what, args...); } + + template + void warn(const std::string &what, Args const &... args) const { warn(what.c_str(), args...); } + template + void warn(const char *what, Args const &... args) const { log(log_level::LVL_WARN, what, args...); } + + template + void error(const std::string &what, Args const &... args) const { error(what.c_str(), args...); } + template + void error(const char *what, Args const &... args) const { log(log_level::LVL_ERROR, what, args...); } + + template + void fatal(const std::string &what, Args const &... args) const { fatal(what.c_str(), args...); } + template + void fatal(const char *what, Args const &... args) const { log(log_level::LVL_FATAL, what, args...); } + + template + void log(log_level lvl, const char *what, Args const &... args) const; + void log(log_level lvl, const char *message) const; + +private: + void write(const char *message, size_t size) const; + void close(); + +private: + std::string path_; + FILE *stream = nullptr; + mutable std::mutex mutex_; + std::string source_; +}; + +template +void logger::log(log_level lvl, const char *what, ARGS const &... args) const +{ + char message_buffer[16384]; + +#ifdef _MSC_VER + sprintf_s(message_buffer, 16384, what, args...); +#else + sprintf(message_buffer, what, args...); +#endif + + log(lvl, message_buffer); +} + +} +#endif //QUERY_LOGGER_HPP diff --git a/include/matador/utils/os.hpp b/include/matador/utils/os.hpp index d36a04d..b2c3130 100644 --- a/include/matador/utils/os.hpp +++ b/include/matador/utils/os.hpp @@ -5,12 +5,40 @@ namespace matador::utils::os { +#ifdef _WIN32 +extern char DIR_SEPARATOR; +extern const char* DIR_SEPARATOR_STRING; +#else +extern char DIR_SEPARATOR; +extern const char* DIR_SEPARATOR_STRING; +#endif + std::string error_string(unsigned long error); std::string getenv(const char* name); [[maybe_unused]] std::string getenv(const std::string &name); +FILE* fopen(const std::string &path, const char *modes); +FILE* fopen(const char *path, const char *modes); + +std::string get_current_dir(); + +bool mkdir(const std::string &dirname); +bool mkdir(const char *dirname); + +bool chdir(const std::string &dirname); +bool chdir(const char *dirname); + +bool rmdir(const std::string &dirname); +bool rmdir(const char *dirname); + +bool mkpath(const std::string &path); +bool mkpath(const char *path); + +bool rmpath(const std::string &path); +bool rmpath(const char *path); + } #endif //QUERY_OS_HPP diff --git a/include/matador/utils/string.hpp b/include/matador/utils/string.hpp index a5b88b2..ba1bb79 100644 --- a/include/matador/utils/string.hpp +++ b/include/matador/utils/string.hpp @@ -2,9 +2,22 @@ #define QUERY_STRING_HPP #include +#include namespace matador::utils { +/** + * Splits a string by a delimiter and + * add the string tokens to a vector. The + * size of the vector is returned. + * + * @param str The string to split. + * @param delim The delimiter character. + * @param values The result vector. + * @return The size of the vector. + */ +size_t split(const std::string &str, char delim, std::vector &values); + /** * Replaces all occurrences of string from in given string * with string to. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bbcfe39..aca6ad2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,7 +57,8 @@ set(UTILS_HEADER ../include/matador/utils/os.hpp ../include/matador/utils/access.hpp ../include/matador/utils/identifier.hpp - ../include/matador/utils/cascade_type.hpp) + ../include/matador/utils/cascade_type.hpp + ../include/matador/utils/logger.hpp) set(UTILS_SOURCES utils/field_attributes.cpp @@ -66,7 +67,8 @@ set(UTILS_SOURCES utils/library.cpp utils/os.cpp utils/identifier.cpp - sql/value_extractor.cpp) + sql/value_extractor.cpp + utils/logger.cpp) add_library(matador STATIC ${SQL_SOURCES} ${SQL_HEADER} ${UTILS_SOURCES} ${UTILS_HEADER}) target_include_directories(matador PUBLIC ${PROJECT_SOURCE_DIR}/include) diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index a7d5018..d55691d 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -10,6 +10,7 @@ namespace matador::sql { connection::connection(connection_info info) : connection_info_(std::move(info)) +, logger_(stdout, "SQL") { connection_.reset(backend_provider::instance().create_connection(connection_info_.type, connection_info_)); } @@ -20,6 +21,7 @@ connection::connection(const std::string& dns) connection::connection(const connection &x) : connection_info_(x.connection_info_) +, logger_(x.logger_) { if (x.connection_) { throw std::runtime_error("couldn't copy connection with valid connection impl"); @@ -28,6 +30,7 @@ connection::connection(const connection &x) connection &connection::operator=(const connection &x) { connection_info_ = x.connection_info_; + logger_ = x.logger_; if (x.connection_) { throw std::runtime_error("couldn't copy connection with valid connection impl"); } @@ -75,11 +78,13 @@ bool connection::exists(const std::string &table_name) const std::pair connection::execute(const std::string &sql) const { + logger_.debug(sql); return {connection_->execute(sql), sql}; } -std::unique_ptr connection::call_fetch(const std::string &sql) const +std::unique_ptr connection::fetch(const std::string &sql) const { + logger_.debug(sql); return connection_->fetch(sql); } diff --git a/src/sql/dialect.cpp b/src/sql/dialect.cpp index d7e81d1..2f48ab0 100644 --- a/src/sql/dialect.cpp +++ b/src/sql/dialect.cpp @@ -37,11 +37,14 @@ const std::string &dialect::data_type_at(data_type_t type) const std::string dialect::prepare_identifier(const column &col) const { std::string result(col.name()); - escape_quotes_in_identifier(result); if (!col.is_function()) { + escape_quotes_in_identifier(result); quote_identifier(result); } else { - return sql_func_map_.at(col.function()) + "(" + result + ")"; + result = sql_func_map_.at(col.function()) + "(" + result + ")"; + } + if (!col.alias().empty()) { + result += " AS " + col.alias(); } return result; } diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index d7b725b..e232010 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -460,4 +460,9 @@ column count(const std::string &column) return {sql_function_t::COUNT, column}; } +column count_all() +{ + return count("*"); +} + } \ No newline at end of file diff --git a/src/sql/query_intermediates.cpp b/src/sql/query_intermediates.cpp index d32b0c5..8a5b545 100644 --- a/src/sql/query_intermediates.cpp +++ b/src/sql/query_intermediates.cpp @@ -15,7 +15,7 @@ record query_select_finish::fetch_one() std::unique_ptr query_select_finish::fetch() { - return session_.call_fetch(builder_.compile().sql); + return session_.fetch(builder_.compile().sql); } query_intermediate::query_intermediate(session &db, query_builder &query) diff --git a/src/sql/session.cpp b/src/sql/session.cpp index 906db2b..66db828 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -56,7 +56,7 @@ query_result session::fetch(const query &q) const const_cast(col).type(rit->type()); } } - auto res = c->call_fetch(q.sql); + auto res = c->fetch(q.sql); return query_result{std::move(res), q.prototype}; } @@ -83,13 +83,13 @@ const class dialect &session::dialect() const return dialect_; } -std::unique_ptr session::call_fetch(const std::string &sql) const +std::unique_ptr session::fetch(const std::string &sql) const { auto c = pool_.acquire(); if (!c.valid()) { throw std::logic_error("no database connection available"); } - return c->call_fetch(sql); + return c->fetch(sql); } } \ No newline at end of file diff --git a/src/utils/logger.cpp b/src/utils/logger.cpp new file mode 100644 index 0000000..8830b9a --- /dev/null +++ b/src/utils/logger.cpp @@ -0,0 +1,149 @@ +#include "matador/utils/logger.hpp" + +#include "matador/utils/os.hpp" +#include "matador/utils/string.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace matador::utils { + +std::size_t acquire_thread_index(std::thread::id id); + +std::map level_strings = { /* NOLINT */ +{ log_level::LVL_DEBUG, "DEBUG" }, +{ log_level::LVL_INFO, "INFO" }, +{ log_level::LVL_WARN, "WARN" }, +{ log_level::LVL_ERROR, "ERROR" }, +{ log_level::LVL_TRACE, "TRACE" } +}; + +logger::logger(const std::string &path, std::string source) +: source_(std::move(source)) +{ + std::string filename(path); + // find last dir delimiter + const char *last = strrchr(path.c_str(), os::DIR_SEPARATOR); + if (last != nullptr) { + path_.assign(path.data(), last-path.data()); + } else { + path_.clear(); + } + + if (last != nullptr) { + filename = (last + 1); + } + // extract base path and extension + std::vector result; + if (utils::split(filename, '.', result) != 2) { + throw std::logic_error("split path must consists of two elements"); + } + // get current path + auto pwd = os::get_current_dir(); + // make path + os::mkpath(path_); + // change into path + os::chdir(path_); + // create file + stream = os::fopen(filename, "a"); + if (stream == nullptr) { + os::chdir(pwd); + throw std::logic_error("error opening file"); + } + os::chdir(pwd); +} + +logger::logger(FILE *file, std::string source) +: stream(file) +, source_(std::move(source)) +{} + +logger::logger(const logger &x) +: path_(x.path_) +, stream(x.stream) +, source_(x.source_) +{} + +logger &logger::operator=(const logger &x) +{ + if (this == &x) { + return *this; + } + path_ = x.path_; + stream = x.stream; + source_ = x.source_; + return *this; +} + +logger::logger(logger &&x) noexcept +: path_(std::move(x.path_)) +, stream(x.stream) +, source_(std::move(x.source_)) +{ + x.stream = nullptr; +} + +logger &logger::operator=(logger &&x) noexcept +{ + path_ = std::move(x.path_); + std::swap(stream, x.stream); + source_ = std::move(x.source_); + return *this; +} + +void logger::log(log_level lvl, const char *message) const +{ + using namespace std::chrono; + auto timestamp = system_clock::now(); + auto coarse = system_clock::to_time_t(timestamp); + auto fine = time_point_cast(timestamp); + + char timestamp_buffer[sizeof "9999-12-31 23:59:59.999"]; + std::snprintf(timestamp_buffer + std::strftime(timestamp_buffer, + sizeof timestamp_buffer - 3, + "%F %T.", + std::localtime(&coarse)), 4, "%03ld", fine.time_since_epoch().count() % 1000); + char buffer[1024]; + +#ifdef _MSC_VER + int ret = sprintf_s(buffer, 1024, "%s [Thread %zu] [%-7s] [%s]: %s\n", timestamp_buffer, acquire_thread_index(std::this_thread::get_id()), level_strings[lvl].c_str(), source_.c_str(), message); +#else + int ret = sprintf(buffer, "%s [Thread %lu] [%-7s] [%s]: %s\n", timestamp_buffer, acquire_thread_index(std::this_thread::get_id()), level_strings[lvl].c_str(), source_.c_str(), message); +#endif + + std::lock_guard l(mutex_); + write(buffer, ret); +} + +void logger::write(const char *message, size_t size) const +{ + fwrite(message, sizeof(char), size, stream); + fflush(stream); +} + +void logger::close() +{ + if (stream) { + fclose(stream); + stream = nullptr; + } +} + +std::size_t acquire_thread_index(std::thread::id id) +{ + static std::size_t next_index = 0; + static std::mutex my_mutex; + static std::map ids; + std::lock_guard lock(my_mutex); + if(ids.find(id) == ids.end()) { + ids[id] = next_index++; + } + return ids[id]; +} + +} \ No newline at end of file diff --git a/src/utils/os.cpp b/src/utils/os.cpp index d1d985e..e675d67 100644 --- a/src/utils/os.cpp +++ b/src/utils/os.cpp @@ -1,13 +1,28 @@ #include "matador/utils/os.hpp" -#include - #ifdef _WIN32 +#include +#include #include +#else +#include +#include #endif +#include +#include +#include + namespace matador::utils::os { +#ifdef _WIN32 +char DIR_SEPARATOR = '\\'; +const char* DIR_SEPARATOR_STRING = "\\"; +#else +char DIR_SEPARATOR = '/'; +const char* DIR_SEPARATOR_STRING = "/"; +#endif + std::string getenv(const char *name) { #ifdef _WIN32 char var[1024]; @@ -46,4 +61,148 @@ std::string error_string(unsigned long error) { return result; } #endif + +std::string get_current_dir() +{ + char buffer[1024]; +#ifdef _WIN32 + char *dir = _getcwd(buffer, 1024); +#else + char *dir = ::getcwd(buffer, 1024); +#endif + if (dir == nullptr) { +#ifdef _WIN32 + ::strerror_s(buffer, 1023, errno); + throw std::logic_error(buffer); +#else + throw std::logic_error(::strerror(errno)); +#endif + } + return {buffer}; +} + +FILE* fopen(const std::string &path, const char *modes) +{ + return fopen(path.c_str(), modes); +} + +FILE* fopen(const char *path, const char *modes) +{ +#ifdef _WIN32 + return _fsopen(path, modes, _SH_DENYWR); +#else + return ::fopen(path, modes); +#endif +} + +bool mkdir(const std::string &dirname) +{ + return os::mkdir(dirname.c_str()); +} + +bool mkdir(const char *dirname) +{ + if (dirname == nullptr || strlen(dirname) == 0) { + return true; + } +#ifdef _WIN32 + return ::_mkdir(dirname) == 0; +#else + return ::mkdir(dirname, S_IRWXU) == 0; +#endif +} + +bool chdir(const std::string &dirname) +{ + return os::chdir(dirname.c_str()); +} + +bool chdir(const char *dirname) +{ + if (dirname == nullptr || strlen(dirname) == 0) { + return true; + } +#ifdef _WIN32 + return _chdir(dirname) == 0; +#else + return ::chdir(dirname) == 0; +#endif +} + +bool rmdir(const std::string &dirname) +{ + return os::rmdir(dirname.c_str()); +} + +bool rmdir(const char *dirname) +{ +#ifdef _WIN32 + return _rmdir(dirname) == 0; +#else + return ::rmdir(dirname) == 0; +#endif +} + +bool mkpath(const std::string &path) { + return os::rmpath(path.c_str()); +} + +bool mkpath(const char *path) { + char tmp[256]; + size_t len; + + snprintf(tmp, sizeof(tmp),"%s",path); + len = strlen(tmp); + if (len == 0) { + return true; + } + if(tmp[len - 1] == DIR_SEPARATOR) { + tmp[len - 1] = 0; + } + for(char *p = tmp + 1; *p; p++) { + if (*p == DIR_SEPARATOR) { + *p = 0; + if (!os::mkdir(tmp)) { + return false; + } + *p = DIR_SEPARATOR; + } + } + return os::mkdir(tmp); +} + +bool rmpath(const std::string &path) +{ + return os::rmpath(path.c_str()); +} + +bool rmpath(const char *path) +{ + // change next to last path segment + std::vector pathcopy(path, path+::strlen(path)+1); + std::vector segments; +#ifdef _WIN32 + char *next_token = nullptr; + char *segment = ::strtok_s(pathcopy.data(), DIR_SEPARATOR_STRING, &next_token); +#else + char *segment = ::strtok(pathcopy.data(), DIR_SEPARATOR_STRING); +#endif + + while (segment != nullptr) { + os::chdir(segment); + segments.emplace_back(segment); +#ifdef _WIN32 + segment = ::strtok_s(nullptr, DIR_SEPARATOR_STRING, &next_token); +#else + segment = ::strtok(nullptr, DIR_SEPARATOR_STRING); +#endif + } + + auto first = segments.rbegin(); + for (auto it=first; it!=segments.rend(); ++it) { + os::chdir(".."); + os::rmdir(*it); + } + return true; +} } \ No newline at end of file diff --git a/src/utils/string.cpp b/src/utils/string.cpp index adbe7fe..1345c13 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -1,5 +1,7 @@ #include "matador/utils/string.hpp" +#include + namespace matador::utils { void replace_all(std::string &in, const std::string &from, const std::string &to) @@ -19,4 +21,14 @@ const std::string &to_string(const std::string &str) return str; } +size_t split(const std::string &str, char delim, std::vector &values) +{ + std::stringstream ss(str); + std::string item; + while (std::getline(ss, item, delim)) { + values.push_back(item); + } + return values.size(); +} + } \ No newline at end of file diff --git a/test/SessionRecordTest.cpp b/test/SessionRecordTest.cpp index 5e2a473..8a25fff 100644 --- a/test/SessionRecordTest.cpp +++ b/test/SessionRecordTest.cpp @@ -28,6 +28,43 @@ TEST_CASE("Create and drop table statement", "[session record]") { REQUIRE(res.second == R"(DROP TABLE "person")"); } +TEST_CASE("Create and drop table statement with foreign key", "[session record]") { + connection_pool pool("sqlite://sqlite.db", 4); + session s(pool); + + auto res = s.create() + .table("airplane", { + make_pk_column("id"), + make_column("brand", 255), + make_column("model", 255), + }) + .execute(); + + REQUIRE(res.second == R"(CREATE TABLE "airplane" ("id" BIGINT NOT NULL, "brand" VARCHAR(255), "model" VARCHAR(255), CONSTRAINT PK_airplane PRIMARY KEY (id)))"); + + res = s.create() + .table("flight", { + make_pk_column("id"), + make_fk_column("airplane_id", "airplane", "id"), + make_column("pilot_name", 255), + }) + .execute(); + + REQUIRE(res.second == R"(CREATE TABLE "flight" ("id" BIGINT NOT NULL, "airplane_id" BIGINT, "pilot_name" VARCHAR(255), CONSTRAINT PK_flight PRIMARY KEY (id), CONSTRAINT FK_flight_airplane_id FOREIGN KEY (airplane_id) REFERENCES airplane(id)))"); + + res = s.drop() + .table("flight") + .execute(); + + REQUIRE(res.second == R"(DROP TABLE "flight")"); + + res = s.drop() + .table("airplane") + .execute(); + + REQUIRE(res.second == R"(DROP TABLE "airplane")"); +} + TEST_CASE("Execute insert record statement", "[session record]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); @@ -72,6 +109,51 @@ TEST_CASE("Execute insert record statement", "[session record]") { .execute(); } +TEST_CASE("Execute insert record statement with foreign key", "[session record]") { + connection_pool pool("sqlite://sqlite.db", 4); + session s(pool); + + auto res = s.create() + .table("airplane", { + make_pk_column("id"), + make_column("brand", 255), + make_column("model", 255), + }) + .execute(); + + REQUIRE(res.second == R"(CREATE TABLE "airplane" ("id" BIGINT NOT NULL, "brand" VARCHAR(255), "model" VARCHAR(255), CONSTRAINT PK_airplane PRIMARY KEY (id)))"); + + res = s.create() + .table("flight", { + make_pk_column("id"), + make_fk_column("airplane_id", "airplane", "id"), + make_column("pilot_name", 255), + }) + .execute(); + + REQUIRE(res.second == R"(CREATE TABLE "flight" ("id" BIGINT NOT NULL, "airplane_id" BIGINT, "pilot_name" VARCHAR(255), CONSTRAINT PK_flight PRIMARY KEY (id), CONSTRAINT FK_flight_airplane_id FOREIGN KEY (airplane_id) REFERENCES airplane(id)))"); + + res = s.insert().into("airplane", {"id", "brand", "model"}).values({1, "Airbus", "A380"}).execute(); + REQUIRE(res.first == 1); + + res = s.insert().into("airplane", {"id", "brand", "model"}).values({2, "Boeing", "707"}).execute(); + REQUIRE(res.first == 1); + + res = s.insert().into("airplane", {"id", "brand", "model"}).values({3, "Boeing", "747"}).execute(); + REQUIRE(res.first == 1); + + auto count = s.select({count_all()}).from("airplane").fetch_value(); + REQUIRE(count == 3); + + res = s.insert().into("flight", {"id", "airplane_id", "pilot_name"}).values({4, 1, "George"}).execute(); + REQUIRE(res.first == 1); + + res = s.drop().table("flight").execute(); + REQUIRE(res.second == R"(DROP TABLE "flight")"); + res = s.drop().table("airplane").execute(); + REQUIRE(res.second == R"(DROP TABLE "airplane")"); +} + TEST_CASE("Execute update record statement", "[session record]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); @@ -122,6 +204,53 @@ TEST_CASE("Execute update record statement", "[session record]") { s.drop().table("person").execute(); } +TEST_CASE("Execute select statement", "[session record]") { + connection_pool pool("sqlite://sqlite.db", 4); + session s(pool); + + auto res = s.create() + .table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("age") + }) + .execute(); + + REQUIRE(res.first == 0); + + res = s.insert().into("person", {"id", "name", "age"}).values({1, "george", 45}).execute(); + REQUIRE(res.first == 1); + res = s.insert().into("person", {"id", "name", "age"}).values({2, "jane", 32}).execute(); + REQUIRE(res.first == 1); + res = s.insert().into("person", {"id", "name", "age"}).values({3, "michael", 67}).execute(); + REQUIRE(res.first == 1); + res = s.insert().into("person", {"id", "name", "age"}).values({4, "bob", 13}).execute(); + REQUIRE(res.first == 1); + + auto result = s.select({"id", "name", "age"}) + .from("person") + .fetch_all(); + + std::list 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 = s.select({"id", "name", "age"}) + .from("person") + .fetch_one(); + REQUIRE(rec.at(1).str() == "george"); + + auto name = s.select({"name"}) + .from("person") + .fetch_value(); + REQUIRE(name == "george"); + + s.drop().table("person").execute(); +} + TEST_CASE("Execute select statement with order by", "[session record]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); @@ -147,8 +276,7 @@ TEST_CASE("Execute select statement with order by", "[session record]") { auto result = s.select({"id", "name", "age"}) .from("person") - .where("id"_col == 8) - .order_by("name").desc() + .order_by("name").asc() .fetch_all(); std::list expected_names {"bob", "george", "jane", "michael"}; @@ -156,6 +284,7 @@ TEST_CASE("Execute select statement with order by", "[session record]") { REQUIRE(p.at(1).str() == expected_names.front()); expected_names.pop_front(); } + REQUIRE(expected_names.empty()); s.drop().table("person").execute(); } @@ -186,10 +315,10 @@ TEST_CASE("Execute select statement with group by and order by", "[session recor auto result = s.select({alias(count("age"), "age_count"), "age"}) .from("person") .group_by("age") - .order_by("age_count").asc() + .order_by("age_count").desc() .fetch_all(); - std::list> expected_values {{2, 13}, {2, 45}, {1, 67}}; + std::list> expected_values {{2, 45}, {2, 13}, {1, 67}}; for (const auto &r : result) { REQUIRE(r.at(0).as() == expected_values.front().first); REQUIRE(r.at(1).as() == expected_values.front().second); diff --git a/test/SessionTest.cpp b/test/SessionTest.cpp index 0534ec1..6511fe4 100644 --- a/test/SessionTest.cpp +++ b/test/SessionTest.cpp @@ -16,11 +16,15 @@ TEST_CASE("Create table with foreign key relation", "[session]") { connection_pool pool("sqlite://sqlite.db", 4); session s(pool); - auto res = s.create().table("airplane").execute(); + auto res = s.create() + .table("airplane") + .execute(); REQUIRE(res.first == 0); REQUIRE(res.second == R"(CREATE TABLE "airplane" ("id" BIGINT, "brand" VARCHAR(255), "model" VARCHAR(255), CONSTRAINT PK_airplane PRIMARY KEY (id)))"); - res = s.create().table("flight").execute(); + res = s.create() + .table("flight") + .execute(); REQUIRE(res.first == 0); REQUIRE(res.second == R"(CREATE TABLE "flight" ("id" BIGINT, "airplane_id" BIGINT, "pilot_name" VARCHAR(255), CONSTRAINT PK_flight PRIMARY KEY (id), CONSTRAINT FK_flight_airplane_id FOREIGN KEY (airplane_id) REFERENCES airplane(id)))");