Implement our own database ORM, and update the whole code to use it
Entirely replace LiteSQL fix #3271
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
#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"; };
|
||||
@@ -0,0 +1,35 @@
|
||||
#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;
|
||||
}
|
||||
};
|
||||
+102
-112
@@ -2,175 +2,166 @@
|
||||
#ifdef USE_DATABASE
|
||||
|
||||
#include <database/database.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <irc/iid.hpp>
|
||||
#include <uuid/uuid.h>
|
||||
#include <utils/get_first_non_empty.hpp>
|
||||
#include <utils/time.hpp>
|
||||
|
||||
using namespace std::string_literals;
|
||||
#include <sqlite3.h>
|
||||
|
||||
std::unique_ptr<db::BibouDB> Database::db;
|
||||
sqlite3* Database::db;
|
||||
Database::MucLogLineTable Database::muc_log_lines("MucLogLine_");
|
||||
Database::GlobalOptionsTable Database::global_options("GlobalOptions_");
|
||||
Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_");
|
||||
Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_");
|
||||
|
||||
void Database::open(const std::string& filename, const std::string& db_type)
|
||||
void Database::open(const std::string& filename)
|
||||
{
|
||||
try
|
||||
auto res = sqlite3_open_v2(filename.data(), &Database::db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
|
||||
log_debug("open: ", res);
|
||||
Database::muc_log_lines.create(Database::db);
|
||||
Database::global_options.create(Database::db);
|
||||
Database::irc_server_options.create(Database::db);
|
||||
Database::irc_channel_options.create(Database::db);
|
||||
}
|
||||
|
||||
|
||||
Database::GlobalOptions Database::get_global_options(const std::string& owner)
|
||||
{
|
||||
auto request = Database::global_options.select().where() << Owner{} << "=" << owner;
|
||||
|
||||
Database::GlobalOptions options{Database::global_options.get_name()};
|
||||
auto result = request.execute(Database::db);
|
||||
if (result.size() == 1)
|
||||
options = result.front();
|
||||
else
|
||||
options.col<Owner>() = owner;
|
||||
return options;
|
||||
}
|
||||
|
||||
Database::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server)
|
||||
{
|
||||
auto request = Database::irc_server_options.select().where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
|
||||
|
||||
Database::IrcServerOptions options{Database::irc_server_options.get_name()};
|
||||
auto result = request.execute(Database::db);
|
||||
if (result.size() == 1)
|
||||
options = result.front();
|
||||
else
|
||||
{
|
||||
auto new_db = std::make_unique<db::BibouDB>(db_type,
|
||||
"database="s + filename);
|
||||
if (new_db->needsUpgrade())
|
||||
new_db->upgrade();
|
||||
Database::db = std::move(new_db);
|
||||
} catch (const litesql::DatabaseError& e) {
|
||||
log_error("Failed to open database ", filename, ". ", e.what());
|
||||
throw;
|
||||
options.col<Owner>() = owner;
|
||||
options.col<Server>() = server;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
void Database::set_verbose(const bool val)
|
||||
Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, const std::string& server, const std::string& channel)
|
||||
{
|
||||
Database::db->verbose = val;
|
||||
auto request = Database::irc_channel_options.select().where() << Owner{} << "=" << owner <<\
|
||||
" and " << Server{} << "=" << server <<\
|
||||
" and " << Channel{} << "=" << channel;
|
||||
Database::IrcChannelOptions options{Database::irc_channel_options.get_name()};
|
||||
auto result = request.execute(Database::db);
|
||||
if (result.size() == 1)
|
||||
options = result.front();
|
||||
else
|
||||
{
|
||||
options.col<Owner>() = owner;
|
||||
options.col<Server>() = server;
|
||||
options.col<Channel>() = channel;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
db::GlobalOptions Database::get_global_options(const std::string& owner)
|
||||
{
|
||||
try {
|
||||
auto options = litesql::select<db::GlobalOptions>(*Database::db,
|
||||
db::GlobalOptions::Owner == owner).one();
|
||||
return options;
|
||||
} catch (const litesql::NotFound& e) {
|
||||
db::GlobalOptions options(*Database::db);
|
||||
options.owner = owner;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
db::IrcServerOptions Database::get_irc_server_options(const std::string& owner,
|
||||
const std::string& server)
|
||||
{
|
||||
try {
|
||||
auto options = litesql::select<db::IrcServerOptions>(*Database::db,
|
||||
db::IrcServerOptions::Owner == owner &&
|
||||
db::IrcServerOptions::Server == server).one();
|
||||
return options;
|
||||
} catch (const litesql::NotFound& e) {
|
||||
db::IrcServerOptions options(*Database::db);
|
||||
options.owner = owner;
|
||||
options.server = server;
|
||||
// options.update();
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
db::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner,
|
||||
const std::string& server,
|
||||
const std::string& channel)
|
||||
{
|
||||
try {
|
||||
auto options = litesql::select<db::IrcChannelOptions>(*Database::db,
|
||||
db::IrcChannelOptions::Owner == owner &&
|
||||
db::IrcChannelOptions::Server == server &&
|
||||
db::IrcChannelOptions::Channel == channel).one();
|
||||
return options;
|
||||
} catch (const litesql::NotFound& e) {
|
||||
db::IrcChannelOptions options(*Database::db);
|
||||
options.owner = owner;
|
||||
options.server = server;
|
||||
options.channel = channel;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner,
|
||||
const std::string& server,
|
||||
const std::string& channel)
|
||||
Database::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server,
|
||||
const std::string& channel)
|
||||
{
|
||||
auto coptions = Database::get_irc_channel_options(owner, server, channel);
|
||||
auto soptions = Database::get_irc_server_options(owner, server);
|
||||
|
||||
coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
|
||||
soptions.encodingIn.value());
|
||||
coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
|
||||
soptions.encodingOut.value());
|
||||
coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
|
||||
soptions.col<EncodingIn>());
|
||||
coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
|
||||
soptions.col<EncodingOut>());
|
||||
|
||||
coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
|
||||
soptions.maxHistoryLength.value());
|
||||
coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
|
||||
soptions.col<MaxHistoryLength>());
|
||||
|
||||
return coptions;
|
||||
}
|
||||
|
||||
db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner,
|
||||
const std::string& server,
|
||||
const std::string& channel)
|
||||
Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel)
|
||||
{
|
||||
auto coptions = Database::get_irc_channel_options(owner, server, channel);
|
||||
auto soptions = Database::get_irc_server_options(owner, server);
|
||||
auto goptions = Database::get_global_options(owner);
|
||||
|
||||
coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
|
||||
soptions.encodingIn.value());
|
||||
coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
|
||||
soptions.encodingOut.value());
|
||||
coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
|
||||
soptions.col<EncodingIn>());
|
||||
|
||||
coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
|
||||
soptions.maxHistoryLength.value(),
|
||||
goptions.maxHistoryLength.value());
|
||||
coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
|
||||
soptions.col<EncodingOut>());
|
||||
|
||||
coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
|
||||
soptions.col<MaxHistoryLength>(),
|
||||
goptions.col<MaxHistoryLength>());
|
||||
|
||||
return coptions;
|
||||
}
|
||||
|
||||
std::string Database::store_muc_message(const std::string& owner, const Iid& iid,
|
||||
Database::time_point date,
|
||||
const std::string& body,
|
||||
const std::string& nick)
|
||||
std::string Database::store_muc_message(const std::string& owner, const std::string& chan_name,
|
||||
const std::string& server_name, Database::time_point date,
|
||||
const std::string& body, const std::string& nick)
|
||||
{
|
||||
db::MucLogLine line(*Database::db);
|
||||
auto line = Database::muc_log_lines.row();
|
||||
|
||||
auto uuid = Database::gen_uuid();
|
||||
|
||||
line.uuid = uuid;
|
||||
line.owner = owner;
|
||||
line.ircChanName = iid.get_local();
|
||||
line.ircServerName = iid.get_server();
|
||||
line.date = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
|
||||
line.body = body;
|
||||
line.nick = nick;
|
||||
line.col<Uuid>() = uuid;
|
||||
line.col<Owner>() = owner;
|
||||
line.col<IrcChanName>() = chan_name;
|
||||
line.col<IrcServerName>() = server_name;
|
||||
line.col<Date>() = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
|
||||
line.col<Body>() = body;
|
||||
line.col<Nick>() = nick;
|
||||
|
||||
line.update();
|
||||
line.save(Database::db);
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
|
||||
std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
|
||||
int limit, const std::string& start, const std::string& end)
|
||||
{
|
||||
auto request = litesql::select<db::MucLogLine>(*Database::db,
|
||||
db::MucLogLine::Owner == owner &&
|
||||
db::MucLogLine::IrcChanName == chan_name &&
|
||||
db::MucLogLine::IrcServerName == server);
|
||||
request.orderBy(db::MucLogLine::Id, false);
|
||||
auto request = Database::muc_log_lines.select().where() << Database::Owner{} << "=" << owner << \
|
||||
" and " << Database::IrcChanName{} << "=" << chan_name << \
|
||||
" and " << Database::IrcServerName{} << "=" << server;
|
||||
|
||||
if (limit >= 0)
|
||||
request.limit(limit);
|
||||
if (!start.empty())
|
||||
{
|
||||
const auto start_time = utils::parse_datetime(start);
|
||||
if (start_time != -1)
|
||||
request.where(db::MucLogLine::Date >= start_time);
|
||||
request << " and " << Database::Date{} << ">=" << start_time;
|
||||
}
|
||||
if (!end.empty())
|
||||
{
|
||||
const auto end_time = utils::parse_datetime(end);
|
||||
if (end_time != -1)
|
||||
request.where(db::MucLogLine::Date <= end_time);
|
||||
request << " and " << Database::Date{} << "<=" << end_time;
|
||||
}
|
||||
const auto& res = request.all();
|
||||
return {res.crbegin(), res.crend()};
|
||||
|
||||
request.order_by() << Id{} << " DESC ";
|
||||
|
||||
if (limit >= 0)
|
||||
request.limit() << limit;
|
||||
|
||||
auto result = request.execute(Database::db);
|
||||
|
||||
return {result.crbegin(), result.crend()};
|
||||
}
|
||||
|
||||
void Database::close()
|
||||
{
|
||||
Database::db.reset(nullptr);
|
||||
sqlite3_close_v2(Database::db);
|
||||
}
|
||||
|
||||
std::string Database::gen_uuid()
|
||||
@@ -182,5 +173,4 @@ std::string Database::gen_uuid()
|
||||
return uuid_str;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
+115
-38
@@ -1,22 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <biboumi.h>
|
||||
#ifdef USE_DATABASE
|
||||
|
||||
#include "biboudb.hpp"
|
||||
#include <database/table.hpp>
|
||||
#include <database/column.hpp>
|
||||
#include <database/count_query.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <litesql.hpp>
|
||||
#include <chrono>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
static MucLogLineTable muc_log_lines;
|
||||
static GlobalOptionsTable global_options;
|
||||
static IrcServerOptionsTable irc_server_options;
|
||||
static IrcChannelOptionsTable irc_channel_options;
|
||||
static sqlite3* db;
|
||||
|
||||
private:
|
||||
static std::string gen_uuid();
|
||||
static std::unique_ptr<db::BibouDB> db;
|
||||
};
|
||||
#endif /* USE_DATABASE */
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
#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:
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#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);
|
||||
@@ -0,0 +1,75 @@
|
||||
#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;
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
#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)
|
||||
{
|
||||
this->insert_col_name<0>();
|
||||
this->body += " from " + this->table_name;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
typename std::enable_if<N < sizeof...(T), void>::type
|
||||
insert_col_name()
|
||||
{
|
||||
using ColumnsType = std::tuple<T...>;
|
||||
ColumnsType tuple{};
|
||||
auto value = std::get<N>(tuple);
|
||||
|
||||
this->body += " "s + value.name;
|
||||
|
||||
if (N < (sizeof...(T) - 1))
|
||||
this->body += ", ";
|
||||
|
||||
this->insert_col_name<N+1>();
|
||||
}
|
||||
template <std::size_t N>
|
||||
typename std::enable_if<N == sizeof...(T), void>::type
|
||||
insert_col_name()
|
||||
{}
|
||||
|
||||
SelectQuery& where()
|
||||
{
|
||||
this->body += " WHERE ";
|
||||
return *this;
|
||||
};
|
||||
|
||||
SelectQuery& order_by()
|
||||
{
|
||||
this->body += " ORDER BY ";
|
||||
return *this;
|
||||
}
|
||||
|
||||
SelectQuery& limit()
|
||||
{
|
||||
this->body += " LIMIT ";
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto execute(sqlite3* db)
|
||||
{
|
||||
auto statement = this->prepare(db);
|
||||
int i = 1;
|
||||
for (const std::string& param: this->params)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, i, param.data(), static_cast<int>(param.size()), SQLITE_TRANSIENT) != SQLITE_OK)
|
||||
log_debug("Failed to bind ", param, " to param ", i);
|
||||
else
|
||||
log_debug("Bound ", param, " to ", i);
|
||||
|
||||
i++;
|
||||
}
|
||||
std::vector<Row<T...>> rows;
|
||||
while (sqlite3_step(statement) == SQLITE_ROW)
|
||||
{
|
||||
Row<T...> row(this->table_name);
|
||||
extract_row_values(row, statement);
|
||||
rows.push_back(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
const std::string table_name;
|
||||
};
|
||||
|
||||
template <typename T, typename... ColumnTypes>
|
||||
typename std::enable_if<!std::is_integral<T>::value, SelectQuery<ColumnTypes...>&>::type
|
||||
operator<<(SelectQuery<ColumnTypes...>& query, const T&)
|
||||
{
|
||||
query.body += T::name;
|
||||
return query;
|
||||
}
|
||||
|
||||
template <typename... ColumnTypes>
|
||||
SelectQuery<ColumnTypes...>& operator<<(SelectQuery<ColumnTypes...>& query, const char* str)
|
||||
{
|
||||
query.body += str;
|
||||
return query;
|
||||
}
|
||||
|
||||
template <typename... ColumnTypes>
|
||||
SelectQuery<ColumnTypes...>& operator<<(SelectQuery<ColumnTypes...>& query, const std::string& str)
|
||||
{
|
||||
query.body += "?";
|
||||
actual_add_param(query, str);
|
||||
return query;
|
||||
}
|
||||
|
||||
template <typename Integer, typename... ColumnTypes>
|
||||
typename std::enable_if<std::is_integral<Integer>::value, SelectQuery<ColumnTypes...>&>::type
|
||||
operator<<(SelectQuery<ColumnTypes...>& query, const Integer& i)
|
||||
{
|
||||
query.body += "?";
|
||||
actual_add_param(query, i);
|
||||
return query;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <database/select_query.hpp>
|
||||
#include <database/type_to_sql.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <database/row.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
template <typename... T>
|
||||
class Table
|
||||
{
|
||||
static_assert(sizeof...(T) > 0, "Table cannot be empty");
|
||||
using ColumnTypes = std::tuple<T...>;
|
||||
|
||||
public:
|
||||
using RowType = Row<T...>;
|
||||
|
||||
Table(std::string name):
|
||||
name(std::move(name))
|
||||
{}
|
||||
|
||||
void create(sqlite3* db)
|
||||
{
|
||||
std::string res{"CREATE TABLE IF NOT EXISTS "};
|
||||
res += this->name;
|
||||
res += " (\n";
|
||||
this->add_column_create<0>(res);
|
||||
res += ")";
|
||||
|
||||
log_debug(res);
|
||||
|
||||
char* error;
|
||||
const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error);
|
||||
log_debug("result: ", +result);
|
||||
if (result != SQLITE_OK)
|
||||
{
|
||||
log_error("Error executing query: ", error);
|
||||
sqlite3_free(error);
|
||||
}
|
||||
}
|
||||
|
||||
RowType row()
|
||||
{
|
||||
return {this->name};
|
||||
}
|
||||
|
||||
SelectQuery<T...> select()
|
||||
{
|
||||
SelectQuery<T...> select(this->name);
|
||||
return select;
|
||||
}
|
||||
|
||||
const std::string& get_name() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
private:
|
||||
template <std::size_t N>
|
||||
typename std::enable_if<N < sizeof...(T), void>::type
|
||||
add_column_create(std::string& str)
|
||||
{
|
||||
using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
|
||||
using RealType = typename ColumnType::real_type;
|
||||
str += ColumnType::name;
|
||||
str += " ";
|
||||
str += TypeToSQLType<RealType>::type;
|
||||
str += " "s + ColumnType::options;
|
||||
if (N != sizeof...(T) - 1)
|
||||
str += ",";
|
||||
str += "\n";
|
||||
|
||||
add_column_create<N+1>(str);
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
typename std::enable_if<N == sizeof...(T), void>::type
|
||||
add_column_create(std::string&)
|
||||
{ }
|
||||
|
||||
const std::string name;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
#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<bool>::type = "INTEGER";
|
||||
template <> const std::string TypeToSQLType<std::string>::type = "TEXT";
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
template <typename T>
|
||||
struct TypeToSQLType { static const std::string type; };
|
||||
Reference in New Issue
Block a user