Merge branch 'postgresql' into 'master'

Add postgresql support

Closes #3237

See merge request louiz/biboumi!18
This commit is contained in:
louiz’
2017-12-16 16:32:32 +01:00
42 changed files with 1156 additions and 367 deletions
+10 -4
View File
@@ -17,6 +17,7 @@ variables:
SYSTEMD: "-DWITH_SYSTEMD=1" SYSTEMD: "-DWITH_SYSTEMD=1"
LIBIDN: "-DWITH_LIBIDN=1" LIBIDN: "-DWITH_LIBIDN=1"
SQLITE3: "-DWITH_SQLITE3=1" SQLITE3: "-DWITH_SQLITE3=1"
POSTGRESQL: "-WITH_POSTGRESQL=1"
# #
## Build jobs ## Build jobs
@@ -27,10 +28,10 @@ variables:
tags: tags:
- docker - docker
script: 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/ - mkdir build/
- cd 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 everything -j$(nproc || echo 1)
- make coverage_check -j$(nproc || echo 1) - make coverage_check -j$(nproc || echo 1)
artifacts: artifacts:
@@ -74,19 +75,25 @@ build:2:
build:3: build:3:
variables: variables:
SQLITE3: "-DWITHOUT_SQLITE3=1" SQLITE3: "-DWITHOUT_SQLITE3=1"
TEST_POSTGRES_URI: "postgres@postgres/postgres"
services:
- postgres:latest
<<: *fedora_build <<: *fedora_build
build:4: build:4:
variables: variables:
SQLITE3: "-DWITHOUT_SQLITE3=1" SQLITE3: "-DWITHOUT_SQLITE3=1"
POSTGRESQL: "-DWITHOUT_POSTGRESQL=1"
BOTAN: "-DWITHOUT_BOTAN=1" BOTAN: "-DWITHOUT_BOTAN=1"
LIBIDN: "-DWITHOUT_LIBIDN=1" LIBIDN: "-DWITHOUT_LIBIDN=1"
<<: *fedora_build <<: *fedora_build
build:5: build:5:
variables: variables:
SQLITE3: "-DWITHOUT_SQLITE3=1"
UDNS: "-DWITHOUT_UDNS=1" UDNS: "-DWITHOUT_UDNS=1"
TEST_POSTGRES_URI: "postgres@postgres/postgres"
services:
- postgres:latest
<<: *fedora_build <<: *fedora_build
build:6: build:6:
@@ -100,7 +107,6 @@ build:7:
UDNS: "-DWITHOUT_UDNS=1" UDNS: "-DWITHOUT_UDNS=1"
<<: *fedora_build <<: *fedora_build
# #
## Test jobs ## Test jobs
# #
+19 -2
View File
@@ -14,7 +14,7 @@ endif()
# #
## Find optional instrumentation libraries that will be used in debug only ## 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) find_library(LIBUBSAN NAMES ubsan libubsan.so.0)
# #
@@ -130,6 +130,12 @@ elseif(NOT WITHOUT_SQLITE3)
find_package(SQLITE3) find_package(SQLITE3)
endif() 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 ## Set all the include directories, depending on what libraries are used
# #
@@ -187,12 +193,17 @@ file(GLOB source_network
src/network/*.[hc]pp) src/network/*.[hc]pp)
add_library(network OBJECT ${source_network}) add_library(network OBJECT ${source_network})
if(SQLITE3_FOUND) if(SQLITE3_FOUND OR PQ_FOUND)
file(GLOB source_database file(GLOB source_database
src/database/*.[hc]pp) src/database/*.[hc]pp)
add_library(database OBJECT ${source_database}) add_library(database OBJECT ${source_database})
if(SQLITE3_FOUND)
include_directories(database ${SQLITE3_INCLUDE_DIRS}) include_directories(database ${SQLITE3_INCLUDE_DIRS})
endif()
if(PQ_FOUND)
include_directories(database ${PQ_INCLUDE_DIRS})
endif()
set(USE_DATABASE TRUE) set(USE_DATABASE TRUE)
else() else()
add_library(database OBJECT "") add_library(database OBJECT "")
@@ -260,8 +271,14 @@ if(LIBIDN_FOUND)
target_link_libraries(test_suite ${LIBIDN_LIBRARIES}) target_link_libraries(test_suite ${LIBIDN_LIBRARIES})
endif() endif()
if(USE_DATABASE) if(USE_DATABASE)
if(SQLITE3_FOUND)
target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES}) target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES})
target_link_libraries(test_suite ${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() endif()
# Define a __FILENAME__ macro with the relative path (from the base project directory) # Define a __FILENAME__ macro with the relative path (from the base project directory)
+7 -4
View File
@@ -32,10 +32,12 @@ libiconv_
libuuid_ libuuid_
Generate unique IDs Generate unique IDs
sqlite3_ (option, but highly recommended) sqlite3_
Provides a way to store various options in a (sqlite3) database. Each user or
of the gateway can store their own values (for example their prefered port, libpq_
or their IRC password). Without this dependency, many interesting features 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. are missing.
libidn_ (optional, but recommended) libidn_ (optional, but recommended)
@@ -165,3 +167,4 @@ to use biboumi.
.. _systemd: https://www.freedesktop.org/wiki/Software/systemd/ .. _systemd: https://www.freedesktop.org/wiki/Software/systemd/
.. _biboumi.1.rst: doc/biboumi.1.rst .. _biboumi.1.rst: doc/biboumi.1.rst
.. _gcrypt: https://www.gnu.org/software/libgcrypt/ .. _gcrypt: https://www.gnu.org/software/libgcrypt/
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
+43
View File
@@ -0,0 +1,43 @@
# - 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)
+14
View File
@@ -77,6 +77,20 @@ port
The TCP port to use to connect to the local XMPP component. The default The TCP port to use to connect to the local XMPP component. The default
value is 5347. 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 admin
----- -----
+2 -1
View File
@@ -32,7 +32,8 @@ RUN apk add --no-cache g++\
openssl\ openssl\
libressl-dev\ libressl-dev\
zlib-dev\ zlib-dev\
curl curl\
postgresql-dev
# Install botan # Install botan
RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
+2 -1
View File
@@ -39,7 +39,8 @@ RUN apt install -y g++\
openssl\ openssl\
zlib1g-dev\ zlib1g-dev\
libssl-dev\ libssl-dev\
curl curl\
libpq-dev
# Install botan # Install botan
RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
+1
View File
@@ -39,6 +39,7 @@ RUN dnf --refresh install -y\
openssl-devel\ openssl-devel\
which\ which\
java-1.8.0-openjdk\ java-1.8.0-openjdk\
postgresql-devel\
&& dnf clean all && dnf clean all
# Install botan # Install botan
+2
View File
@@ -13,6 +13,7 @@ RUN apk add --no-cache\
make\ make\
udns-dev\ udns-dev\
sqlite-dev\ sqlite-dev\
postgresql-dev\
libuuid\ libuuid\
util-linux-dev\ util-linux-dev\
expat-dev\ expat-dev\
@@ -30,6 +31,7 @@ RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./bibou
-DWITH_BOTAN=1\ -DWITH_BOTAN=1\
-DWITH_SQLITE3=1\ -DWITH_SQLITE3=1\
-DWITH_LIBIDN=1\ -DWITH_LIBIDN=1\
-DWITH_POSTGRESQL=1\
&& make -j8 && make install && rm -rf /biboumi && make -j8 && make install && rm -rf /biboumi
RUN adduser biboumi -D -h /home/biboumi RUN adduser biboumi -D -h /home/biboumi
+24 -1
View File
@@ -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_PASSWORD: Sets the value of the *password* option.
* BIBOUMI_ADMIN: Sets the value of the *admin* 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_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: 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 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. 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/ chown -R 1000:1000 database/
chmod 777 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
```
+2
View File
@@ -6,6 +6,8 @@
#cmakedefine BOTAN_FOUND #cmakedefine BOTAN_FOUND
#cmakedefine GCRYPT_FOUND #cmakedefine GCRYPT_FOUND
#cmakedefine UDNS_FOUND #cmakedefine UDNS_FOUND
#cmakedefine PQ_FOUND
#cmakedefine SQLITE3_FOUND
#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" #cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}"
#cmakedefine PROJECT_NAME "${PROJECT_NAME}" #cmakedefine PROJECT_NAME "${PROJECT_NAME}"
#cmakedefine HAS_GET_TIME #cmakedefine HAS_GET_TIME
+1
View File
@@ -1031,6 +1031,7 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam
(void)hostname; (void)hostname;
(void)chan_name; (void)chan_name;
(void)resource; (void)resource;
(void)history_limit;
#endif #endif
} }
+7 -2
View File
@@ -13,5 +13,10 @@ struct Column
T value{}; T value{};
}; };
struct Id: Column<std::size_t> { static constexpr auto name = "id_"; struct Id: Column<std::size_t> {
static constexpr auto options = "PRIMARY KEY AUTOINCREMENT"; }; 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) {}
};
+5 -9
View File
@@ -2,11 +2,10 @@
#include <database/query.hpp> #include <database/query.hpp>
#include <database/table.hpp> #include <database/table.hpp>
#include <database/statement.hpp>
#include <string> #include <string>
#include <sqlite3.h>
struct CountQuery: public Query struct CountQuery: public Query
{ {
CountQuery(std::string name): CountQuery(std::string name):
@@ -15,20 +14,17 @@ struct CountQuery: public Query
this->body += std::move(name); 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; int64_t res = 0;
if (sqlite3_step(statement.get()) == SQLITE_ROW) if (statement->step() != StepResult::Error)
res = sqlite3_column_int64(statement.get(), 0); res = statement->get_column_int64(0);
else else
{ {
log_error("Count request didn’t return a result"); log_error("Count request didn’t return a result");
return 0; return 0;
} }
if (sqlite3_step(statement.get()) != SQLITE_DONE)
log_warning("Count request returned more than one result.");
return res; return res;
} }
}; };
+39 -36
View File
@@ -7,16 +7,19 @@
#include <utils/time.hpp> #include <utils/time.hpp>
#include <config/config.hpp> #include <config/config.hpp>
#include <database/sqlite3_engine.hpp>
#include <database/postgresql_engine.hpp>
#include <database/engine.hpp>
#include <database/index.hpp> #include <database/index.hpp>
#include <sqlite3.h> #include <memory>
sqlite3* Database::db; std::unique_ptr<DatabaseEngine> Database::db;
Database::MucLogLineTable Database::muc_log_lines("MucLogLine_"); Database::MucLogLineTable Database::muc_log_lines("muclogline_");
Database::GlobalOptionsTable Database::global_options("GlobalOptions_"); Database::GlobalOptionsTable Database::global_options("globaloptions_");
Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_"); Database::IrcServerOptionsTable Database::irc_server_options("ircserveroptions_");
Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_"); Database::IrcChannelOptionsTable Database::irc_channel_options("ircchanneloptions_");
Database::RosterTable Database::roster("roster"); Database::RosterTable Database::roster("roster");
std::map<Database::CacheKey, Database::EncodingIn::real_type> Database::encoding_in_cache{}; 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. // Try to open the specified database.
// Close and replace the previous database pointer if it succeeded. If it did // Close and replace the previous database pointer if it succeeded. If it did
// not, just leave things untouched // not, just leave things untouched
sqlite3* new_db; std::unique_ptr<DatabaseEngine> new_db;
auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); static const auto psql_prefix = "postgresql://"s;
Database::close(); static const auto psql_prefix2 = "postgres://"s;
if (res != SQLITE_OK) if ((filename.substr(0, psql_prefix.size()) == psql_prefix) ||
{ (filename.substr(0, psql_prefix2.size()) == psql_prefix2))
log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db)); new_db = PostgresqlEngine::open(filename);
sqlite3_close(new_db); else
throw std::runtime_error(""); new_db = Sqlite3Engine::open(filename);
} if (!new_db)
Database::db = new_db; return;
Database::muc_log_lines.create(Database::db); Database::db = std::move(new_db);
Database::muc_log_lines.upgrade(Database::db); Database::muc_log_lines.create(*Database::db);
Database::global_options.create(Database::db); Database::muc_log_lines.upgrade(*Database::db);
Database::global_options.upgrade(Database::db); Database::global_options.create(*Database::db);
Database::irc_server_options.create(Database::db); Database::global_options.upgrade(*Database::db);
Database::irc_server_options.upgrade(Database::db); Database::irc_server_options.create(*Database::db);
Database::irc_channel_options.create(Database::db); Database::irc_server_options.upgrade(*Database::db);
Database::irc_channel_options.upgrade(Database::db); Database::irc_channel_options.create(*Database::db);
Database::roster.create(Database::db); Database::irc_channel_options.upgrade(*Database::db);
Database::roster.upgrade(Database::db); Database::roster.create(*Database::db);
create_index<Database::Owner, Database::IrcChanName, Database::IrcServerName>(Database::db, "archive_index", Database::muc_log_lines.get_name()); 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; request.where() << Owner{} << "=" << owner;
Database::GlobalOptions options{Database::global_options.get_name()}; Database::GlobalOptions options{Database::global_options.get_name()};
auto result = request.execute(Database::db); auto result = request.execute(*Database::db);
if (result.size() == 1) if (result.size() == 1)
options = result.front(); options = result.front();
else else
@@ -73,7 +77,7 @@ Database::IrcServerOptions Database::get_irc_server_options(const std::string& o
request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server; request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
Database::IrcServerOptions options{Database::irc_server_options.get_name()}; 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) if (result.size() == 1)
options = result.front(); options = result.front();
else else
@@ -91,7 +95,7 @@ Database::IrcChannelOptions Database::get_irc_channel_options(const std::string&
" and " << Server{} << "=" << server <<\ " and " << Server{} << "=" << server <<\
" and " << Channel{} << "=" << channel; " and " << Channel{} << "=" << channel;
Database::IrcChannelOptions options{Database::irc_channel_options.get_name()}; 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) if (result.size() == 1)
options = result.front(); options = result.front();
else else
@@ -186,7 +190,7 @@ std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owne
if (limit >= 0) if (limit >= 0)
request.limit() << limit; request.limit() << limit;
auto result = request.execute(Database::db); auto result = request.execute(*Database::db);
return {result.crbegin(), result.crend()}; 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 << \ query << " WHERE " << Database::RemoteJid{} << "=" << remote << \
" AND " << Database::LocalJid{} << "=" << local; " 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) 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 << \ query.where() << Database::LocalJid{} << "=" << local << \
" and " << Database::RemoteJid{} << "=" << remote; " and " << Database::RemoteJid{} << "=" << remote;
auto res = query.execute(Database::db); auto res = query.execute(*Database::db);
return !res.empty(); return !res.empty();
} }
@@ -226,19 +230,18 @@ std::vector<Database::RosterItem> Database::get_contact_list(const std::string&
auto query = Database::roster.select(); auto query = Database::roster.select();
query.where() << Database::LocalJid{} << "=" << local; query.where() << Database::LocalJid{} << "=" << local;
return query.execute(Database::db); return query.execute(*Database::db);
} }
std::vector<Database::RosterItem> Database::get_full_roster() std::vector<Database::RosterItem> Database::get_full_roster()
{ {
auto query = Database::roster.select(); auto query = Database::roster.select();
return query.execute(Database::db); return query.execute(*Database::db);
} }
void Database::close() void Database::close()
{ {
sqlite3_close(Database::db);
Database::db = nullptr; Database::db = nullptr;
} }
+20 -13
View File
@@ -7,6 +7,8 @@
#include <database/column.hpp> #include <database/column.hpp>
#include <database/count_query.hpp> #include <database/count_query.hpp>
#include <database/engine.hpp>
#include <utils/optional_bool.hpp> #include <utils/optional_bool.hpp>
#include <chrono> #include <chrono>
@@ -25,11 +27,11 @@ class Database
struct Owner: Column<std::string> { static constexpr auto name = "owner_"; }; 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 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_"; }; 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_"; struct Ports: Column<std::string> { static constexpr auto name = "ports_";
Ports(): Column<std::string>("6667") {} }; 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") {} }; TlsPorts(): Column<std::string>("6697;6670") {} };
struct Username: Column<std::string> { static constexpr auto name = "username_"; }; struct Username: Column<std::string> { static constexpr auto name = "username_"; };
struct Realname: Column<std::string> { static constexpr auto name = "realname_"; }; 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) {} }; 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) {}}; 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) {} }; VerifyCert(): Column<bool>(true) {} };
struct Persistent: Column<bool> { static constexpr auto name = "persistent_"; struct Persistent: Column<bool> { static constexpr auto name = "persistent_";
@@ -134,7 +136,7 @@ class Database
static int64_t count(const TableType& table) static int64_t count(const TableType& table)
{ {
CountQuery query{table.get_name()}; CountQuery query{table.get_name()};
return query.execute(Database::db); return query.execute(*Database::db);
} }
static MucLogLineTable muc_log_lines; static MucLogLineTable muc_log_lines;
@@ -142,7 +144,7 @@ class Database
static IrcServerOptionsTable irc_server_options; static IrcServerOptionsTable irc_server_options;
static IrcChannelOptionsTable irc_channel_options; static IrcChannelOptionsTable irc_channel_options;
static RosterTable roster; 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. * Some caches, to avoid doing very frequent query requests for a few options.
@@ -177,6 +179,11 @@ class Database
Database::encoding_in_cache.clear(); Database::encoding_in_cache.clear();
} }
static auto raw_exec(const std::string& query)
{
Database::db->raw_exec(query);
}
private: private:
static std::string gen_uuid(); static std::string gen_uuid();
static std::map<CacheKey, EncodingIn::real_type> encoding_in_cache; static std::map<CacheKey, EncodingIn::real_type> encoding_in_cache;
+41
View File
@@ -0,0 +1,41 @@
#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;
virtual std::set<std::string> get_all_columns_from_table(const std::string& table_name) = 0;
virtual std::tuple<bool, std::string> raw_exec(const std::string& query) = 0;
virtual std::unique_ptr<Statement> prepare(const std::string& query) = 0;
virtual void extract_last_insert_rowid(Statement& statement) = 0;
virtual std::string get_returning_id_sql_string(const std::string&)
{
return {};
}
virtual std::string id_column_type() = 0;
int64_t last_inserted_rowid{-1};
};
+9 -13
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <sqlite3.h> #include <database/engine.hpp>
#include <string> #include <string>
#include <tuple> #include <tuple>
@@ -25,18 +25,14 @@ add_column_name(std::string& out)
} }
template <typename... Columns> template <typename... Columns>
void create_index(sqlite3* db, const std::string& name, const std::string& table) void create_index(DatabaseEngine& db, const std::string& name, const std::string& table)
{ {
std::string res{"CREATE INDEX IF NOT EXISTS "}; std::string query{"CREATE INDEX IF NOT EXISTS "};
res += name + " ON " + table + "("; query += name + " ON " + table + "(";
add_column_name<0, Columns...>(res); add_column_name<0, Columns...>(query);
res += ")"; query += ")";
char* error; auto result = db.raw_exec(query);
const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error); if (std::get<0>(result) == false)
if (result != SQLITE_OK) log_error("Error executing query: ", std::get<1>(result));
{
log_error("Error executing query: ", error);
sqlite3_free(error);
}
} }
+50 -47
View File
@@ -10,64 +10,63 @@
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <sqlite3.h> template <std::size_t N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
template <int N, typename ColumnType, typename... T> update_autoincrement_id(std::tuple<T...>& columns, Statement& statement)
typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
actual_bind(Statement& statement, std::vector<std::string>& params, const std::tuple<T...>&)
{ {
const auto value = params.front(); using ColumnType = typename std::decay<decltype(std::get<N>(columns))>::type;
params.erase(params.begin()); if (std::is_same<ColumnType, Id>::value)
if (sqlite3_bind_text(statement.get(), N + 1, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT) != SQLITE_OK)
log_error("Failed to bind ", value, " to param ", N);
}
template <int N, typename ColumnType, typename... T>
typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
actual_bind(Statement& statement, std::vector<std::string>&, const std::tuple<T...>& columns)
{
auto&& column = std::get<Id>(columns);
if (column.value != 0)
{ {
if (sqlite3_bind_int64(statement.get(), N + 1, static_cast<sqlite3_int64>(column.value)) != SQLITE_OK) log_debug("EXTRACTING LAST ID");
log_error("Failed to bind ", column.value, " to id."); auto&& column = std::get<Id>(columns);
} }
else if (sqlite3_bind_null(statement.get(), N + 1) != SQLITE_OK) update_autoincrement_id<N+1>(columns, statement);
log_error("Failed to bind NULL to param ", N);
} }
template <std::size_t N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>&, Statement& statement)
{}
struct InsertQuery: public Query struct InsertQuery: public Query
{ {
InsertQuery(const std::string& name): template <typename... T>
Query("INSERT OR REPLACE INTO ") InsertQuery(const std::string& name, const std::tuple<T...>& columns):
Query("INSERT INTO ")
{ {
this->body += name; this->body += name;
this->insert_col_names(columns);
this->insert_values(columns);
} }
template <typename... T> template <typename... T>
void execute(const std::tuple<T...>& columns, sqlite3* db) void execute(DatabaseEngine& db, std::tuple<T...>& columns)
{ {
auto statement = this->prepare(db); auto statement = db.prepare(this->body);
{ this->bind_param(columns, *statement);
this->bind_param(columns, statement);
if (sqlite3_step(statement.get()) != SQLITE_DONE) if (statement->step() != StepResult::Error)
log_error("Failed to execute query: ", sqlite3_errmsg(db)); db.extract_last_insert_rowid(*statement);
} else
log_error("Failed to extract the rowid from the last INSERT");
} }
template <int N=0, typename... T> template <int N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type typename std::enable_if<N < sizeof...(T), void>::type
bind_param(const std::tuple<T...>& columns, Statement& statement) bind_param(const std::tuple<T...>& columns, Statement& statement, int index=1)
{ {
using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type; auto&& column = std::get<N>(columns);
using ColumnType = std::decay_t<decltype(column)>;
actual_bind<N, ColumnType>(statement, this->params, columns); if (!std::is_same<ColumnType, Id>::value)
this->bind_param<N+1>(columns, statement); actual_bind(statement, column.value, index++);
this->bind_param<N+1>(columns, statement, index);
} }
template <int N=0, typename... T> template <int N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type typename std::enable_if<N == sizeof...(T), void>::type
bind_param(const std::tuple<T...>&, Statement&) bind_param(const std::tuple<T...>&, Statement&, int)
{} {}
template <typename... T> template <typename... T>
@@ -80,18 +79,21 @@ struct InsertQuery: public Query
template <int N=0, typename... T> template <int N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type typename std::enable_if<N < sizeof...(T), void>::type
insert_value(const std::tuple<T...>& columns) insert_value(const std::tuple<T...>& columns, int index=1)
{ {
this->body += "?"; using ColumnType = std::decay_t<decltype(std::get<N>(columns))>;
if (!std::is_same<ColumnType, Id>::value)
{
this->body += "$" + std::to_string(index++);
if (N != sizeof...(T) - 1) if (N != sizeof...(T) - 1)
this->body += ","; this->body += ", ";
this->body += " "; }
add_param(*this, std::get<N>(columns)); this->insert_value<N+1>(columns, index);
this->insert_value<N+1>(columns);
} }
template <int N=0, typename... T> template <int N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type typename std::enable_if<N == sizeof...(T), void>::type
insert_value(const std::tuple<T...>&) insert_value(const std::tuple<T...>&, const int)
{ } { }
template <typename... T> template <typename... T>
@@ -99,27 +101,28 @@ struct InsertQuery: public Query
{ {
this->body += " ("; this->body += " (";
this->insert_col_name(columns); this->insert_col_name(columns);
this->body += ")\n"; this->body += ")";
} }
template <int N=0, typename... T> template <int N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type typename std::enable_if<N < sizeof...(T), void>::type
insert_col_name(const std::tuple<T...>& columns) insert_col_name(const std::tuple<T...>& columns)
{ {
using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type; using ColumnType = std::decay_t<decltype(std::get<N>(columns))>;
if (!std::is_same<ColumnType, Id>::value)
{
this->body += ColumnType::name; this->body += ColumnType::name;
if (N < (sizeof...(T) - 1)) if (N < (sizeof...(T) - 1))
this->body += ", "; this->body += ", ";
}
this->insert_col_name<N+1>(columns); this->insert_col_name<N+1>(columns);
} }
template <int N=0, typename... T> template <int N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type typename std::enable_if<N == sizeof...(T), void>::type
insert_col_name(const std::tuple<T...>&) insert_col_name(const std::tuple<T...>&)
{} {}
private:
}; };
+88
View File
@@ -0,0 +1,88 @@
#include <biboumi.h>
#ifdef PQ_FOUND
#include <utils/scopeguard.hpp>
#include <database/postgresql_engine.hpp>
#include <database/postgresql_statement.hpp>
#include <logger/logger.hpp>
PostgresqlEngine::PostgresqlEngine(PGconn*const conn):
conn(conn)
{}
PostgresqlEngine::~PostgresqlEngine()
{
PQfinish(this->conn);
}
std::unique_ptr<DatabaseEngine> PostgresqlEngine::open(const std::string& conninfo)
{
log_debug("trying to open: ", conninfo);
PGconn* con = PQconnectdb(conninfo.data());
if (!con)
{
log_error("Failed to allocate a Postgresql connection");
throw std::runtime_error("");
}
const auto status = PQstatus(con);
if (status != CONNECTION_OK)
{
const char* errmsg = PQerrorMessage(con);
log_error("Postgresql connection failed: ", errmsg);
throw std::runtime_error("failed to open connection.");
}
return std::make_unique<PostgresqlEngine>(con);
}
std::set<std::string> PostgresqlEngine::get_all_columns_from_table(const std::string& table_name)
{
const auto query = "SELECT column_name from information_schema.columns where table_name='" + table_name + "'";
auto statement = this->prepare(query);
std::set<std::string> columns;
while (statement->step() == StepResult::Row)
columns.insert(statement->get_column_text(0));
log_debug("found ", columns.size(), " columns.");
return columns;
}
std::tuple<bool, std::string> PostgresqlEngine::raw_exec(const std::string& query)
{
log_debug("raw_exec:", query);
PGresult* res = PQexec(this->conn, query.data());
auto sg = utils::make_scope_guard([res](){
PQclear(res);
});
auto res_status = PQresultStatus(res);
if (res_status != PGRES_COMMAND_OK)
return std::make_tuple(false, PQresultErrorMessage(res));
return std::make_tuple(true, std::string{});
}
std::unique_ptr<Statement> PostgresqlEngine::prepare(const std::string& query)
{
return std::make_unique<PostgresqlStatement>(query, this->conn);
}
void PostgresqlEngine::extract_last_insert_rowid(Statement& statement)
{
this->last_inserted_rowid = statement.get_column_int64(0);
}
std::string PostgresqlEngine::get_returning_id_sql_string(const std::string& col_name)
{
return " RETURNING " + col_name;
}
std::string PostgresqlEngine::id_column_type()
{
return "SERIAL";
}
#endif
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <biboumi.h>
#include <string>
#include <stdexcept>
#include <memory>
#include <database/statement.hpp>
#include <database/engine.hpp>
#include <tuple>
#include <set>
#ifdef PQ_FOUND
#include <libpq-fe.h>
class PostgresqlEngine: public DatabaseEngine
{
public:
PostgresqlEngine(PGconn*const conn);
~PostgresqlEngine();
static std::unique_ptr<DatabaseEngine> open(const std::string& string);
std::set<std::string> get_all_columns_from_table(const std::string& table_name) override final;
std::tuple<bool, std::string> raw_exec(const std::string& query) override final;
std::unique_ptr<Statement> prepare(const std::string& query) override;
void extract_last_insert_rowid(Statement& statement) override;
std::string get_returning_id_sql_string(const std::string& col_name) override;
std::string id_column_type() override;
private:
PGconn* const conn;
};
#else
class PostgresqlEngine
{
public:
static std::unique_ptr<DatabaseEngine> open(const std::string& string)
{
throw std::runtime_error("Cannot open postgresql database "s + string + ": biboumi is not compiled with libpq.");
}
};
#endif
+132
View File
@@ -0,0 +1,132 @@
#pragma once
#include <database/statement.hpp>
#include <logger/logger.hpp>
#include <libpq-fe.h>
class PostgresqlStatement: public Statement
{
public:
PostgresqlStatement(std::string body, PGconn*const conn):
body(std::move(body)),
conn(conn)
{}
~PostgresqlStatement()
{
PQclear(this->result);
this->result = nullptr;
}
PostgresqlStatement(const PostgresqlStatement&) = delete;
PostgresqlStatement& operator=(const PostgresqlStatement&) = delete;
PostgresqlStatement(PostgresqlStatement&& other) = delete;
PostgresqlStatement& operator=(PostgresqlStatement&& other) = delete;
StepResult step() override final
{
if (!this->executed)
{
this->current_tuple = 0;
this->executed = true;
if (!this->execute())
return StepResult::Error;
}
else
{
this->current_tuple++;
}
if (this->current_tuple < PQntuples(this->result))
return StepResult::Row;
return StepResult::Done;
}
int64_t get_column_int64(const int col) override
{
const char* result = PQgetvalue(this->result, this->current_tuple, col);
std::istringstream iss;
iss.str(result);
int64_t res;
iss >> res;
return res;
}
std::string get_column_text(const int col) override
{
const char* result = PQgetvalue(this->result, this->current_tuple, col);
return result;
}
int get_column_int(const int col) override
{
const char* result = PQgetvalue(this->result, this->current_tuple, col);
std::istringstream iss;
iss.str(result);
int res;
iss >> res;
return res;
}
void bind(std::vector<std::string> params) override
{
this->params = std::move(params);
}
bool bind_text(const int, const std::string& data) override
{
this->params.push_back(data);
return true;
}
bool bind_int64(const int, const std::int64_t value) override
{
this->params.push_back(std::to_string(value));
return true;
}
bool bind_null(const int) override
{
this->params.push_back("NULL");
return true;
}
private:
private:
bool execute()
{
std::vector<const char*> params;
params.reserve(this->params.size());
for (const auto& param: this->params)
{
log_debug("param:", param);
params.push_back(param.data());
}
log_debug("body: ", body);
const int param_size = static_cast<int>(this->params.size());
this->result = PQexecParams(this->conn, this->body.data(),
param_size,
nullptr,
params.data(),
nullptr,
nullptr,
0);
const auto status = PQresultStatus(this->result);
if (status == PGRES_TUPLES_OK)
{
log_debug("PGRES_TUPLES_OK");
}
else if (status != PGRES_COMMAND_OK)
{
log_error("Failed to execute command: ", PQresultErrorMessage(this->result));
return false;
}
return true;
}
bool executed{false};
std::string body;
PGconn*const conn;
std::vector<std::string> params;
PGresult* result{nullptr};
int current_tuple{0};
};
+25 -4
View File
@@ -1,9 +1,29 @@
#include <database/query.hpp> #include <database/query.hpp>
#include <database/column.hpp> #include <database/column.hpp>
template <> void actual_bind(Statement& statement, const std::string& value, int index)
void add_param<Id>(Query&, const Id&) {
{} log_debug("binding string:", value, " to col ", index);
statement.bind_text(index, value);
}
void actual_bind(Statement& statement, const std::size_t value, int index)
{
log_debug("binding size_t:", value);
statement.bind_int64(index, value);
}
void actual_bind(Statement& statement, const OptionalBool& value, int index)
{
log_debug("binding optional_t:", value.to_string());
if (!value.is_set)
statement.bind_int64(index, 0);
else if (value.value)
statement.bind_int64(index, 1);
else
statement.bind_int64(index, -1);
}
void actual_add_param(Query& query, const std::string& val) void actual_add_param(Query& query, const std::string& val)
{ {
@@ -28,7 +48,8 @@ Query& operator<<(Query& query, const char* str)
Query& operator<<(Query& query, const std::string& str) Query& operator<<(Query& query, const std::string& str)
{ {
query.body += "?"; query.body += "$" + std::to_string(query.current_param);
query.current_param++;
actual_add_param(query, str); actual_add_param(query, str);
return query; return query;
} }
+6 -33
View File
@@ -9,54 +9,27 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <sqlite3.h> void actual_bind(Statement& statement, const std::string& value, int index);
void actual_bind(Statement& statement, const std::size_t value, int index);
void actual_bind(Statement& statement, const OptionalBool& value, int index);
struct Query struct Query
{ {
std::string body; std::string body;
std::vector<std::string> params; std::vector<std::string> params;
int current_param{1};
Query(std::string str): Query(std::string str):
body(std::move(str)) body(std::move(str))
{} {}
Statement prepare(sqlite3* db)
{
sqlite3_stmt* stmt;
auto res = sqlite3_prepare(db, this->body.data(), static_cast<int>(this->body.size()) + 1,
&stmt, nullptr);
if (res != SQLITE_OK)
{
log_error("Error preparing statement: ", sqlite3_errmsg(db));
return nullptr;
}
Statement statement(stmt);
int i = 1;
for (const std::string& param: this->params)
{
if (sqlite3_bind_text(statement.get(), i, param.data(), static_cast<int>(param.size()), SQLITE_TRANSIENT) != SQLITE_OK)
log_error("Failed to bind ", param, " to param ", i);
i++;
}
return statement;
}
void execute(sqlite3* db)
{
auto statement = this->prepare(db);
while (sqlite3_step(statement.get()) != SQLITE_DONE)
;
}
}; };
template <typename ColumnType> template <typename ColumnType>
void add_param(Query& query, const ColumnType& column) void add_param(Query& query, const ColumnType& column)
{ {
std::cout << "add_param<ColumnType>" << std::endl;
actual_add_param(query, column.value); actual_add_param(query, column.value);
} }
template <>
void add_param<Id>(Query& query, const Id& column);
template <typename T> template <typename T>
void actual_add_param(Query& query, const T& val) void actual_add_param(Query& query, const T& val)
@@ -81,7 +54,7 @@ template <typename Integer>
typename std::enable_if<std::is_integral<Integer>::value, Query&>::type typename std::enable_if<std::is_integral<Integer>::value, Query&>::type
operator<<(Query& query, const Integer& i) operator<<(Query& query, const Integer& i)
{ {
query.body += "?"; query.body += "$" + std::to_string(query.current_param++);
actual_add_param(query, i); actual_add_param(query, i);
return query; return query;
} }
+37 -38
View File
@@ -1,40 +1,13 @@
#pragma once #pragma once
#include <database/insert_query.hpp> #include <database/insert_query.hpp>
#include <database/update_query.hpp>
#include <logger/logger.hpp> #include <logger/logger.hpp>
#include <utils/is_one_of.hpp>
#include <type_traits> #include <type_traits>
#include <sqlite3.h>
template <typename ColumnType, typename... T>
typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
update_id(std::tuple<T...>&, sqlite3*)
{}
template <typename ColumnType, typename... T>
typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
update_id(std::tuple<T...>& columns, sqlite3* db)
{
auto&& column = std::get<ColumnType>(columns);
auto res = sqlite3_last_insert_rowid(db);
column.value = static_cast<Id::real_type>(res);
}
template <std::size_t N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>& columns, sqlite3* db)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
update_id<ColumnType>(columns, db);
update_autoincrement_id<N+1>(columns, db);
}
template <std::size_t N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>&, sqlite3*)
{}
template <typename... T> template <typename... T>
struct Row struct Row
{ {
@@ -56,17 +29,43 @@ struct Row
return col.value; return col.value;
} }
void save(sqlite3* db) template <bool Coucou=true>
void save(std::unique_ptr<DatabaseEngine>& db, typename std::enable_if<!is_one_of<Id, T...> && Coucou>::type* = nullptr)
{ {
InsertQuery query(this->table_name); this->insert(*db);
query.insert_col_names(this->columns);
query.insert_values(this->columns);
query.execute(this->columns, db);
update_autoincrement_id(this->columns, db);
} }
template <bool Coucou=true>
void save(std::unique_ptr<DatabaseEngine>& db, typename std::enable_if<is_one_of<Id, T...> && Coucou>::type* = nullptr)
{
const Id& id = std::get<Id>(this->columns);
if (id.value == Id::unset_value)
{
this->insert(*db);
std::get<Id>(this->columns).value = db->last_inserted_rowid;
}
else
this->update(*db);
}
private:
void insert(DatabaseEngine& db)
{
InsertQuery query(this->table_name, this->columns);
// Ugly workaround for non portable stuff
query.body += db.get_returning_id_sql_string(Id::name);
query.execute(db, this->columns);
}
void update(DatabaseEngine& db)
{
UpdateQuery query(this->table_name, this->columns);
query.execute(db, this->columns);
}
public:
std::tuple<T...> columns; std::tuple<T...> columns;
std::string table_name; std::string table_name;
}; };
+15 -13
View File
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <database/engine.hpp>
#include <database/statement.hpp> #include <database/statement.hpp>
#include <database/query.hpp> #include <database/query.hpp>
#include <logger/logger.hpp> #include <logger/logger.hpp>
@@ -10,32 +12,27 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <sqlite3.h>
using namespace std::string_literals; using namespace std::string_literals;
template <typename T> template <typename T>
typename std::enable_if<std::is_integral<T>::value, sqlite3_int64>::type typename std::enable_if<std::is_integral<T>::value, std::int64_t>::type
extract_row_value(Statement& statement, const int i) extract_row_value(Statement& statement, const int i)
{ {
return sqlite3_column_int64(statement.get(), i); return statement.get_column_int64(i);
} }
template <typename T> template <typename T>
typename std::enable_if<std::is_same<std::string, T>::value, T>::type typename std::enable_if<std::is_same<std::string, T>::value, T>::type
extract_row_value(Statement& statement, const int i) extract_row_value(Statement& statement, const int i)
{ {
const auto size = sqlite3_column_bytes(statement.get(), i); return statement.get_column_text(i);
const unsigned char* str = sqlite3_column_text(statement.get(), i);
std::string result(reinterpret_cast<const char*>(str), static_cast<std::size_t>(size));
return result;
} }
template <typename T> template <typename T>
typename std::enable_if<std::is_same<OptionalBool, T>::value, T>::type typename std::enable_if<std::is_same<OptionalBool, T>::value, T>::type
extract_row_value(Statement& statement, const int i) extract_row_value(Statement& statement, const int i)
{ {
const auto integer = sqlite3_column_int(statement.get(), i); const auto integer = statement.get_column_int(i);
OptionalBool result; OptionalBool result;
if (integer > 0) if (integer > 0)
result.set_value(true); result.set_value(true);
@@ -109,16 +106,21 @@ struct SelectQuery: public Query
return *this; return *this;
} }
auto execute(sqlite3* db) auto execute(DatabaseEngine& db)
{ {
auto statement = this->prepare(db);
std::vector<Row<T...>> rows; std::vector<Row<T...>> rows;
while (sqlite3_step(statement.get()) == SQLITE_ROW)
auto statement = db.prepare(this->body);
statement->bind(std::move(this->params));
while (statement->step() == StepResult::Row)
{ {
log_debug("one result.");
Row<T...> row(this->table_name); Row<T...> row(this->table_name);
extract_row_values(row, statement); extract_row_values(row, *statement);
rows.push_back(row); rows.push_back(row);
} }
return rows; return rows;
} }
+99
View File
@@ -0,0 +1,99 @@
#include <biboumi.h>
#ifdef SQLITE3_FOUND
#include <database/sqlite3_engine.hpp>
#include <database/sqlite3_statement.hpp>
#include <utils/tolower.hpp>
#include <logger/logger.hpp>
#include <vector>
Sqlite3Engine::Sqlite3Engine(sqlite3* db):
db(db)
{
}
Sqlite3Engine::~Sqlite3Engine()
{
sqlite3_close(this->db);
}
std::set<std::string> Sqlite3Engine::get_all_columns_from_table(const std::string& table_name)
{
std::set<std::string> result;
char* errmsg;
std::string query{"PRAGMA table_info(" + table_name + ")"};
int res = sqlite3_exec(this->db, query.data(), [](void* param, int columns_nb, char** columns, char**) -> int {
constexpr int name_column = 1;
std::set<std::string>* result = static_cast<std::set<std::string>*>(param);
if (name_column < columns_nb)
result->insert(utils::tolower(columns[name_column]));
return 0;
}, &result, &errmsg);
if (res != SQLITE_OK)
{
log_error("Error executing ", query, ": ", errmsg);
sqlite3_free(errmsg);
}
log_debug("List of columns in table ", table_name, ":");
for (const auto& c: result)
log_debug(c);
return result;
}
std::unique_ptr<DatabaseEngine> Sqlite3Engine::open(const std::string& filename)
{
sqlite3* new_db;
auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
if (res != SQLITE_OK)
{
log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db));
sqlite3_close(new_db);
throw std::runtime_error("");
}
return std::make_unique<Sqlite3Engine>(new_db);
}
std::tuple<bool, std::string> Sqlite3Engine::raw_exec(const std::string& query)
{
char* error;
const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error);
if (result != SQLITE_OK)
{
std::string err_msg(error);
sqlite3_free(error);
return std::make_tuple(false, err_msg);
}
return std::make_tuple(true, std::string{});
}
std::unique_ptr<Statement> Sqlite3Engine::prepare(const std::string& query)
{
sqlite3_stmt* stmt;
log_debug("SQLITE3: ", query);
auto res = sqlite3_prepare(db, query.data(), static_cast<int>(query.size()) + 1,
&stmt, nullptr);
if (res != SQLITE_OK)
{
log_error("Error preparing statement: ", sqlite3_errmsg(db));
return nullptr;
}
return std::make_unique<Sqlite3Statement>(stmt);
}
void Sqlite3Engine::extract_last_insert_rowid(Statement& statement)
{
this->last_inserted_rowid = sqlite3_last_insert_rowid(this->db);
log_debug("extracted inserted ID: ", this->last_inserted_rowid);
}
std::string Sqlite3Engine::id_column_type()
{
return "INTEGER PRIMARY KEY AUTOINCREMENT";
}
#endif
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <database/engine.hpp>
#include <database/statement.hpp>
#include <memory>
#include <string>
#include <tuple>
#include <set>
#include <biboumi.h>
#ifdef SQLITE3_FOUND
#include <sqlite3.h>
class Sqlite3Engine: public DatabaseEngine
{
public:
Sqlite3Engine(sqlite3* db);
~Sqlite3Engine();
static std::unique_ptr<DatabaseEngine> open(const std::string& string);
std::set<std::string> get_all_columns_from_table(const std::string& table_name) override final;
std::tuple<bool, std::string> raw_exec(const std::string& query) override final;
std::unique_ptr<Statement> prepare(const std::string& query) override;
void extract_last_insert_rowid(Statement& statement) override;
std::string id_column_type() override;
private:
sqlite3* const db;
};
#else
class Sqlite3Engine
{
public:
static std::unique_ptr<DatabaseEngine> open(const std::string& string)
{
throw std::runtime_error("Cannot open sqlite3 database "s + string + ": biboumi is not compiled with sqlite3 lib.");
}
};
#endif
+93
View File
@@ -0,0 +1,93 @@
#pragma once
#include <database/statement.hpp>
#include <logger/logger.hpp>
#include <sqlite3.h>
class Sqlite3Statement: public Statement
{
public:
Sqlite3Statement(sqlite3_stmt* stmt):
stmt(stmt) {}
~Sqlite3Statement()
{
sqlite3_finalize(this->stmt);
}
StepResult step() override final
{
auto res = sqlite3_step(this->get());
log_debug("step: ", res);
if (res == SQLITE_ROW)
return StepResult::Row;
else if (res == SQLITE_DONE)
return StepResult::Done;
else
return StepResult::Error;
}
void bind(std::vector<std::string> params) override
{
int i = 1;
for (const std::string& param: params)
{
if (sqlite3_bind_text(this->get(), i, param.data(), static_cast<int>(param.size()), SQLITE_TRANSIENT) != SQLITE_OK)
log_error("Failed to bind ", param, " to param ", i);
i++;
}
}
int64_t get_column_int64(const int col) override
{
return sqlite3_column_int64(this->get(), col);
}
std::string get_column_text(const int col) override
{
const auto size = sqlite3_column_bytes(this->get(), col);
const unsigned char* str = sqlite3_column_text(this->get(), col);
std::string result(reinterpret_cast<const char*>(str), static_cast<std::size_t>(size));
return result;
}
bool bind_text(const int pos, const std::string& data) override
{
return sqlite3_bind_text(this->get(), pos, data.data(), static_cast<int>(data.size()), SQLITE_TRANSIENT) == SQLITE_OK;
}
bool bind_int64(const int pos, const std::int64_t value) override
{
return sqlite3_bind_int64(this->get(), pos, static_cast<sqlite3_int64>(value)) == SQLITE_OK;
}
bool bind_null(const int pos) override
{
return sqlite3_bind_null(this->get(), pos) == SQLITE_OK;
}
int get_column_int(const int col) override
{
return sqlite3_column_int(this->get(), col);
}
Sqlite3Statement(const Sqlite3Statement&) = delete;
Sqlite3Statement& operator=(const Sqlite3Statement&) = delete;
Sqlite3Statement(Sqlite3Statement&& other):
stmt(other.stmt)
{
other.stmt = nullptr;
}
Sqlite3Statement& operator=(Sqlite3Statement&& other)
{
this->stmt = other.stmt;
other.stmt = nullptr;
return *this;
}
sqlite3_stmt* get()
{
return this->stmt;
}
private:
sqlite3_stmt* stmt;
int last_step_result{SQLITE_OK};
};
+20 -26
View File
@@ -1,35 +1,29 @@
#pragma once #pragma once
#include <sqlite3.h> #include <cstdint>
#include <string>
#include <vector>
enum class StepResult
{
Row,
Done,
Error,
};
class Statement class Statement
{ {
public: public:
Statement(sqlite3_stmt* stmt): virtual ~Statement() = default;
stmt(stmt) {} virtual StepResult step() = 0;
~Statement()
{
sqlite3_finalize(this->stmt);
}
Statement(const Statement&) = delete; virtual void bind(std::vector<std::string> params) = 0;
Statement& operator=(const Statement&) = delete;
Statement(Statement&& other):
stmt(other.stmt)
{
other.stmt = nullptr;
}
Statement& operator=(Statement&& other)
{
this->stmt = other.stmt;
other.stmt = nullptr;
return *this;
}
sqlite3_stmt* get()
{
return this->stmt;
}
private: virtual std::int64_t get_column_int64(const int col) = 0;
sqlite3_stmt* stmt; virtual std::string get_column_text(const int col) = 0;
virtual int get_column_int(const int col) = 0;
virtual bool bind_text(const int pos, const std::string& data) = 0;
virtual bool bind_int64(const int pos, const std::int64_t value) = 0;
virtual bool bind_null(const int pos) = 0;
}; };
-23
View File
@@ -1,23 +0,0 @@
#include <database/table.hpp>
std::set<std::string> get_all_columns_from_table(sqlite3* db, const std::string& table_name)
{
std::set<std::string> result;
char* errmsg;
std::string query{"PRAGMA table_info(" + table_name + ")"};
int res = sqlite3_exec(db, query.data(), [](void* param, int columns_nb, char** columns, char**) -> int {
constexpr int name_column = 1;
std::set<std::string>* result = static_cast<std::set<std::string>*>(param);
if (name_column < columns_nb)
result->insert(columns[name_column]);
return 0;
}, &result, &errmsg);
if (res != SQLITE_OK)
{
log_error("Error executing ", query, ": ", errmsg);
sqlite3_free(errmsg);
}
return result;
}
+48 -41
View File
@@ -1,7 +1,8 @@
#pragma once #pragma once
#include <database/engine.hpp>
#include <database/select_query.hpp> #include <database/select_query.hpp>
#include <database/type_to_sql.hpp>
#include <database/row.hpp> #include <database/row.hpp>
#include <algorithm> #include <algorithm>
@@ -10,22 +11,26 @@
using namespace std::string_literals; using namespace std::string_literals;
std::set<std::string> get_all_columns_from_table(sqlite3* db, const std::string& table_name); template <typename T>
std::string ToSQLType(DatabaseEngine& db)
template <typename ColumnType>
void add_column_to_table(sqlite3* db, const std::string& table_name)
{ {
const std::string name = ColumnType::name; if (std::is_same<T, Id>::value)
std::string query{"ALTER TABLE " + table_name + " ADD " + ColumnType::name + " " + TypeToSQLType<typename ColumnType::real_type>::type}; return db.id_column_type();
char* error; else if (std::is_same<typename T::real_type, std::string>::value)
const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error); return "TEXT";
if (result != SQLITE_OK) else
{ return "INTEGER";
log_error("Error adding column ", name, " to table ", table_name, ": ", error);
sqlite3_free(error);
}
} }
template <typename ColumnType>
void add_column_to_table(DatabaseEngine& db, const std::string& table_name)
{
const std::string name = ColumnType::name;
std::string query{"ALTER TABLE " + table_name + " ADD " + ColumnType::name + " " + ToSQLType<ColumnType>(db)};
auto res = db.raw_exec(query);
if (std::get<0>(res) == false)
log_error("Error adding column ", name, " to table ", table_name, ": ", std::get<1>(res));
}
template <typename ColumnType, decltype(ColumnType::options) = nullptr> template <typename ColumnType, decltype(ColumnType::options) = nullptr>
void append_option(std::string& s) void append_option(std::string& s)
@@ -50,27 +55,24 @@ class Table
name(std::move(name)) name(std::move(name))
{} {}
void upgrade(sqlite3* db) void upgrade(DatabaseEngine& db)
{ {
const auto existing_columns = get_all_columns_from_table(db, this->name); const auto existing_columns = db.get_all_columns_from_table(this->name);
add_column_if_not_exists(db, existing_columns); add_column_if_not_exists(db, existing_columns);
} }
void create(sqlite3* db) void create(DatabaseEngine& db)
{ {
std::string res{"CREATE TABLE IF NOT EXISTS "}; std::string query{"CREATE TABLE IF NOT EXISTS "};
res += this->name; query += this->name;
res += " (\n"; query += " (\n";
this->add_column_create(res); this->add_column_create(db, query);
res += ")"; query += ")";
char* error; log_debug("create:" , query);
const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error); auto result = db.raw_exec(query);
if (result != SQLITE_OK) if (std::get<0>(result) == false)
{ log_error("Error executing query: ", std::get<1>(result));
log_error("Error executing query: ", error);
sqlite3_free(error);
}
} }
RowType row() RowType row()
@@ -78,7 +80,7 @@ class Table
return {this->name}; return {this->name};
} }
SelectQuery<T...> select() auto select()
{ {
SelectQuery<T...> select(this->name); SelectQuery<T...> select(this->name);
return select; return select;
@@ -93,39 +95,44 @@ class Table
template <std::size_t N=0> template <std::size_t N=0>
typename std::enable_if<N < sizeof...(T), void>::type typename std::enable_if<N < sizeof...(T), void>::type
add_column_if_not_exists(sqlite3* db, const std::set<std::string>& existing_columns) add_column_if_not_exists(DatabaseEngine& db, const std::set<std::string>& existing_columns)
{ {
using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type; using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
if (existing_columns.count(ColumnType::name) != 1) if (existing_columns.count(ColumnType::name) == 0)
{
add_column_to_table<ColumnType>(db, this->name); add_column_to_table<ColumnType>(db, this->name);
}
add_column_if_not_exists<N+1>(db, existing_columns); add_column_if_not_exists<N+1>(db, existing_columns);
} }
template <std::size_t N=0> template <std::size_t N=0>
typename std::enable_if<N == sizeof...(T), void>::type typename std::enable_if<N == sizeof...(T), void>::type
add_column_if_not_exists(sqlite3*, const std::set<std::string>&) add_column_if_not_exists(DatabaseEngine&, const std::set<std::string>&)
{} {}
template <std::size_t N=0> template <std::size_t N=0>
typename std::enable_if<N < sizeof...(T), void>::type typename std::enable_if<N < sizeof...(T), void>::type
add_column_create(std::string& str) add_column_create(DatabaseEngine& db, std::string& str)
{ {
using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type; using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
using RealType = typename ColumnType::real_type; // using RealType = typename ColumnType::real_type;
str += ColumnType::name; str += ColumnType::name;
str += " "; str += " ";
str += TypeToSQLType<RealType>::type; // if (std::is_same<ColumnType, Id>::value)
append_option<ColumnType>(str); // {
// str += "INTEGER PRIMARY KEY AUTOINCREMENT";
// }
// else
// {
str += ToSQLType<ColumnType>(db);
// append_option<ColumnType>(str);
// }
if (N != sizeof...(T) - 1) if (N != sizeof...(T) - 1)
str += ","; str += ",";
str += "\n"; str += "\n";
add_column_create<N+1>(str); add_column_create<N+1>(db, str);
} }
template <std::size_t N=0> template <std::size_t N=0>
typename std::enable_if<N == sizeof...(T), void>::type typename std::enable_if<N == sizeof...(T), void>::type
add_column_create(std::string&) add_column_create(DatabaseEngine&, std::string&)
{ } { }
const std::string name; const std::string name;
-9
View File
@@ -1,9 +0,0 @@
#include <database/type_to_sql.hpp>
template <> const std::string TypeToSQLType<int>::type = "INTEGER";
template <> const std::string TypeToSQLType<std::size_t>::type = "INTEGER";
template <> const std::string TypeToSQLType<long>::type = "INTEGER";
template <> const std::string TypeToSQLType<long long>::type = "INTEGER";
template <> const std::string TypeToSQLType<bool>::type = "INTEGER";
template <> const std::string TypeToSQLType<std::string>::type = "TEXT";
template <> const std::string TypeToSQLType<OptionalBool>::type = "INTEGER";
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <utils/optional_bool.hpp>
#include <string>
template <typename T>
struct TypeToSQLType { static const std::string type; };
template <> const std::string TypeToSQLType<int>::type;
template <> const std::string TypeToSQLType<std::size_t>::type;
template <> const std::string TypeToSQLType<long>::type;
template <> const std::string TypeToSQLType<long long>::type;
template <> const std::string TypeToSQLType<bool>::type;
template <> const std::string TypeToSQLType<std::string>::type;
template <> const std::string TypeToSQLType<OptionalBool>::type;
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include <database/query.hpp>
#include <database/engine.hpp>
using namespace std::string_literals;
template <class T, class... Tuple>
struct Index;
template <class T, class... Types>
struct Index<T, std::tuple<T, Types...>>
{
static const std::size_t value = 0;
};
template <class T, class U, class... Types>
struct Index<T, std::tuple<U, Types...>>
{
static const std::size_t value = Index<T, std::tuple<Types...>>::value + 1;
};
struct UpdateQuery: public Query
{
template <typename... T>
UpdateQuery(const std::string& name, const std::tuple<T...>& columns):
Query("UPDATE ")
{
this->body += name;
this->insert_col_names_and_values(columns);
}
template <typename... T>
void insert_col_names_and_values(const std::tuple<T...>& columns)
{
this->body += " SET ";
this->insert_col_name_and_value(columns);
this->body += " WHERE "s + Id::name + "=$" + std::to_string(this->current_param);
}
template <int N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
insert_col_name_and_value(const std::tuple<T...>& columns)
{
using ColumnType = std::decay_t<decltype(std::get<N>(columns))>;
if (!std::is_same<ColumnType, Id>::value)
{
this->body += ColumnType::name + "=$"s + std::to_string(this->current_param);
this->current_param++;
if (N < (sizeof...(T) - 1))
this->body += ", ";
}
this->insert_col_name_and_value<N+1>(columns);
}
template <int N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
insert_col_name_and_value(const std::tuple<T...>&)
{}
template <typename... T>
void execute(DatabaseEngine& db, const std::tuple<T...>& columns)
{
auto statement = db.prepare(this->body);
this->bind_param(columns, *statement);
this->bind_id(columns, *statement);
statement->step();
}
template <int N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
bind_param(const std::tuple<T...>& columns, Statement& statement, int index=1)
{
auto&& column = std::get<N>(columns);
using ColumnType = std::decay_t<decltype(column)>;
if (!std::is_same<ColumnType, Id>::value)
actual_bind(statement, column.value, index++);
this->bind_param<N+1>(columns, statement, index);
}
template <int N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
bind_param(const std::tuple<T...>&, Statement&, int)
{}
template <typename... T>
void bind_id(const std::tuple<T...>& columns, Statement& statement)
{
static constexpr auto index = Index<Id, std::tuple<T...>>::value;
auto&& value = std::get<index>(columns);
actual_bind(statement, value.value, sizeof...(T));
}
};
+2 -1
View File
@@ -88,7 +88,8 @@ int main(int ac, char** av)
#ifdef USE_DATABASE #ifdef USE_DATABASE
try { try {
open_database(); open_database();
} catch (...) { } catch (const std::exception& e) {
log_error(e.what());
return 1; return 1;
} }
#endif #endif
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <type_traits>
template <typename...>
struct is_one_of_implem {
static constexpr bool value = false;
};
template <typename F, typename S, typename... T>
struct is_one_of_implem<F, S, T...> {
static constexpr bool value =
std::is_same<F, S>::value || is_one_of_implem<F, T...>::value;
};
template<typename... T>
constexpr bool is_one_of = is_one_of_implem<T...>::value;
+8
View File
@@ -0,0 +1,8 @@
#include <utils/optional_bool.hpp>
std::ostream& operator<<(std::ostream& os, const OptionalBool& o)
{
os << o.to_string();
return os;
}
+3 -1
View File
@@ -20,7 +20,7 @@ struct OptionalBool
this->is_set = false; this->is_set = false;
} }
std::string to_string() std::string to_string() const
{ {
if (this->is_set == false) if (this->is_set == false)
return "unset"; return "unset";
@@ -33,3 +33,5 @@ struct OptionalBool
bool is_set{false}; bool is_set{false};
bool value{false}; bool value{false};
}; };
std::ostream& operator<<(std::ostream& os, const OptionalBool& o);
+6
View File
@@ -1080,6 +1080,9 @@ void BiboumiComponent::on_irc_client_connected(const std::string& irc_hostname,
const auto local_jid = irc_hostname + "@" + this->served_hostname; const auto local_jid = irc_hostname + "@" + this->served_hostname;
if (Database::has_roster_item(local_jid, jid)) if (Database::has_roster_item(local_jid, jid))
this->send_presence_to_contact(local_jid, jid, ""); this->send_presence_to_contact(local_jid, jid, "");
#else
(void)irc_hostname;
(void)jid;
#endif #endif
} }
@@ -1089,6 +1092,9 @@ void BiboumiComponent::on_irc_client_disconnected(const std::string& irc_hostnam
const auto local_jid = irc_hostname + "@" + this->served_hostname; const auto local_jid = irc_hostname + "@" + this->served_hostname;
if (Database::has_roster_item(local_jid, jid)) if (Database::has_roster_item(local_jid, jid))
this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable"); this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable");
#else
(void)irc_hostname;
(void)jid;
#endif #endif
} }
+26 -3
View File
@@ -1,19 +1,43 @@
#include "catch.hpp" #include "catch.hpp"
#include <biboumi.h>
#ifdef USE_DATABASE
#include <cstdlib>
#include <database/database.hpp> #include <database/database.hpp>
#include <config/config.hpp> #include <config/config.hpp>
TEST_CASE("Database") TEST_CASE("Database")
{ {
#ifdef USE_DATABASE #ifdef PQ_FOUND
std::string postgresql_uri{"postgresql://"};
const char* env_value = ::getenv("TEST_POSTGRES_URI");
if (env_value != nullptr)
Database::open("postgresql://"s + env_value);
else
#endif
Database::open(":memory:"); Database::open(":memory:");
Database::raw_exec("DELETE FROM " + Database::irc_server_options.get_name());
Database::raw_exec("DELETE FROM " + Database::irc_channel_options.get_name());
SECTION("Basic retrieve and update") SECTION("Basic retrieve and update")
{ {
auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com");
CHECK(Database::count(Database::irc_server_options) == 0);
o.save(Database::db); o.save(Database::db);
CHECK(Database::count(Database::irc_server_options) == 1);
o.col<Database::Realname>() = "Different realname";
CHECK(o.col<Database::Realname>() == "Different realname");
o.save(Database::db);
CHECK(o.col<Database::Realname>() == "Different realname");
CHECK(Database::count(Database::irc_server_options) == 1);
auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com");
CHECK(a.col<Database::Realname>() == "Different realname");
auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com");
// b does not yet exist in the db, the object is created but not yet // b does not yet exist in the db, the object is created but not yet
@@ -28,7 +52,6 @@ TEST_CASE("Database")
SECTION("channel options") SECTION("channel options")
{ {
Config::set("db_name", ":memory:");
auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo");
CHECK(o.col<Database::EncodingIn>() == ""); CHECK(o.col<Database::EncodingIn>() == "");
@@ -95,5 +118,5 @@ TEST_CASE("Database")
} }
Database::close(); Database::close();
#endif
} }
#endif
+12
View File
@@ -11,6 +11,7 @@
#include <utils/system.hpp> #include <utils/system.hpp>
#include <utils/scopeguard.hpp> #include <utils/scopeguard.hpp>
#include <utils/dirname.hpp> #include <utils/dirname.hpp>
#include <utils/is_one_of.hpp>
using namespace std::string_literals; using namespace std::string_literals;
@@ -171,3 +172,14 @@ TEST_CASE("dirname")
CHECK(utils::dirname(".") == "."); CHECK(utils::dirname(".") == ".");
CHECK(utils::dirname("./") == "./"); CHECK(utils::dirname("./") == "./");
} }
TEST_CASE("is_in")
{
CHECK((is_one_of<int, float, std::string, int>) == true);
CHECK((is_one_of<int, float, std::string>) == false);
CHECK((is_one_of<int>) == false);
CHECK((is_one_of<int, int>) == true);
CHECK((is_one_of<bool, int>) == false);
CHECK((is_one_of<bool, bool>) == true);
CHECK((is_one_of<bool, bool, bool, bool, bool, int>) == true);
}