database.cpp 12.7 KB
Newer Older
1 2 3
#include "biboumi.h"
#ifdef USE_DATABASE

4
#include <database/database.hpp>
5
#include <utils/get_first_non_empty.hpp>
6
#include <utils/time.hpp>
7
#include <utils/uuid.hpp>
8

9
#include <config/config.hpp>
louiz’'s avatar
louiz’ committed
10 11
#include <database/sqlite3_engine.hpp>
#include <database/postgresql_engine.hpp>
12

louiz’'s avatar
louiz’ committed
13
#include <database/engine.hpp>
14 15
#include <database/index.hpp>

louiz’'s avatar
louiz’ committed
16 17 18 19 20 21 22
#include <memory>

std::unique_ptr<DatabaseEngine> 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_");
23
Database::RosterTable Database::roster("roster");
24
Database::AfterConnectionCommandsTable Database::after_connection_commands("after_connection_commands_");
25
std::map<Database::CacheKey, Database::EncodingIn::real_type> Database::encoding_in_cache{};
26

27 28 29
Database::GlobalPersistent::GlobalPersistent():
    Column<bool>{Config::get_bool("persistent_by_default", false)}
{}
30

31
void Database::open(const std::string& filename)
32
{
33 34 35
  // Try to open the specified database.
  // Close and replace the previous database pointer if it succeeded. If it did
  // not, just leave things untouched
louiz’'s avatar
louiz’ committed
36 37
  std::unique_ptr<DatabaseEngine> new_db;
  static const auto psql_prefix = "postgresql://"s;
38 39 40
  static const auto psql_prefix2 = "postgres://"s;
  if ((filename.substr(0, psql_prefix.size()) == psql_prefix) ||
      (filename.substr(0, psql_prefix2.size()) == psql_prefix2))
41
    new_db = PostgresqlEngine::open(filename);
louiz’'s avatar
louiz’ committed
42 43 44 45 46 47 48
  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);
louiz’'s avatar
louiz’ committed
49
  convert_date_format(*Database::db, Database::muc_log_lines);
louiz’'s avatar
louiz’ committed
50 51 52 53 54 55 56 57
  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);
58 59
  Database::after_connection_commands.create(*Database::db);
  Database::after_connection_commands.upgrade(*Database::db);
louiz’'s avatar
louiz’ committed
60
  create_index<Database::Owner, Database::IrcChanName, Database::IrcServerName>(*Database::db, "archive_index", Database::muc_log_lines.get_name());
louiz’'s avatar
louiz’ committed
61
  Database::db->init_session();
62 63
}

64
Database::GlobalOptions Database::get_global_options(const std::string& owner)
65
{
66 67
  auto request = Database::global_options.select();
  request.where() << Owner{} << "=" << owner;
68 69

  Database::GlobalOptions options{Database::global_options.get_name()};
louiz’'s avatar
louiz’ committed
70
  auto result = request.execute(*Database::db);
71 72 73 74 75
  if (result.size() == 1)
    options = result.front();
  else
    options.col<Owner>() = owner;
  return options;
76 77
}

78
Database::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server)
79
{
80 81
  auto request = Database::irc_server_options.select();
  request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
82 83

  Database::IrcServerOptions options{Database::irc_server_options.get_name()};
louiz’'s avatar
louiz’ committed
84
  auto result = request.execute(*Database::db);
85 86 87 88 89 90 91 92
  if (result.size() == 1)
    options = result.front();
  else
    {
      options.col<Owner>() = owner;
      options.col<Server>() = server;
    }
  return options;
93 94
}

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
Database::AfterConnectionCommands Database::get_after_connection_commands(const IrcServerOptions& server_options)
{
  const auto id = server_options.col<Id>();
  if (id == Id::unset_value)
    return {};
  auto request = Database::after_connection_commands.select();
  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<Id>();
  if (id == Id::unset_value)
    return ;
110 111

  Transaction transaction;
112 113 114 115 116 117 118 119 120 121 122
  auto query = Database::after_connection_commands.del();
  query.where() << ForeignKey{} << "=" << id;
  query.execute(*Database::db);

  for (auto& command: commands)
    {
      command.col<ForeignKey>() = server_options.col<Id>();
      command.save(Database::db);
    }
}

123
Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, const std::string& server, const std::string& channel)
124
{
125 126 127 128
  auto request = Database::irc_channel_options.select();
  request.where() << Owner{} << "=" << owner <<\
          " and " << Server{} << "=" << server <<\
          " and " << Channel{} << "=" << channel;
129
  Database::IrcChannelOptions options{Database::irc_channel_options.get_name()};
louiz’'s avatar
louiz’ committed
130
  auto result = request.execute(*Database::db);
131 132 133 134 135 136 137 138 139
  if (result.size() == 1)
    options = result.front();
  else
    {
      options.col<Owner>() = owner;
      options.col<Server>() = server;
      options.col<Channel>() = channel;
    }
  return options;
140 141
}

142 143
Database::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server,
                                                                                  const std::string& channel)
144 145 146
{
  auto coptions = Database::get_irc_channel_options(owner, server, channel);
  auto soptions = Database::get_irc_server_options(owner, server);
147

148 149 150 151
  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>());
152

153 154
  coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
                                                         soptions.col<MaxHistoryLength>());
155 156 157 158

  return coptions;
}

159
Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel)
160 161 162 163 164
{
  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);

165 166
  coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
                                                   soptions.col<EncodingIn>());
167

168 169 170 171 172 173
  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>());
174

175 176 177
  return coptions;
}

178
std::string Database::store_muc_message(const std::string& owner, const std::string& chan_name,
louiz’'s avatar
louiz’ committed
179
                                        const std::string& server_name, DateTime::time_point date,
180
                                        const std::string& body, const std::string& nick)
181
{
182
  auto line = Database::muc_log_lines.row();
183

184 185
  auto uuid = Database::gen_uuid();

186 187 188 189
  line.col<Uuid>() = uuid;
  line.col<Owner>() = owner;
  line.col<IrcChanName>() = chan_name;
  line.col<IrcServerName>() = server_name;
louiz’'s avatar
louiz’ committed
190
  line.col<Date>() = date;
191 192
  line.col<Body>() = body;
  line.col<Nick>() = nick;
193

194
  line.save(Database::db);
195 196

  return uuid;
197 198
}

199
std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
200
                                                   int limit, const std::string& start, const std::string& end, const Id::real_type reference_record_id, Database::Paging paging)
201
{
202 203 204
  if (limit == 0)
    return {};

205 206 207 208
  auto request = Database::muc_log_lines.select();
  request.where() << Database::Owner{} << "=" << owner << \
          " and " << Database::IrcChanName{} << "=" << chan_name << \
          " and " << Database::IrcServerName{} << "=" << server;
209 210 211 212 213

  if (!start.empty())
    {
      const auto start_time = utils::parse_datetime(start);
      if (start_time != -1)
louiz’'s avatar
louiz’ committed
214 215 216 217 218
        {
          DateTime datetime(start_time);
          DatetimeWriter writer(datetime, *Database::db);
          request << " and " << Database::Date{} << ">=" << writer;
        }
219 220 221 222 223
    }
  if (!end.empty())
    {
      const auto end_time = utils::parse_datetime(end);
      if (end_time != -1)
louiz’'s avatar
louiz’ committed
224 225 226 227 228
        {
          DateTime datetime(end_time);
          DatetimeWriter writer(datetime, *Database::db);
          request << " and " << Database::Date{} << "<=" << writer;
        }
229
    }
230
  if (reference_record_id != Id::unset_value)
231
    {
232 233 234 235 236 237
      request << " and " << Id{};
      if (paging == Database::Paging::first)
        request << ">";
      else
        request << "<";
      request << reference_record_id;
238
    }
239

240
  if (paging == Database::Paging::first)
louiz’'s avatar
louiz’ committed
241
    request.order_by() << Database::Date{} << " ASC";
242
  else
louiz’'s avatar
louiz’ committed
243
    request.order_by() << Database::Date{} << " DESC";
244 245 246 247

  if (limit >= 0)
    request.limit() << limit;

louiz’'s avatar
louiz’ committed
248
  auto result = request.execute(*Database::db);
249 250 251 252 253

  if (paging == Database::Paging::first)
    return result;
  else
    return {result.crbegin(), result.crend()};
254 255
}

256 257 258 259 260 261 262 263 264 265 266 267 268
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 = Database::muc_log_lines.select();
  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)
louiz’'s avatar
louiz’ committed
269 270 271 272 273
        {
          DateTime datetime(start_time);
          DatetimeWriter writer(datetime, *Database::db);
          request << " and " << Database::Date{} << ">=" << writer;
        }
274 275 276 277 278
    }
  if (!end.empty())
    {
      const auto end_time = utils::parse_datetime(end);
      if (end_time != -1)
louiz’'s avatar
louiz’ committed
279 280 281 282 283
        {
          DateTime datetime(end_time);
          DatetimeWriter writer(datetime, *Database::db);
          request << " and " << Database::Date{} << "<=" << writer;
        }
284 285 286 287 288 289 290 291 292
    }

  auto result = request.execute(*Database::db);

  if (result.empty())
    throw Database::RecordNotFound{};
  return result.front();
}

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
void Database::add_roster_item(const std::string& local, const std::string& remote)
{
  auto roster_item = Database::roster.row();

  roster_item.col<Database::LocalJid>() = local;
  roster_item.col<Database::RemoteJid>() = remote;

  roster_item.save(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;

louiz’'s avatar
louiz’ committed
309
//  query.execute(*Database::db);
310 311 312 313 314 315 316 317
}

bool Database::has_roster_item(const std::string& local, const std::string& remote)
{
  auto query = Database::roster.select();
  query.where() << Database::LocalJid{} << "=" << local << \
        " and " << Database::RemoteJid{} << "=" << remote;

louiz’'s avatar
louiz’ committed
318
  auto res = query.execute(*Database::db);
319 320 321 322 323 324 325 326 327

  return !res.empty();
}

std::vector<Database::RosterItem> Database::get_contact_list(const std::string& local)
{
  auto query = Database::roster.select();
  query.where() << Database::LocalJid{} << "=" << local;

louiz’'s avatar
louiz’ committed
328
  return query.execute(*Database::db);
329 330 331 332 333 334
}

std::vector<Database::RosterItem> Database::get_full_roster()
{
  auto query = Database::roster.select();

louiz’'s avatar
louiz’ committed
335
  return query.execute(*Database::db);
336 337
}

338 339
void Database::close()
{
340
  Database::db = nullptr;
341
}
342

343 344
std::string Database::gen_uuid()
{
345
  return utils::gen_uuid();
346 347
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
Transaction::Transaction()
{
  const auto result = Database::raw_exec("BEGIN");
  if (std::get<bool>(result) == false)
    log_error("Failed to create SQL transaction: ", std::get<std::string>(result));
  else
    this->success = true;

}

Transaction::~Transaction()
{
  if (this->success)
    {
      const auto result = Database::raw_exec("END");
      if (std::get<bool>(result) == false)
        log_error("Failed to end SQL transaction: ", std::get<std::string>(result));
    }
}
louiz’'s avatar
louiz’ committed
367 368 369 370 371 372 373 374

void Transaction::rollback()
{
  this->success = false;
  const auto result = Database::raw_exec("ROLLBACK");
  if (std::get<bool>(result) == false)
    log_error("Failed to rollback SQL transaction: ", std::get<std::string>(result));
}
375
#endif