#include "biboumi.h" #ifdef USE_DATABASE #include #include #include #include #include #include #include #include #include #include #include #include std::unique_ptr Database::db; Database::MucLogLineTable Database::muc_log_lines("muclogline_"); Database::GlobalOptionsTable Database::global_options("globaloptions_"); Database::IrcServerOptionsTable Database::irc_server_options("ircserveroptions_"); Database::IrcChannelOptionsTable Database::irc_channel_options("ircchanneloptions_"); Database::RosterTable Database::roster("roster"); Database::AfterConnectionCommandsTable Database::after_connection_commands("after_connection_commands_"); std::map Database::encoding_in_cache{}; Database::GlobalPersistent::GlobalPersistent(): Column{Config::get_bool("persistent_by_default", false)} {} void Database::open(const std::string& filename) { // Try to open the specified database. // Close and replace the previous database pointer if it succeeded. If it did // not, just leave things untouched std::unique_ptr new_db; static const auto psql_prefix = "postgresql://"s; static const auto psql_prefix2 = "postgres://"s; if ((filename.substr(0, psql_prefix.size()) == psql_prefix) || (filename.substr(0, psql_prefix2.size()) == psql_prefix2)) new_db = PostgresqlEngine::open(filename); else new_db = Sqlite3Engine::open(filename); if (!new_db) return; Database::db = std::move(new_db); Database::muc_log_lines.create(*Database::db); Database::muc_log_lines.upgrade(*Database::db); convert_date_format(*Database::db, Database::muc_log_lines); Database::global_options.create(*Database::db); Database::global_options.upgrade(*Database::db); Database::irc_server_options.create(*Database::db); Database::irc_server_options.upgrade(*Database::db); Database::irc_channel_options.create(*Database::db); Database::irc_channel_options.upgrade(*Database::db); Database::roster.create(*Database::db); Database::roster.upgrade(*Database::db); Database::after_connection_commands.create(*Database::db); Database::after_connection_commands.upgrade(*Database::db); create_index(*Database::db, "archive_index", Database::muc_log_lines.get_name()); Database::db->init_session(); } Database::GlobalOptions Database::get_global_options(const std::string& owner) { auto request = select(Database::global_options); request.where() << Owner{} << "=" << owner; auto result = request.execute(*Database::db); if (result.size() == 1) return result.front(); Database::GlobalOptions options{Database::global_options.get_name()}; options.col() = owner; return options; } Database::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server) { auto request = select(Database::irc_server_options); request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server; auto result = request.execute(*Database::db); if (result.size() == 1) return result.front(); Database::IrcServerOptions options{Database::irc_server_options.get_name()}; options.col() = owner; options.col() = server; return options; } Database::AfterConnectionCommands Database::get_after_connection_commands(const IrcServerOptions& server_options) { const auto id = server_options.col(); if (id == Id::unset_value) return {}; auto request = select(Database::after_connection_commands); request.where() << ForeignKey{} << "=" << id; return request.execute(*Database::db); } void Database::set_after_connection_commands(const Database::IrcServerOptions& server_options, Database::AfterConnectionCommands& commands) { const auto id = server_options.col(); if (id == Id::unset_value) return ; Transaction transaction; auto query = Database::after_connection_commands.del(); query.where() << ForeignKey{} << "=" << id; query.execute(*Database::db); for (auto& command: commands) { command.col() = server_options.col(); save(command, *Database::db); } } Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, const std::string& server, const std::string& channel) { auto request = select(Database::irc_channel_options); request.where() << Owner{} << "=" << owner <<\ " and " << Server{} << "=" << server <<\ " and " << Channel{} << "=" << channel; auto result = request.execute(*Database::db); if (result.size() == 1) return result.front(); Database::IrcChannelOptions options{Database::irc_channel_options.get_name()}; options.col() = owner; options.col() = server; options.col() = channel; return options; } 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.col() = get_first_non_empty(coptions.col(), soptions.col()); coptions.col() = get_first_non_empty(coptions.col(), soptions.col()); coptions.col() = get_first_non_empty(coptions.col(), soptions.col()); return coptions; } 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.col() = get_first_non_empty(coptions.col(), soptions.col()); coptions.col() = get_first_non_empty(coptions.col(), soptions.col()); coptions.col() = get_first_non_empty(coptions.col(), soptions.col(), goptions.col()); return coptions; } std::string Database::store_muc_message(const std::string& owner, const std::string& chan_name, const std::string& server_name, DateTime::time_point date, const std::string& body, const std::string& nick) { auto line = Database::muc_log_lines.row(); auto uuid = Database::gen_uuid(); line.col() = uuid; line.col() = owner; line.col() = chan_name; line.col() = server_name; line.col() = date; line.col() = body; line.col() = nick; save(line, *Database::db); return uuid; } std::vector 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, const Id::real_type reference_record_id, Database::Paging paging) { if (limit == 0) return {}; auto request = select(Database::muc_log_lines); request.where() << Database::Owner{} << "=" << owner << \ " and " << Database::IrcChanName{} << "=" << chan_name << \ " and " << Database::IrcServerName{} << "=" << server; if (!start.empty()) { const auto start_time = utils::parse_datetime(start); if (start_time != -1) { DateTime datetime(start_time); DatetimeWriter writer(datetime, *Database::db); request << " and " << Database::Date{} << ">=" << writer; } } if (!end.empty()) { const auto end_time = utils::parse_datetime(end); if (end_time != -1) { DateTime datetime(end_time); DatetimeWriter writer(datetime, *Database::db); request << " and " << Database::Date{} << "<=" << writer; } } if (reference_record_id != Id::unset_value) { request << " and " << Id{}; if (paging == Database::Paging::first) request << ">"; else request << "<"; request << reference_record_id; } if (paging == Database::Paging::first) request.order_by() << Database::Date{} << " ASC"; else request.order_by() << Database::Date{} << " DESC"; if (limit >= 0) request.limit() << limit; auto result = request.execute(*Database::db); if (paging == Database::Paging::first) return result; else return {result.crbegin(), result.crend()}; } Database::MucLogLine Database::get_muc_log(const std::string& owner, const std::string& chan_name, const std::string& server, const std::string& uuid, const std::string& start, const std::string& end) { auto request = select(Database::muc_log_lines); request.where() << Database::Owner{} << "=" << owner << \ " and " << Database::IrcChanName{} << "=" << chan_name << \ " and " << Database::IrcServerName{} << "=" << server << \ " and " << Database::Uuid{} << "=" << uuid; if (!start.empty()) { const auto start_time = utils::parse_datetime(start); if (start_time != -1) { DateTime datetime(start_time); DatetimeWriter writer(datetime, *Database::db); request << " and " << Database::Date{} << ">=" << writer; } } if (!end.empty()) { const auto end_time = utils::parse_datetime(end); if (end_time != -1) { DateTime datetime(end_time); DatetimeWriter writer(datetime, *Database::db); request << " and " << Database::Date{} << "<=" << writer; } } auto result = request.execute(*Database::db); if (result.empty()) throw Database::RecordNotFound{}; return result.front(); } void Database::add_roster_item(const std::string& local, const std::string& remote) { auto roster_item = Database::roster.row(); roster_item.col() = local; roster_item.col() = remote; save(roster_item, *Database::db); } void Database::delete_roster_item(const std::string& local, const std::string& remote) { Query query("DELETE FROM "s + Database::roster.get_name()); query << " WHERE " << Database::RemoteJid{} << "=" << remote << \ " AND " << Database::LocalJid{} << "=" << local; // query.execute(*Database::db); } bool Database::has_roster_item(const std::string& local, const std::string& remote) { auto query = select(Database::roster); query.where() << Database::LocalJid{} << "=" << local << \ " and " << Database::RemoteJid{} << "=" << remote; auto res = query.execute(*Database::db); return !res.empty(); } std::vector Database::get_contact_list(const std::string& local) { auto query = select(Database::roster); query.where() << Database::LocalJid{} << "=" << local; return query.execute(*Database::db); } std::vector Database::get_full_roster() { auto query = select(Database::roster); return query.execute(*Database::db); } void Database::close() { Database::db = nullptr; } std::string Database::gen_uuid() { return utils::gen_uuid(); } Transaction::Transaction() { const auto result = Database::raw_exec("BEGIN"); if (std::get(result) == false) log_error("Failed to create SQL transaction: ", std::get(result)); else this->success = true; } Transaction::~Transaction() { if (this->success) { const auto result = Database::raw_exec("END"); if (std::get(result) == false) log_error("Failed to end SQL transaction: ", std::get(result)); } } void Transaction::rollback() { this->success = false; const auto result = Database::raw_exec("ROLLBACK"); if (std::get(result) == false) log_error("Failed to rollback SQL transaction: ", std::get(result)); } #endif