Commit 50cadf3d authored by louiz’'s avatar louiz’

Implement our own database ORM, and update the whole code to use it

Entirely replace LiteSQL

fix #3271
parent 7ca95a09
<?xml version="1.0"?>
<!DOCTYPE database SYSTEM "litesql.dtd">
<database name="BibouDB" namespace="db">
<object name="GlobalOptions">
<field name="owner" type="string" length="3071"/>
<field name="maxHistoryLength" type="integer" default="20"/>
<field name="recordHistory" type="boolean" default="true"/>
<index unique="true">
<indexfield name="owner"/>
</index>
</object>
<object name="IrcServerOptions">
<field name="owner" type="string" length="3071"/>
<field name="server" type="string" length="3071"/>
<field name="pass" type="string" length="1024" default=""/>
<field name="afterConnectionCommand" type="string" length="510" default=""/>
<field name="tlsPorts" type="string" length="4096" default="6697;6670" />
<field name="ports" type="string" length="4096" default="6667" />
<field name="username" type="string" length="1024" default=""/>
<field name="realname" type="string" length="1024" default=""/>
<field name="verifyCert" type="boolean" default="true"/>
<field name="trustedFingerprint" type="string"/>
<field name="encodingOut" type="string" default="ISO-8859-1"/>
<field name="encodingIn" type="string" default="ISO-8859-1"/>
<field name="maxHistoryLength" type="integer" default="20"/>
<index unique="true">
<indexfield name="owner"/>
<indexfield name="server"/>
</index>
</object>
<object name="IrcChannelOptions">
<field name="owner" type="string" length="3071"/>
<field name="server" type="string" length="3071"/>
<field name="channel" type="string" length="1024"/>
<field name="encodingOut" type="string"/>
<field name="encodingIn" type="string"/>
<field name="maxHistoryLength" type="integer" default="20"/>
<field name="persistent" type="boolean" default="false"/>
<index unique="true">
<indexfield name="owner"/>
<indexfield name="server"/>
<indexfield name="channel"/>
</index>
</object>
<object name="MucLogLine">
<field name="uuid" type="string" length="36" />
<!-- The bare JID of the user for which we stored the line. It's
the JID associated with the Bridge -->
<field name="owner" type="string" length="4096" />
<!-- The room IID -->
<field name="ircChanName" type="string" length="4096" />
<field name="ircServerName" type="string" length="4096" />
<field name="date" type="datetime" />
<field name="body" type="string" length="65536"/>
<field name="nick" type="string" length="4096" />
</object>
</database>
......@@ -23,7 +23,7 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
#ifdef USE_DATABASE
const auto jid = bridge.get_bare_jid();
auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
return options.encodingIn.value();
return options.col<Database::EncodingIn>();
#else
(void)bridge;
(void)iid;
......@@ -32,13 +32,13 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
}
Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller):
user_jid(std::move(user_jid)),
user_jid(std::move(user_jid)),
xmpp(xmpp),
poller(poller)
{
#ifdef USE_DATABASE
const auto options = Database::get_global_options(this->user_jid);
this->set_record_history(options.recordHistory.value());
this->set_record_history(options.col<Database::RecordHistory>());
#endif
}
......@@ -258,7 +258,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(line);
if (this->record_history)
uuid = Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), irc->get_own_nick());
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
......@@ -436,7 +436,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
#ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid,
iid.get_server(), iid.get_local());
persistent = coptions.persistent.value();
persistent = coptions.col<Database::Persistent>();
#endif
if (channel->joined && !channel->parting && !persistent)
{
......@@ -837,7 +837,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(body, encoding);
if (!nick.empty() && this->record_history)
Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), nick);
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
......@@ -994,12 +994,12 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam
{
#ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value());
const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.col<Database::MaxHistoryLength>());
chan_name.append(utils::empty_if_fixed_server("%" + hostname));
for (const auto& line: lines)
{
const auto seconds = line.date.value().timeStamp();
this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(),
const auto seconds = line.col<Database::Date>();
this->xmpp.send_history_message(chan_name, line.col<Database::Nick>(), line.col<Database::Body>(),
this->user_jid + "/" + resource, seconds);
}
#else
......
#pragma once
#include <cstdint>
template <typename T>
struct Column
{
using real_type = T;
T value;
};
struct Id: Column<std::size_t> { static constexpr auto name = "id_";
static constexpr auto options = "PRIMARY KEY AUTOINCREMENT"; };
#pragma once
#include <database/query.hpp>
#include <database/table.hpp>
#include <string>
#include <sqlite3.h>
struct CountQuery: public Query
{
CountQuery(std::string name):
Query("SELECT count(*) FROM ")
{
this->body += std::move(name);
}
std::size_t execute(sqlite3* db)
{
auto statement = this->prepare(db);
std::size_t res = 0;
if (sqlite3_step(statement) == SQLITE_ROW)
res = sqlite3_column_int64(statement, 0);
else
{
log_error("Count request didn’t return a result");
return 0;
}
if (sqlite3_step(statement) != SQLITE_DONE)
log_warning("Count request returned more than one result.");
log_debug("Returning count: ", res);
return res;
}
};
This diff is collapsed.
#pragma once
#include <biboumi.h>
#ifdef USE_DATABASE
#include "biboudb.hpp"
#include <memory>
#include <database/table.hpp>
#include <database/column.hpp>
#include <database/count_query.hpp>
#include <litesql.hpp>
#include <chrono>
#include <string>
#include <memory>
class Iid;
class Database
{
public:
public:
using time_point = std::chrono::system_clock::time_point;
struct Uuid: Column<std::string> { static constexpr auto name = "uuid_";
static constexpr auto options = ""; };
struct Owner: Column<std::string> { static constexpr auto name = "owner_";
static constexpr auto options = ""; };
struct IrcChanName: Column<std::string> { static constexpr auto name = "ircChanName_";
static constexpr auto options = ""; };
struct Channel: Column<std::string> { static constexpr auto name = "channel_";
static constexpr auto options = ""; };
struct IrcServerName: Column<std::string> { static constexpr auto name = "ircServerName_";
static constexpr auto options = ""; };
struct Server: Column<std::string> { static constexpr auto name = "server_";
static constexpr auto options = ""; };
struct Date: Column<time_point::rep> { static constexpr auto name = "date_";
static constexpr auto options = ""; };
struct Body: Column<std::string> { static constexpr auto name = "body_";
static constexpr auto options = ""; };
struct Nick: Column<std::string> { static constexpr auto name = "nick_";
static constexpr auto options = ""; };
struct Pass: Column<std::string> { static constexpr auto name = "pass_";
static constexpr auto options = ""; };
struct Ports: Column<std::string> { static constexpr auto name = "tlsPorts_";
static constexpr auto options = ""; };
struct TlsPorts: Column<std::string> { static constexpr auto name = "ports_";
static constexpr auto options = ""; };
struct Username: Column<std::string> { static constexpr auto name = "username_";
static constexpr auto options = ""; };
struct Realname: Column<std::string> { static constexpr auto name = "realname_";
static constexpr auto options = ""; };
struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterConnectionCommand_";
static constexpr auto options = ""; };
struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedFingerprint_";
static constexpr auto options = ""; };
struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingOut_";
static constexpr auto options = ""; };
struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingIn_";
static constexpr auto options = ""; };
struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxHistoryLength_";
static constexpr auto options = ""; };
struct RecordHistory: Column<bool> { static constexpr auto name = "recordHistory_";
static constexpr auto options = ""; };
struct VerifyCert: Column<bool> { static constexpr auto name = "verifyCert_";
static constexpr auto options = ""; };
struct Persistent: Column<bool> { static constexpr auto name = "persistent_";
static constexpr auto options = ""; };
using MucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, Date, Body, Nick>;
using MucLogLine = MucLogLineTable::RowType;
using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory>;
using GlobalOptions = GlobalOptionsTable::RowType;
using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, AfterConnectionCommand, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength>;
using IrcServerOptions = IrcServerOptionsTable::RowType;
using IrcChannelOptionsTable = Table<Id, Owner, Server, Channel, EncodingOut, EncodingIn, MaxHistoryLength, Persistent>;
using IrcChannelOptions = IrcChannelOptionsTable::RowType;
Database() = default;
~Database() = default;
......@@ -25,42 +104,40 @@ public:
Database& operator=(const Database&) = delete;
Database& operator=(Database&&) = delete;
static void set_verbose(const bool val);
template<typename PersistentType>
static size_t count()
{
return litesql::select<PersistentType>(*Database::db).count();
}
/**
* Return the object from the db. Create it beforehand (with all default
* values) if it is not already present.
*/
static db::GlobalOptions get_global_options(const std::string& owner);
static db::IrcServerOptions get_irc_server_options(const std::string& owner,
static GlobalOptions get_global_options(const std::string& owner);
static IrcServerOptions get_irc_server_options(const std::string& owner,
const std::string& server);
static db::IrcChannelOptions get_irc_channel_options(const std::string& owner,
const std::string& server,
const std::string& channel);
static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner,
const std::string& server,
const std::string& channel);
static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner,
const std::string& server,
const std::string& channel);
static std::vector<db::MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
int limit=-1, const std::string& start="", const std::string& end="");
static std::string store_muc_message(const std::string& owner, const Iid& iid,
time_point date, const std::string& body, const std::string& nick);
static IrcChannelOptions get_irc_channel_options(const std::string& owner,
const std::string& server,
const std::string& channel);
static IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner,
const std::string& server,
const std::string& channel);
static IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner,
const std::string& server,
const std::string& channel);
static std::vector<MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
int limit=-1, const std::string& start="", const std::string& end="");
static std::string store_muc_message(const std::string& owner, const std::string& chan_name, const std::string& server_name,
time_point date, const std::string& body, const std::string& nick);
static void close();
static void open(const std::string& filename, const std::string& db_type="sqlite3");
static void open(const std::string& filename);
template <typename TableType>
static std::size_t count(const TableType& table)
{
CountQuery query{table.get_name()};
return query.execute(Database::db);
}
static MucLogLineTable muc_log_lines;
static GlobalOptionsTable global_options;
static IrcServerOptionsTable irc_server_options;
static IrcChannelOptionsTable irc_channel_options;
static sqlite3* db;
private:
private:
static std::string gen_uuid();
static std::unique_ptr<db::BibouDB> db;
};
#endif /* USE_DATABASE */
#pragma once
#include <database/column.hpp>
#include <database/query.hpp>
#include <logger/logger.hpp>
#include <type_traits>
#include <vector>
#include <string>
#include <tuple>
#include <sqlite3.h>
template <int N, typename ColumnType, typename... T>
typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
actual_bind(sqlite3_stmt* statement, std::vector<std::string>& params, const std::tuple<T...>&)
{
const auto value = params.front();
params.erase(params.begin());
if (sqlite3_bind_text(statement, N + 1, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT) != SQLITE_OK)
log_error("Failed to bind ", value, " to param ", N);
else
log_debug("Bound (not id) [", value, "] to ", 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(sqlite3_stmt* 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, N + 1, column.value) != SQLITE_OK)
log_error("Failed to bind ", column.value, " to id.");
}
else if (sqlite3_bind_null(statement, N + 1) != SQLITE_OK)
log_error("Failed to bind NULL to param ", N);
else
log_debug("Bound NULL to ", N);
}
struct InsertQuery: public Query
{
InsertQuery(const std::string& name):
Query("INSERT OR REPLACE INTO ")
{
this->body += name;
}
template <typename... T>
void execute(const std::tuple<T...>& columns, sqlite3* db)
{
auto statement = this->prepare(db);
{
this->bind_param<0>(columns, statement);
if (sqlite3_step(statement) != SQLITE_DONE)
log_error("Failed to execute query: ", sqlite3_errmsg(db));
}
}
template <int N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
bind_param(const std::tuple<T...>& columns, sqlite3_stmt* statement)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
actual_bind<N, ColumnType>(statement, this->params, columns);
this->bind_param<N+1>(columns, statement);
}
template <int N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
bind_param(const std::tuple<T...>&, sqlite3_stmt*)
{}
template <typename... T>
void insert_values(const std::tuple<T...>& columns)
{
this->body += "VALUES (";
this->insert_value<0>(columns);
this->body += ")";
}
template <int N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
insert_value(const std::tuple<T...>& columns)
{
this->body += "?";
if (N != sizeof...(T) - 1)
this->body += ",";
this->body += " ";
add_param(*this, std::get<N>(columns));
this->insert_value<N+1>(columns);
}
template <int N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
insert_value(const std::tuple<T...>&)
{ }
template <typename... T>
void insert_col_names(const std::tuple<T...>& columns)
{
this->body += " (";
this->insert_col_name<0>(columns);
this->body += ")\n";
}
template <int N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
insert_col_name(const std::tuple<T...>& columns)
{
auto value = std::get<N>(columns);
this->body += value.name;
if (N < (sizeof...(T) - 1))
this->body += ", ";
this->insert_col_name<N+1>(columns);
}
template <int N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
insert_col_name(const std::tuple<T...>&)
{}
private:
};
#include <database/query.hpp>
#include <database/column.hpp>
template <>
void add_param<Id>(Query&, const Id&)
{}
void actual_add_param(Query& query, const std::string& val)
{
query.params.push_back(val);
}
#pragma once
#include <logger/logger.hpp>
#include <vector>
#include <string>
#include <sqlite3.h>
struct Query
{
std::string body;
std::vector<std::string> params;
Query(std::string str):
body(std::move(str))
{}
sqlite3_stmt* prepare(sqlite3* db)
{
sqlite3_stmt* statement;
log_debug(this->body);
auto res = sqlite3_prepare(db, this->body.data(), static_cast<int>(this->body.size()) + 1,
&statement, nullptr);
if (res != SQLITE_OK)
{
log_error("Error preparing statement: ", sqlite3_errmsg(db));
return nullptr;
}
return statement;
}
};
template <typename ColumnType>
void add_param(Query& query, const ColumnType& column)
{
actual_add_param(query, column.value);
}
template <typename T>
void actual_add_param(Query& query, const T& val)
{
query.params.push_back(std::to_string(val));
}
void actual_add_param(Query& query, const std::string& val);
#pragma once
#include <database/insert_query.hpp>
#include <logger/logger.hpp>
#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);
log_debug("Found an autoincrement col.");
auto res = sqlite3_last_insert_rowid(db);
log_debug("Value is now: ", res);
column.value = res;
}
template <std::size_t N, 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, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>&, sqlite3*)
{}
template <typename... T>
struct Row
{
Row(std::string name):
table_name(std::move(name))
{}
template <typename Type>
auto& col()
{
auto&& col = std::get<Type>(this->columns);
return col.value;
}
template <typename Type>
const auto& col() const
{
auto&& col = std::get<Type>(this->columns);
return col.value;
}
void save(sqlite3* db)
{
InsertQuery query(this->table_name);
query.insert_col_names(this->columns);
query.insert_values(this->columns);
log_debug(query.body);
query.execute(this->columns, db);
update_autoincrement_id<0>(this->columns, db);
}
std::tuple<T...> columns;
std::string table_name;
};
#pragma once
#include <database/query.hpp>
#include <logger/logger.hpp>
#include <database/row.hpp>
#include <vector>
#include <string>
#include <sqlite3.h>
using namespace std::string_literals;
template <typename T>
typename std::enable_if<std::is_integral<T>::value, sqlite3_int64>::type
extract_row_value(sqlite3_stmt* statement, const int i)
{
return sqlite3_column_int64(statement, i);
}
template <typename T>
typename std::enable_if<std::is_same<std::string, T>::value, std::string>::type
extract_row_value(sqlite3_stmt* statement, const int i)
{
const auto size = sqlite3_column_bytes(statement, i);
const unsigned char* str = sqlite3_column_text(statement, i);
std::string result(reinterpret_cast<const char*>(str), size);
return result;
}
template <std::size_t N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
extract_row_values(Row<T...>& row, sqlite3_stmt* statement)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(row.columns))>::type;
auto&& column = std::get<N>(row.columns);
column.value = static_cast<decltype(column.value)>(extract_row_value<typename ColumnType::real_type>(statement, N));
extract_row_values<N+1>(row, statement);
}
template <std::size_t N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
extract_row_values(Row<T...>&, sqlite3_stmt*)
{}
template <typename... T>
struct SelectQuery: public Query
{
SelectQuery(std::string table_name):
Query("SELECT"),
table_name(table_name)