added logger

This commit is contained in:
Sascha Kuehl 2023-11-23 18:04:02 +01:00
parent 1dbe4e6096
commit 72bc6db6b3
18 changed files with 621 additions and 30 deletions

View File

@ -7,6 +7,8 @@
#include "matador/sql/query_result.hpp"
#include "matador/sql/record.hpp"
#include "matador/utils/logger.hpp"
#include <string>
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<class Type>
query_result<Type> fetch(const std::string &sql)
{
return query_result<Type>(connection_->fetch(sql));
}
[[nodiscard]] std::unique_ptr<query_result_impl> fetch(const std::string &sql) const;
[[nodiscard]] std::pair<size_t, std::string> execute(const std::string &sql) const;
private:
friend class session;
[[nodiscard]] std::unique_ptr<query_result_impl> call_fetch(const std::string &sql) const;
private:
connection_info connection_info_;
std::unique_ptr<connection_impl> connection_;
utils::logger logger_;
};
}

View File

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

View File

@ -54,8 +54,7 @@ public:
template<typename Type>
Type fetch_value()
{
auto result = fetch_all();
return {};
return fetch_one().at(0).as<Type>();
}
private:

View File

@ -48,7 +48,7 @@ public:
private:
friend class query_select_finish;
[[nodiscard]] std::unique_ptr<query_result_impl> call_fetch(const std::string &sql) const;
[[nodiscard]] std::unique_ptr<query_result_impl> fetch(const std::string &sql) const;
private:
connection_pool<connection> &pool_;

View File

@ -0,0 +1,88 @@
#ifndef QUERY_LOGGER_HPP
#define QUERY_LOGGER_HPP
#include <mutex>
#include <string>
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<typename ... Args>
void info(const std::string &what, Args const &... args) const { info(what.c_str(), args...); }
template<typename ... Args>
void info(const char *what, Args const &... args) const { log(log_level::LVL_INFO, what, args...); }
template<typename ... Args>
void debug(const std::string &what, Args const &... args) const { debug(what.c_str(), args...); }
template<typename ... Args>
void debug(const char *what, Args const &... args) const { log(log_level::LVL_DEBUG, what, args...); }
template<typename ... Args>
void warn(const std::string &what, Args const &... args) const { warn(what.c_str(), args...); }
template<typename ... Args>
void warn(const char *what, Args const &... args) const { log(log_level::LVL_WARN, what, args...); }
template<typename ... Args>
void error(const std::string &what, Args const &... args) const { error(what.c_str(), args...); }
template<typename ... Args>
void error(const char *what, Args const &... args) const { log(log_level::LVL_ERROR, what, args...); }
template<typename ... Args>
void fatal(const std::string &what, Args const &... args) const { fatal(what.c_str(), args...); }
template<typename ... Args>
void fatal(const char *what, Args const &... args) const { log(log_level::LVL_FATAL, what, args...); }
template<typename ... Args>
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<typename... ARGS>
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

View File

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

View File

@ -2,9 +2,22 @@
#define QUERY_STRING_HPP
#include <string>
#include <vector>
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<std::string> &values);
/**
* Replaces all occurrences of string from in given string
* with string to.

View File

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

View File

@ -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<size_t, std::string> connection::execute(const std::string &sql) const
{
logger_.debug(sql);
return {connection_->execute(sql), sql};
}
std::unique_ptr<query_result_impl> connection::call_fetch(const std::string &sql) const
std::unique_ptr<query_result_impl> connection::fetch(const std::string &sql) const
{
logger_.debug(sql);
return connection_->fetch(sql);
}

View File

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

View File

@ -460,4 +460,9 @@ column count(const std::string &column)
return {sql_function_t::COUNT, column};
}
column count_all()
{
return count("*");
}
}

View File

@ -15,7 +15,7 @@ record query_select_finish::fetch_one()
std::unique_ptr<query_result_impl> 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)

View File

@ -56,7 +56,7 @@ query_result<record> session::fetch(const query &q) const
const_cast<column&>(col).type(rit->type());
}
}
auto res = c->call_fetch(q.sql);
auto res = c->fetch(q.sql);
return query_result<record>{std::move(res), q.prototype};
}
@ -83,13 +83,13 @@ const class dialect &session::dialect() const
return dialect_;
}
std::unique_ptr<query_result_impl> session::call_fetch(const std::string &sql) const
std::unique_ptr<query_result_impl> 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);
}
}

149
src/utils/logger.cpp Normal file
View File

@ -0,0 +1,149 @@
#include "matador/utils/logger.hpp"
#include "matador/utils/os.hpp"
#include "matador/utils/string.hpp"
#include <chrono>
#include <cstring>
#include <map>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
namespace matador::utils {
std::size_t acquire_thread_index(std::thread::id id);
std::map<log_level, std::string> 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<std::string> 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<std::chrono::milliseconds>(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<std::mutex> 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<std::thread::id, std::size_t> ids;
std::lock_guard<std::mutex> lock(my_mutex);
if(ids.find(id) == ids.end()) {
ids[id] = next_index++;
}
return ids[id];
}
}

View File

@ -1,13 +1,28 @@
#include "matador/utils/os.hpp"
#include <stdexcept>
#ifdef _WIN32
#include <direct.h>
#include <io.h>
#include <windows.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <cstring>
#include <stdexcept>
#include <vector>
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<char> pathcopy(path, path+::strlen(path)+1);
std::vector<std::string> 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;
}
}

View File

@ -1,5 +1,7 @@
#include "matador/utils/string.hpp"
#include <sstream>
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<std::string> &values)
{
std::stringstream ss(str);
std::string item;
while (std::getline(ss, item, delim)) {
values.push_back(item);
}
return values.size();
}
}

View File

@ -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<connection> pool("sqlite://sqlite.db", 4);
session s(pool);
auto res = s.create()
.table("airplane", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("brand", 255),
make_column<std::string>("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<unsigned long>("id"),
make_fk_column<unsigned long>("airplane_id", "airplane", "id"),
make_column<std::string>("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<connection> 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<connection> pool("sqlite://sqlite.db", 4);
session s(pool);
auto res = s.create()
.table("airplane", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("brand", 255),
make_column<std::string>("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<unsigned long>("id"),
make_fk_column<unsigned long>("airplane_id", "airplane", "id"),
make_column<std::string>("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<int>();
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<connection> 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<connection> pool("sqlite://sqlite.db", 4);
session s(pool);
auto res = s.create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
make_column<unsigned short>("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<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 = s.select({"id", "name", "age"})
.from("person")
.fetch_one();
REQUIRE(rec.at(1).str() == "george");
auto name = s.select({"name"})
.from("person")
.fetch_value<std::string>();
REQUIRE(name == "george");
s.drop().table("person").execute();
}
TEST_CASE("Execute select statement with order by", "[session record]") {
connection_pool<connection> 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<std::string> 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<std::pair<int, int>> expected_values {{2, 13}, {2, 45}, {1, 67}};
std::list<std::pair<int, int>> expected_values {{2, 45}, {2, 13}, {1, 67}};
for (const auto &r : result) {
REQUIRE(r.at(0).as<int>() == expected_values.front().first);
REQUIRE(r.at(1).as<int>() == expected_values.front().second);

View File

@ -16,11 +16,15 @@ TEST_CASE("Create table with foreign key relation", "[session]") {
connection_pool<connection> pool("sqlite://sqlite.db", 4);
session s(pool);
auto res = s.create().table<airplane>("airplane").execute();
auto res = s.create()
.table<airplane>("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>("flight").execute();
res = s.create()
.table<flight>("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)))");