Commit 2c4016a4 authored by louiz’'s avatar louiz’

Merge branch 'postgresql' into 'master'

Add postgresql support

Closes #3237

See merge request !18
parents aaa9de8f b1f850b6
......@@ -17,6 +17,7 @@ variables:
SYSTEMD: "-DWITH_SYSTEMD=1"
LIBIDN: "-DWITH_LIBIDN=1"
SQLITE3: "-DWITH_SQLITE3=1"
POSTGRESQL: "-WITH_POSTGRESQL=1"
#
## Build jobs
......@@ -27,10 +28,10 @@ variables:
tags:
- docker
script:
- "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3}"
- "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL}"
- mkdir build/
- cd build/
- cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3}
- cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL}
- make everything -j$(nproc || echo 1)
- make coverage_check -j$(nproc || echo 1)
artifacts:
......@@ -74,19 +75,25 @@ build:2:
build:3:
variables:
SQLITE3: "-DWITHOUT_SQLITE3=1"
TEST_POSTGRES_URI: "postgres@postgres/postgres"
services:
- postgres:latest
<<: *fedora_build
build:4:
variables:
SQLITE3: "-DWITHOUT_SQLITE3=1"
POSTGRESQL: "-DWITHOUT_POSTGRESQL=1"
BOTAN: "-DWITHOUT_BOTAN=1"
LIBIDN: "-DWITHOUT_LIBIDN=1"
<<: *fedora_build
build:5:
variables:
SQLITE3: "-DWITHOUT_SQLITE3=1"
UDNS: "-DWITHOUT_UDNS=1"
TEST_POSTGRES_URI: "postgres@postgres/postgres"
services:
- postgres:latest
<<: *fedora_build
build:6:
......@@ -100,7 +107,6 @@ build:7:
UDNS: "-DWITHOUT_UDNS=1"
<<: *fedora_build
#
## Test jobs
#
......
......@@ -14,7 +14,7 @@ endif()
#
## Find optional instrumentation libraries that will be used in debug only
#
find_library(LIBASAN NAMES asan libasan.so.3 libasan.so.2 libasan.so.1)
find_library(LIBASAN NAMES asan libasan.so.4 libasan.so.3 libasan.so.2 libasan.so.1)
find_library(LIBUBSAN NAMES ubsan libubsan.so.0)
#
......@@ -130,6 +130,12 @@ elseif(NOT WITHOUT_SQLITE3)
find_package(SQLITE3)
endif()
if(WITH_POSTGRESQL)
find_package(PQ REQUIRED)
elseif(NOT WITHOUT_POSTGRESQL)
find_package(PQ)
endif()
#
## Set all the include directories, depending on what libraries are used
#
......@@ -187,12 +193,17 @@ file(GLOB source_network
src/network/*.[hc]pp)
add_library(network OBJECT ${source_network})
if(SQLITE3_FOUND)
if(SQLITE3_FOUND OR PQ_FOUND)
file(GLOB source_database
src/database/*.[hc]pp)
add_library(database OBJECT ${source_database})
include_directories(database ${SQLITE3_INCLUDE_DIRS})
if(SQLITE3_FOUND)
include_directories(database ${SQLITE3_INCLUDE_DIRS})
endif()
if(PQ_FOUND)
include_directories(database ${PQ_INCLUDE_DIRS})
endif()
set(USE_DATABASE TRUE)
else()
add_library(database OBJECT "")
......@@ -260,8 +271,14 @@ if(LIBIDN_FOUND)
target_link_libraries(test_suite ${LIBIDN_LIBRARIES})
endif()
if(USE_DATABASE)
target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES})
target_link_libraries(test_suite ${SQLITE3_LIBRARIES})
if(SQLITE3_FOUND)
target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES})
target_link_libraries(test_suite ${SQLITE3_LIBRARIES})
endif()
if(PQ_FOUND)
target_link_libraries(${PROJECT_NAME} ${PQ_LIBRARIES})
target_link_libraries(test_suite ${PQ_LIBRARIES})
endif()
endif()
# Define a __FILENAME__ macro with the relative path (from the base project directory)
......
......@@ -32,10 +32,12 @@ libiconv_
libuuid_
Generate unique IDs
sqlite3_ (option, but highly recommended)
Provides a way to store various options in a (sqlite3) database. Each user
of the gateway can store their own values (for example their prefered port,
or their IRC password). Without this dependency, many interesting features
sqlite3_
or
libpq_
Provides a way to store various options in a database. Each user of the
gateway can store their own values (for example their prefered port, or
their IRC password). Without this dependency, many interesting features
are missing.
libidn_ (optional, but recommended)
......@@ -165,3 +167,4 @@ to use biboumi.
.. _systemd: https://www.freedesktop.org/wiki/Software/systemd/
.. _biboumi.1.rst: doc/biboumi.1.rst
.. _gcrypt: https://www.gnu.org/software/libgcrypt/
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
# - Find libpq
# Find the postgresql front end library
#
# This module defines the following variables:
# PQ_FOUND - True if library and include directory are found
# If set to TRUE, the following are also defined:
# PQ_INCLUDE_DIRS - The directory where to find the header file
# PQ_LIBRARIES - Where to find the library file
#
# For conveniance, these variables are also set. They have the same values
# than the variables above. The user can thus choose his/her prefered way
# to write them.
# PQ_LIBRARY
# PQ_INCLUDE_DIR
#
# This file is in the public domain
include(FindPkgConfig)
if(NOT PQ_FOUND)
pkg_check_modules(PQ libpq)
endif()
if(NOT PQ_FOUND)
find_path(PQ_INCLUDE_DIRS NAMES libpq-fe.h
DOC "The libpq include directory")
find_library(PQ_LIBRARIES NAMES pq
DOC "The pq library")
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
# set PQ_FOUND to TRUE if these two variables are set.
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PQ REQUIRED_VARS PQ_LIBRARIES PQ_INCLUDE_DIRS)
if(PQ_FOUND)
set(PQ_LIBRARY ${PQ_LIBRARIES} CACHE INTERNAL "")
set(PQ_INCLUDE_DIR ${PQ_INCLUDE_DIRS} CACHE INTERNAL "")
set(PQ_FOUND ${PQ_FOUND} CACHE INTERNAL "")
endif()
endif()
mark_as_advanced(PQ_INCLUDE_DIRS PQ_LIBRARIES)
......@@ -77,6 +77,20 @@ port
The TCP port to use to connect to the local XMPP component. The default
value is 5347.
db_name
-------
The name of the database to use. This option can only be used if biboumi
has been compiled with a database support (Sqlite3 and/or PostgreSQL). If
the value begins with the postgresql scheme, “postgresql://” or
“postgres://”, then biboumi will try to connect to the PostgreSQL database
specified by the URI. See
https://www.postgresql.org/docs/current/static/libpq-connect.html#idm46428693970032
for all possible values. For example the value could be
“postgresql://user:secret@localhost”. If the value does not start with the
postgresql scheme, then it specifies a filename that will be opened with
Sqlite3. For example the value could be “/var/lib/biboumi/biboumi.sqlite”.
admin
-----
......
......@@ -32,7 +32,8 @@ RUN apk add --no-cache g++\
openssl\
libressl-dev\
zlib-dev\
curl
curl\
postgresql-dev
# Install botan
RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
......
......@@ -39,7 +39,8 @@ RUN apt install -y g++\
openssl\
zlib1g-dev\
libssl-dev\
curl
curl\
libpq-dev
# Install botan
RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
......
......@@ -39,6 +39,7 @@ RUN dnf --refresh install -y\
openssl-devel\
which\
java-1.8.0-openjdk\
postgresql-devel\
&& dnf clean all
# Install botan
......
......@@ -13,6 +13,7 @@ RUN apk add --no-cache\
make\
udns-dev\
sqlite-dev\
postgresql-dev\
libuuid\
util-linux-dev\
expat-dev\
......@@ -30,6 +31,7 @@ RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./bibou
-DWITH_BOTAN=1\
-DWITH_SQLITE3=1\
-DWITH_LIBIDN=1\
-DWITH_POSTGRESQL=1\
&& make -j8 && make install && rm -rf /biboumi
RUN adduser biboumi -D -h /home/biboumi
......
......@@ -38,6 +38,7 @@ The configuration file inside the image contains only a few default values. To
* BIBOUMI_PASSWORD: Sets the value of the *password* option.
* BIBOUMI_ADMIN: Sets the value of the *admin* option.
* BIBOUMI_XMPP_SERVER_IP: Sets the value of the *xmpp_server_ip* option. The default value is **xmpp**.
* BIBOUMI_DB_NAME: Sets the database name to be used by biboumi: a filesystem path pointing at a Sqlite3 file, or a postgresql URI (starting with “postgresql://”). See below to learn how to mount a host directory (to save your Sqlite3 database) or how to link with a postgresql docker container.
You can also directly provide your own configuration file by mounting it inside the container using the -v option:
......@@ -59,7 +60,7 @@ If you want to connect to the XMPP server running on the host machine, use the *
Volumes
-------
The database is stored in the /var/lib/biboumi/ directory. If you don’t bind a local directory to it, the database will be lost when the container is stopped. If you want to keep your database between each run, bind it with the -v option, like this: **-v /srv/biboumi/:/var/lib/biboumi**.
By default, a sqlite3 database is stored in the /var/lib/biboumi/ directory. If you don’t bind a local directory to it, the database will be lost when the container is stopped. If you want to keep your database between each run, bind it with the -v option, like this: **-v /srv/biboumi/:/var/lib/biboumi**.
Note: Due to a limitation in Docker, to be able to read and write into this database, make sure this mounted directory has the proper read and write permissions on the host: it can be owned by UID and GID 1000:1000, or use chmod to give permissions to everyone, for example.
......@@ -67,3 +68,25 @@ Note: Due to a limitation in Docker, to be able to read and write into this data
chown -R 1000:1000 database/
chmod 777 database/
```
Linking with a PostgreSQL container
-----------------------------------
If you want to use a PostgreSQL database, you need to either access the host database (run the biboumi container with --network=host), or link with a [postgresql docker image](https://hub.docker.com/_/postgres/).
To do that, start the PostgreSQL container like this:
```
docker run --name postgres postgres:latest
```
This will run a postgresql instance with a configured superuser named “postgres”, with no password and a database named “postgres” as well. If you want different values, please refer to the PostgreSQL’s image documentation.
Then start your biboumi container, by linking with this PostgreSQL container, and by specifying the correct db_name value (of course, also specify all the other options, like the XMPP hostname and password):
```
docker run --name biboumi \
--link=postgres \
-e BIBOUMI_DB_NAME=postgres://postgres@postgres/postgres \
biboumi
```
......@@ -6,6 +6,8 @@
#cmakedefine BOTAN_FOUND
#cmakedefine GCRYPT_FOUND
#cmakedefine UDNS_FOUND
#cmakedefine PQ_FOUND
#cmakedefine SQLITE3_FOUND
#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}"
#cmakedefine PROJECT_NAME "${PROJECT_NAME}"
#cmakedefine HAS_GET_TIME
......
......@@ -1031,6 +1031,7 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam
(void)hostname;
(void)chan_name;
(void)resource;
(void)history_limit;
#endif
}
......
......@@ -13,5 +13,10 @@ struct Column
T value{};
};
struct Id: Column<std::size_t> { static constexpr auto name = "id_";
static constexpr auto options = "PRIMARY KEY AUTOINCREMENT"; };
struct Id: Column<std::size_t> {
static constexpr std::size_t unset_value = static_cast<std::size_t>(-1);
static constexpr auto name = "id_";
static constexpr auto options = "PRIMARY KEY";
Id(): Column<std::size_t>(-1) {}
};
......@@ -2,11 +2,10 @@
#include <database/query.hpp>
#include <database/table.hpp>
#include <database/statement.hpp>
#include <string>
#include <sqlite3.h>
struct CountQuery: public Query
{
CountQuery(std::string name):
......@@ -15,20 +14,17 @@ struct CountQuery: public Query
this->body += std::move(name);
}
int64_t execute(sqlite3* db)
int64_t execute(DatabaseEngine& db)
{
auto statement = this->prepare(db);
auto statement = db.prepare(this->body);
int64_t res = 0;
if (sqlite3_step(statement.get()) == SQLITE_ROW)
res = sqlite3_column_int64(statement.get(), 0);
if (statement->step() != StepResult::Error)
res = statement->get_column_int64(0);
else
{
log_error("Count request didn’t return a result");
return 0;
}
if (sqlite3_step(statement.get()) != SQLITE_DONE)
log_warning("Count request returned more than one result.");
return res;
}
};
......@@ -7,16 +7,19 @@
#include <utils/time.hpp>
#include <config/config.hpp>
#include <database/sqlite3_engine.hpp>
#include <database/postgresql_engine.hpp>
#include <database/engine.hpp>
#include <database/index.hpp>
#include <sqlite3.h>
#include <memory>
sqlite3* Database::db;
Database::MucLogLineTable Database::muc_log_lines("MucLogLine_");
Database::GlobalOptionsTable Database::global_options("GlobalOptions_");
Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_");
Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_");
std::unique_ptr<DatabaseEngine> Database::db;
Database::MucLogLineTable Database::muc_log_lines("muclogline_");
Database::GlobalOptionsTable Database::global_options("globaloptions_");
Database::IrcServerOptionsTable Database::irc_server_options("ircserveroptions_");
Database::IrcChannelOptionsTable Database::irc_channel_options("ircchanneloptions_");
Database::RosterTable Database::roster("roster");
std::map<Database::CacheKey, Database::EncodingIn::real_type> Database::encoding_in_cache{};
......@@ -29,27 +32,28 @@ void Database::open(const std::string& filename)
// Try to open the specified database.
// Close and replace the previous database pointer if it succeeded. If it did
// not, just leave things untouched
sqlite3* new_db;
auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
Database::close();
if (res != SQLITE_OK)
{
log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db));
sqlite3_close(new_db);
throw std::runtime_error("");
}
Database::db = new_db;
Database::muc_log_lines.create(Database::db);
Database::muc_log_lines.upgrade(Database::db);
Database::global_options.create(Database::db);
Database::global_options.upgrade(Database::db);
Database::irc_server_options.create(Database::db);
Database::irc_server_options.upgrade(Database::db);
Database::irc_channel_options.create(Database::db);
Database::irc_channel_options.upgrade(Database::db);
Database::roster.create(Database::db);
Database::roster.upgrade(Database::db);
create_index<Database::Owner, Database::IrcChanName, Database::IrcServerName>(Database::db, "archive_index", Database::muc_log_lines.get_name());
std::unique_ptr<DatabaseEngine> new_db;
static const auto psql_prefix = "postgresql://"s;
static const auto psql_prefix2 = "postgres://"s;
if ((filename.substr(0, psql_prefix.size()) == psql_prefix) ||
(filename.substr(0, psql_prefix2.size()) == psql_prefix2))
new_db = PostgresqlEngine::open(filename);
else
new_db = Sqlite3Engine::open(filename);
if (!new_db)
return;
Database::db = std::move(new_db);
Database::muc_log_lines.create(*Database::db);
Database::muc_log_lines.upgrade(*Database::db);
Database::global_options.create(*Database::db);
Database::global_options.upgrade(*Database::db);
Database::irc_server_options.create(*Database::db);
Database::irc_server_options.upgrade(*Database::db);
Database::irc_channel_options.create(*Database::db);
Database::irc_channel_options.upgrade(*Database::db);
Database::roster.create(*Database::db);
Database::roster.upgrade(*Database::db);
create_index<Database::Owner, Database::IrcChanName, Database::IrcServerName>(*Database::db, "archive_index", Database::muc_log_lines.get_name());
}
......@@ -59,7 +63,7 @@ Database::GlobalOptions Database::get_global_options(const std::string& owner)
request.where() << Owner{} << "=" << owner;
Database::GlobalOptions options{Database::global_options.get_name()};
auto result = request.execute(Database::db);
auto result = request.execute(*Database::db);
if (result.size() == 1)
options = result.front();
else
......@@ -73,7 +77,7 @@ Database::IrcServerOptions Database::get_irc_server_options(const std::string& o
request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
Database::IrcServerOptions options{Database::irc_server_options.get_name()};
auto result = request.execute(Database::db);
auto result = request.execute(*Database::db);
if (result.size() == 1)
options = result.front();
else
......@@ -91,7 +95,7 @@ Database::IrcChannelOptions Database::get_irc_channel_options(const std::string&
" and " << Server{} << "=" << server <<\
" and " << Channel{} << "=" << channel;
Database::IrcChannelOptions options{Database::irc_channel_options.get_name()};
auto result = request.execute(Database::db);
auto result = request.execute(*Database::db);
if (result.size() == 1)
options = result.front();
else
......@@ -186,7 +190,7 @@ std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owne
if (limit >= 0)
request.limit() << limit;
auto result = request.execute(Database::db);
auto result = request.execute(*Database::db);
return {result.crbegin(), result.crend()};
}
......@@ -207,7 +211,7 @@ void Database::delete_roster_item(const std::string& local, const std::string& r
query << " WHERE " << Database::RemoteJid{} << "=" << remote << \
" AND " << Database::LocalJid{} << "=" << local;
query.execute(Database::db);
// query.execute(*Database::db);
}
bool Database::has_roster_item(const std::string& local, const std::string& remote)
......@@ -216,7 +220,7 @@ bool Database::has_roster_item(const std::string& local, const std::string& remo
query.where() << Database::LocalJid{} << "=" << local << \
" and " << Database::RemoteJid{} << "=" << remote;
auto res = query.execute(Database::db);
auto res = query.execute(*Database::db);
return !res.empty();
}
......@@ -226,19 +230,18 @@ std::vector<Database::RosterItem> Database::get_contact_list(const std::string&
auto query = Database::roster.select();
query.where() << Database::LocalJid{} << "=" << local;
return query.execute(Database::db);
return query.execute(*Database::db);
}
std::vector<Database::RosterItem> Database::get_full_roster()
{
auto query = Database::roster.select();
return query.execute(Database::db);
return query.execute(*Database::db);
}
void Database::close()
{
sqlite3_close(Database::db);
Database::db = nullptr;
}
......
......@@ -7,6 +7,8 @@
#include <database/column.hpp>
#include <database/count_query.hpp>
#include <database/engine.hpp>
#include <utils/optional_bool.hpp>
#include <chrono>
......@@ -25,11 +27,11 @@ class Database
struct Owner: Column<std::string> { static constexpr auto name = "owner_"; };
struct IrcChanName: Column<std::string> { static constexpr auto name = "ircChanName_"; };
struct IrcChanName: Column<std::string> { static constexpr auto name = "ircchanname_"; };
struct Channel: Column<std::string> { static constexpr auto name = "channel_"; };
struct IrcServerName: Column<std::string> { static constexpr auto name = "ircServerName_"; };
struct IrcServerName: Column<std::string> { static constexpr auto name = "ircservername_"; };
struct Server: Column<std::string> { static constexpr auto name = "server_"; };
......@@ -44,30 +46,30 @@ class Database
struct Ports: Column<std::string> { static constexpr auto name = "ports_";
Ports(): Column<std::string>("6667") {} };
struct TlsPorts: Column<std::string> { static constexpr auto name = "tlsPorts_";
struct TlsPorts: Column<std::string> { static constexpr auto name = "tlsports_";
TlsPorts(): Column<std::string>("6697;6670") {} };
struct Username: Column<std::string> { static constexpr auto name = "username_"; };
struct Realname: Column<std::string> { static constexpr auto name = "realname_"; };
struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterConnectionCommand_"; };
struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterconnectioncommand_"; };
struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedFingerprint_"; };
struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedfingerprint_"; };
struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingOut_"; };
struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingout_"; };
struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingIn_"; };
struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingin_"; };
struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxHistoryLength_";
struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxhistorylength_";
MaxHistoryLength(): Column<int>(20) {} };
struct RecordHistory: Column<bool> { static constexpr auto name = "recordHistory_";
struct RecordHistory: Column<bool> { static constexpr auto name = "recordhistory_";
RecordHistory(): Column<bool>(true) {}};
struct RecordHistoryOptional: Column<OptionalBool> { static constexpr auto name = "recordHistory_"; };
struct RecordHistoryOptional: Column<OptionalBool> { static constexpr auto name = "recordhistory_"; };
struct VerifyCert: Column<bool> { static constexpr auto name = "verifyCert_";
struct VerifyCert: Column<bool> { static constexpr auto name = "verifycert_";
VerifyCert(): Column<bool>(true) {} };
struct Persistent: Column<bool> { static constexpr auto name = "persistent_";
......@@ -134,7 +136,7 @@ class Database
static int64_t count(const TableType& table)
{
CountQuery query{table.get_name()};
return query.execute(Database::db);
return query.execute(*Database::db);
}
static MucLogLineTable muc_log_lines;
......@@ -142,7 +144,7 @@ class Database
static IrcServerOptionsTable irc_server_options;
static IrcChannelOptionsTable irc_channel_options;
static RosterTable roster;
static sqlite3* db;
static std::unique_ptr<DatabaseEngine> db;
/**
* Some caches, to avoid doing very frequent query requests for a few options.
......@@ -177,6 +179,11 @@ class Database
Database::encoding_in_cache.clear();
}
static auto raw_exec(const std::string& query)
{
Database::db->raw_exec(query);
}
private:
static std::string gen_uuid();
static std::map<CacheKey, EncodingIn::real_type> encoding_in_cache;
......
#pragma once
/**
* Interface to provide non-portable behaviour, specific to each
* database engine we want to support.
*
* Everything else (all portable stuf) should go outside of this class.
*/
#include <database/statement.hpp>
#include <memory>
#include <string>
#include <vector>
#include <tuple>
#include <set>
class DatabaseEngine
{
public:
DatabaseEngine() = default;
virtual ~DatabaseEngine() = default;
DatabaseEngine(const DatabaseEngine&) = delete;
DatabaseEngine& operator=(const DatabaseEngine&) = delete;
DatabaseEngine(DatabaseEngine&&) = delete;
DatabaseEngine& operator=(DatabaseEngine&&) = delete;