Merge branch 'postgresql' into 'master'
Add postgresql support Closes #3237 See merge request louiz/biboumi!18
This commit is contained in:
+10
-4
@@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
+22
-5
@@ -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})
|
||||||
|
|
||||||
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)
|
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)
|
||||||
target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES})
|
if(SQLITE3_FOUND)
|
||||||
target_link_libraries(test_suite ${SQLITE3_LIBRARIES})
|
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()
|
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
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (N != sizeof...(T) - 1)
|
|
||||||
this->body += ",";
|
if (!std::is_same<ColumnType, Id>::value)
|
||||||
this->body += " ";
|
{
|
||||||
add_param(*this, std::get<N>(columns));
|
this->body += "$" + std::to_string(index++);
|
||||||
this->insert_value<N+1>(columns);
|
if (N != sizeof...(T) - 1)
|
||||||
|
this->body += ", ";
|
||||||
|
}
|
||||||
|
this->insert_value<N+1>(columns, 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
|
||||||
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))>;
|
||||||
|
|
||||||
this->body += ColumnType::name;
|
if (!std::is_same<ColumnType, Id>::value)
|
||||||
|
{
|
||||||
|
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:
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-55
@@ -1,72 +1,71 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
Row(std::string name):
|
Row(std::string name):
|
||||||
table_name(std::move(name))
|
table_name(std::move(name))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
typename Type::real_type& col()
|
typename Type::real_type& col()
|
||||||
{
|
{
|
||||||
auto&& col = std::get<Type>(this->columns);
|
auto&& col = std::get<Type>(this->columns);
|
||||||
return col.value;
|
return col.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
const auto& col() const
|
const auto& col() const
|
||||||
{
|
{
|
||||||
auto&& col = std::get<Type>(this->columns);
|
auto&& col = std::get<Type>(this->columns);
|
||||||
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);
|
{
|
||||||
query.insert_col_names(this->columns);
|
this->insert(*db);
|
||||||
query.insert_values(this->columns);
|
}
|
||||||
|
|
||||||
query.execute(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);
|
||||||
|
}
|
||||||
|
|
||||||
update_autoincrement_id(this->columns, 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::string table_name;
|
||||||
|
|
||||||
std::tuple<T...> columns;
|
|
||||||
std::string table_name;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
+49
-42
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <utils/optional_bool.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const OptionalBool& o)
|
||||||
|
{
|
||||||
|
os << o.to_string();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-4
@@ -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
|
||||||
Database::open(":memory:");
|
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::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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user