~singpolyma/biboumi

61de6b1dac4ef29627f3bdb9ce11b6c0d06f4a24 — louiz’ 5 years ago a90f196
Revert "Use a different Date data type"

This reverts commit 857c7d3972a03cbeebf730d99b924d3710dee6a0.
M CHANGELOG.rst => CHANGELOG.rst +0 -2
@@ 1,8 1,6 @@
Version 8.0
===========

- Changed the data type used to store the dates in the database. This
  requires an automatic migration on the first start.
- Add a complete='true' in MAM’s iq result when appropriate
- The “virtual” channel with an empty name (for example
  %irc.freenode.net@biboumi) has been entirely removed.

M src/bridge/bridge.cpp => src/bridge/bridge.cpp +2 -2
@@ 1009,9 1009,9 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam
  chan_name.append(utils::empty_if_fixed_server("%" + hostname));
  for (const auto& line: lines)
    {
      const DateTime& datetime = line.col<Database::Date>();
      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, datetime);
                                      this->user_jid + "/" + resource, seconds);
    }
#else
  (void)hostname;

M src/database/database.cpp => src/database/database.cpp +9 -34
@@ 48,7 48,6 @@ void Database::open(const std::string& filename)
  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);


@@ 60,9 59,9 @@ void Database::open(const std::string& filename)
  Database::after_connection_commands.create(*Database::db);
  Database::after_connection_commands.upgrade(*Database::db);
  create_index<Database::Owner, Database::IrcChanName, Database::IrcServerName>(*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);


@@ 171,7 170,7 @@ Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_gl
}

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& server_name, Database::time_point date,
                                        const std::string& body, const std::string& nick)
{
  auto line = Database::muc_log_lines.row();


@@ 182,7 181,7 @@ std::string Database::store_muc_message(const std::string& owner, const std::str
  line.col<Owner>() = owner;
  line.col<IrcChanName>() = chan_name;
  line.col<IrcServerName>() = server_name;
  line.col<Date>() = date;
  line.col<Date>() = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
  line.col<Body>() = body;
  line.col<Nick>() = nick;



@@ 206,21 205,13 @@ std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owne
    {
      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;
        }
        request << " and " << Database::Date{} << ">=" << start_time;
    }
  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;
        }
        request << " and " << Database::Date{} << "<=" << end_time;
    }
  if (reference_record_id != Id::unset_value)
    {


@@ 233,9 224,9 @@ std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owne
    }

  if (paging == Database::Paging::first)
    request.order_by() << Database::Date{} << " ASC";
    request.order_by() << Database::Date{} << " ASC, " << Id{} << " ASC ";
  else
    request.order_by() << Database::Date{} << " DESC";
    request.order_by() << Database::Date{} << " DESC, " << Id{} << " DESC ";

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


@@ 261,21 252,13 @@ Database::MucLogLine Database::get_muc_log(const std::string& owner, const std::
    {
      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;
        }
        request << " and " << Database::Date{} << ">=" << start_time;
    }
  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;
        }
        request << " and " << Database::Date{} << "<=" << end_time;
    }

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


@@ 359,12 342,4 @@ Transaction::~Transaction()
        log_error("Failed to end SQL transaction: ", std::get<std::string>(result));
    }
}

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));
}
#endif

M src/database/database.hpp => src/database/database.hpp +4 -26
@@ 10,7 10,6 @@
#include <database/engine.hpp>

#include <utils/optional_bool.hpp>
#include <utils/datetime.hpp>

#include <chrono>
#include <string>


@@ 18,9 17,11 @@
#include <memory>
#include <map>


class Database
{
 public:
  using time_point = std::chrono::system_clock::time_point;
  struct RecordNotFound: public std::exception {};
  enum class Paging { first, last };



@@ 36,8 37,7 @@ class Database

  struct Server: Column<std::string> { static constexpr auto name = "server_"; };

  struct OldDate: Column<std::chrono::system_clock::time_point::rep> { static constexpr auto name = "date_"; };
  struct Date: Column<DateTime> { static constexpr auto name = "date_"; };
  struct Date: Column<time_point::rep> { static constexpr auto name = "date_"; };

  struct Body: Column<std::string> { static constexpr auto name = "body_"; };



@@ 88,8 88,6 @@ class Database

  using MucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, Date, Body, Nick>;
  using MucLogLine = MucLogLineTable::RowType;
  using OldMucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, OldDate, Body, Nick>;
  using OldMucLogLine = OldMucLogLineTable::RowType;

  using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory, GlobalPersistent>;
  using GlobalOptions = GlobalOptionsTable::RowType;


@@ 143,7 141,7 @@ class Database
   */
  static MucLogLine 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="");
  static std::string 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);
                                       time_point date, const std::string& body, const std::string& nick);

  static void add_roster_item(const std::string& local, const std::string& remote);
  static bool has_roster_item(const std::string& local, const std::string& remote);


@@ 170,13 168,6 @@ class Database

  static std::unique_ptr<DatabaseEngine> db;

  static DatabaseEngine::EngineType engine_type()
  {
    if (Database::db)
      return Database::db->engine_type();
    return DatabaseEngine::EngineType::None;
  }

  /**
   * Some caches, to avoid doing very frequent query requests for a few options.
   */


@@ 225,20 216,7 @@ class Transaction
public:
  Transaction();
  ~Transaction();
  void rollback();
  bool success{false};
};

template <typename... T>
void convert_date_format(DatabaseEngine& db, Table<T...> table)
{
  const auto existing_columns = db.get_all_columns_from_table(table.get_name());
  const auto date_pair = existing_columns.find(Database::Date::name);
  if (date_pair != existing_columns.end() && date_pair->second == "integer")
    {
      log_info("Converting Date_ format to the new one.");
      db.convert_date_format(db);
    }
}

#endif /* USE_DATABASE */

D src/database/datetime_writer.hpp => src/database/datetime_writer.hpp +0 -32
@@ 1,32 0,0 @@
#pragma once

#include <utils/datetime.hpp>
#include <database/engine.hpp>

#include <logger/logger.hpp>
#include <database/postgresql_engine.hpp>
#include <database/sqlite3_engine.hpp>

class DatetimeWriter
{
public:
  DatetimeWriter(DateTime datetime, const DatabaseEngine& engine):
      datetime(datetime),
      engine(engine)
  {}

  long double get_value() const
  {
    const long double epoch_duration = this->datetime.epoch().count();
    const long double epoch_seconds = epoch_duration / std::chrono::system_clock::period::den;
    return this->engine.epoch_to_floating_value(epoch_seconds);
  }
  std::string escape_param_number(int value) const
  {
    return this->engine.escape_param_number(value);
  }

private:
  const DateTime datetime;
  const DatabaseEngine& engine;
};

M src/database/engine.hpp => src/database/engine.hpp +2 -16
@@ 13,7 13,6 @@
#include <string>
#include <vector>
#include <tuple>
#include <map>
#include <set>

class DatabaseEngine


@@ 28,10 27,7 @@ class DatabaseEngine
  DatabaseEngine(DatabaseEngine&&) = delete;
  DatabaseEngine& operator=(DatabaseEngine&&) = delete;

  enum class EngineType { None, Postgresql, Sqlite3, };
  virtual EngineType engine_type() const = 0;

  virtual std::map<std::string, std::string> get_all_columns_from_table(const std::string& table_name) = 0;
  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;


@@ 39,17 35,7 @@ class DatabaseEngine
  {
    return {};
  }
  virtual void convert_date_format(DatabaseEngine&) = 0;
  virtual std::string id_column_type() const = 0;
  virtual std::string datetime_column_type() const = 0;
  virtual long double epoch_to_floating_value(long double seconds) const = 0;
  virtual std::string escape_param_number(int nb) const
  {
    return "$" + std::to_string(nb);
  }
  virtual void init_session()
  {
  }
  virtual std::string id_column_type() = 0;

  int64_t last_inserted_rowid{-1};
};

D src/database/insert_query.cpp => src/database/insert_query.cpp +0 -21
@@ 1,21 0,0 @@
#include <database/insert_query.hpp>

template <>
std::string before_value<Database::Date>()
{
  if (Database::engine_type() == DatabaseEngine::EngineType::Sqlite3)
    return "julianday(";
  if (Database::engine_type() == DatabaseEngine::EngineType::Postgresql)
    return "to_timestamp(";
  return {};
}

template <>
std::string after_value<Database::Date>()
{
  if (Database::engine_type() == DatabaseEngine::EngineType::Sqlite3)
    return ", \"unixepoch\")";
  if (Database::engine_type() == DatabaseEngine::EngineType::Postgresql)
    return ")";
  return {};
}

M src/database/insert_query.hpp => src/database/insert_query.hpp +1 -21
@@ 30,24 30,6 @@ typename std::enable_if<N == sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>&, Statement&)
{}

template <typename T>
std::string before_value()
{
  return {};
}

template <typename T>
std::string after_value()
{
  return {};
}

template <>
std::string before_value<Database::Date>();

template <>
std::string after_value<Database::Date>();

struct InsertQuery: public Query
{
  template <typename... T>


@@ 96,7 78,7 @@ struct InsertQuery: public Query
  template <typename... T>
  void insert_values(const std::tuple<T...>& columns)
  {
    this->body += " VALUES (";
    this->body += "VALUES (";
    this->insert_value(columns);
    this->body += ")";
  }


@@ 109,9 91,7 @@ struct InsertQuery: public Query

    if (!std::is_same<ColumnType, Id>::value)
      {
        this->body += before_value<ColumnType>();
        this->body += "$" + std::to_string(index++);
        this->body += after_value<ColumnType>();
        if (N != sizeof...(T) - 1)
          this->body += ", ";
      }

M src/database/postgresql_engine.cpp => src/database/postgresql_engine.cpp +5 -39
@@ 2,7 2,6 @@
#ifdef PQ_FOUND

#include <utils/scopeguard.hpp>
#include <utils/tolower.hpp>

#include <database/query.hpp>



@@ 13,7 12,6 @@
#include <logger/logger.hpp>

#include <cstring>
#include <database/database.hpp>

PostgresqlEngine::PostgresqlEngine(PGconn*const conn):
    conn(conn)


@@ 54,14 52,14 @@ std::unique_ptr<DatabaseEngine> PostgresqlEngine::open(const std::string& connin
  return std::make_unique<PostgresqlEngine>(con);
}

std::map<std::string, std::string> PostgresqlEngine::get_all_columns_from_table(const std::string& table_name)
std::set<std::string> PostgresqlEngine::get_all_columns_from_table(const std::string& table_name)
{
  const auto query = "SELECT column_name, data_type from information_schema.columns where table_name='" + table_name + "'";
  const auto query = "SELECT column_name from information_schema.columns where table_name='" + table_name + "'";
  auto statement = this->prepare(query);
  std::map<std::string, std::string> columns;
  std::set<std::string> columns;

  while (statement->step() == StepResult::Row)
    columns[utils::tolower(statement->get_column_text(0))] = utils::tolower(statement->get_column_text(1));
    columns.insert(statement->get_column_text(0));

  return columns;
}


@@ 98,41 96,9 @@ std::string PostgresqlEngine::get_returning_id_sql_string(const std::string& col
  return " RETURNING " + col_name;
}

std::string PostgresqlEngine::id_column_type() const
std::string PostgresqlEngine::id_column_type()
{
  return "SERIAL";
}

std::string PostgresqlEngine::datetime_column_type() const
{
  return "TIMESTAMP";
}

void PostgresqlEngine::convert_date_format(DatabaseEngine& db)
{
  const auto table_name = Database::muc_log_lines.get_name();
  const std::string column_name = Database::Date::name;
  const std::string query = "ALTER TABLE " + table_name + " ALTER COLMUN " + column_name + " SET DATA TYPE timestamp USING to_timestamp(" + column_name + ")";

  auto result = db.raw_exec(query);
  if (!std::get<bool>(result))
    log_error("Failed to execute query: ", std::get<std::string>(result));
}

std::string PostgresqlEngine::escape_param_number(int nb) const
{
  return "to_timestamp(" + DatabaseEngine::escape_param_number(nb) + ")";
}

void PostgresqlEngine::init_session()
{
  const auto res = this->raw_exec("SET SESSION TIME ZONE 'UTC'");
  if (!std::get<bool>(res))
    log_error("Failed to set UTC timezone: ", std::get<std::string>(res));
}
long double PostgresqlEngine::epoch_to_floating_value(long double seconds) const
{
  return seconds;
}

#endif

M src/database/postgresql_engine.hpp => src/database/postgresql_engine.hpp +2 -11
@@ 23,22 23,13 @@ class PostgresqlEngine: public DatabaseEngine
  ~PostgresqlEngine();

  static std::unique_ptr<DatabaseEngine> open(const std::string& string);
  EngineType engine_type() const override
  {
    return EngineType::Postgresql;
  }

  std::map<std::string, std::string> get_all_columns_from_table(const std::string& table_name) override final;
  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() const override;
  std::string datetime_column_type() const override;
  void convert_date_format(DatabaseEngine& engine) override;
  long double epoch_to_floating_value(long double seconds) const override;
  void init_session() override;
  std::string escape_param_number(int nb) const override;
  std::string id_column_type() override;
private:
  PGconn* const conn;
};

M src/database/query.cpp => src/database/query.cpp +0 -16
@@ 21,13 21,6 @@ void actual_bind(Statement& statement, const OptionalBool& value, int index)
    statement.bind_int64(index, -1);
}

void actual_bind(Statement& statement, const DateTime& value, int index)
{
  const auto epoch = value.epoch().count();
  const auto result = std::to_string(static_cast<long double>(epoch) / std::chrono::system_clock::period::den);
  statement.bind_text(index, result);
}

void actual_add_param(Query& query, const std::string& val)
{
  query.params.push_back(val);


@@ 56,12 49,3 @@ Query& operator<<(Query& query, const std::string& str)
  actual_add_param(query, str);
  return query;
}

Query& operator<<(Query& query, const DatetimeWriter& datetime_writer)
{
  query.body += datetime_writer.escape_param_number(query.current_param++);
  actual_add_param(query, datetime_writer.get_value());
  return query;
}



M src/database/query.hpp => src/database/query.hpp +3 -7
@@ 3,7 3,6 @@
#include <biboumi.h>

#include <utils/optional_bool.hpp>
#include <utils/datetime.hpp>
#include <database/statement.hpp>
#include <database/column.hpp>



@@ 11,7 10,6 @@

#include <vector>
#include <string>
#include <database/datetime_writer.hpp>

void actual_bind(Statement& statement, const std::string& value, int index);
void actual_bind(Statement& statement, const std::int64_t& value, int index);


@@ 21,7 19,6 @@ void actual_bind(Statement& statement, const T& value, int index)
  actual_bind(statement, static_cast<std::int64_t>(value), index);
}
void actual_bind(Statement& statement, const OptionalBool& value, int index);
void actual_bind(Statement& statement, const DateTime& value, int index);

#ifdef DEBUG_SQL_QUERIES
#include <utils/scopetimer.hpp>


@@ 77,13 74,15 @@ void actual_add_param(Query& query, const std::string& val);
void actual_add_param(Query& query, const OptionalBool& val);

template <typename T>
typename std::enable_if<!std::is_arithmetic<T>::value, Query&>::type
typename std::enable_if<!std::is_integral<T>::value, Query&>::type
operator<<(Query& query, const T&)
{
  query.body += T::name;
  return query;
}

Query& operator<<(Query& query, const char* str);
Query& operator<<(Query& query, const std::string& str);
template <typename Integer>
typename std::enable_if<std::is_integral<Integer>::value, Query&>::type
operator<<(Query& query, const Integer& i)


@@ 93,6 92,3 @@ operator<<(Query& query, const Integer& i)
  return query;
}

Query& operator<<(Query& query, const char* str);
Query& operator<<(Query& query, const std::string& str);
Query& operator<<(Query& query, const DatetimeWriter& datetime);

D src/database/select_query.cpp => src/database/select_query.cpp +0 -21
@@ 1,21 0,0 @@
#include <database/select_query.hpp>

template <>
std::string before_column<Database::Date>()
{
  if (Database::engine_type() == DatabaseEngine::EngineType::Sqlite3)
    return "strftime(\"%Y-%m-%dT%H:%M:%SZ\", ";
  else if (Database::engine_type() == DatabaseEngine::EngineType::Postgresql)
    return "to_char(";
  return {};
}

template <>
std::string after_column<Database::Date>()
{
  if (Database::engine_type() == DatabaseEngine::EngineType::Sqlite3)
    return ")";
  else if (Database::engine_type() == DatabaseEngine::EngineType::Postgresql)
    return R"(, 'YYYY-MM-DD"T"HH24:MM:SS"Z"'))";
  return {};
}

M src/database/select_query.hpp => src/database/select_query.hpp +2 -29
@@ 5,8 5,8 @@
#include <database/table.hpp>
#include <database/database.hpp>
#include <database/statement.hpp>
#include <utils/datetime.hpp>
#include <database/query.hpp>
#include <logger/logger.hpp>
#include <database/row.hpp>

#include <utils/optional_bool.hpp>


@@ 43,14 43,6 @@ extract_row_value(Statement& statement, const int i)
  return result;
}

template <typename T>
typename std::enable_if<std::is_same<DateTime, T>::value, T>::type
extract_row_value(Statement& statement, const int i)
{
  const std::string timestamp = statement.get_column_text(i);
  return {timestamp};
}

template <std::size_t N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
extract_row_values(Row<T...>& row, Statement& statement)


@@ 68,24 60,6 @@ typename std::enable_if<N == sizeof...(T), void>::type
extract_row_values(Row<T...>&, Statement&)
{}

template <typename ColumnType>
std::string before_column()
{
  return {};
}

template <typename ColumnType>
std::string after_column()
{
  return {};
}

template <>
std::string before_column<Database::Date>();

template <>
std::string after_column<Database::Date>();

template <typename... T>
struct SelectQuery: public Query
{


@@ 104,8 78,7 @@ struct SelectQuery: public Query
      using ColumnsType = std::tuple<T...>;
      using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnsType>()))>::type;

      this->body += " ";
      this->body += before_column<ColumnType>() + ColumnType::name + after_column<ColumnType>();
      this->body += " " + std::string{ColumnType::name};

      if (N < (sizeof...(T) - 1))
        this->body += ", ";

M src/database/sqlite3_engine.cpp => src/database/sqlite3_engine.cpp +6 -62
@@ 2,10 2,7 @@

#ifdef SQLITE3_FOUND

#include <database/database.hpp>
#include <database/select_query.hpp>
#include <database/sqlite3_engine.hpp>

#include <database/sqlite3_statement.hpp>

#include <database/query.hpp>


@@ 24,17 21,16 @@ Sqlite3Engine::~Sqlite3Engine()
  sqlite3_close(this->db);
}

std::map<std::string, std::string> Sqlite3Engine::get_all_columns_from_table(const std::string& table_name)
std::set<std::string> Sqlite3Engine::get_all_columns_from_table(const std::string& table_name)
{
  std::map<std::string, std::string> result;
  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;
    constexpr int data_type_column = 2;
    auto* result = static_cast<std::map<std::string, std::string>*>(param);
    if (name_column < columns_nb && data_type_column < columns_nb)
      (*result)[utils::tolower(columns[name_column])] = utils::tolower(columns[data_type_column]);
    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);



@@ 47,48 43,6 @@ std::map<std::string, std::string> Sqlite3Engine::get_all_columns_from_table(con
  return result;
}

template <typename... T>
static auto make_select_query(const Row<T...>&, const std::string& name)
{
  return SelectQuery<T...>{name};
}

void Sqlite3Engine::convert_date_format(DatabaseEngine& db)
{
  Transaction transaction{};
  auto rollback = [&transaction] (const std::string& error_msg)
  {
    log_error("Failed to execute query: ", error_msg);
    transaction.rollback();
  };

  const auto real_name = Database::muc_log_lines.get_name();
  const auto tmp_name = real_name + "tmp_";
  const std::string date_name = Database::Date::name;

  auto result = db.raw_exec("ALTER TABLE " + real_name + " RENAME TO " + tmp_name);
  if (!std::get<bool>(result))
    return rollback(std::get<std::string>(result));

  Database::muc_log_lines.create(db);

  Database::OldMucLogLineTable old_muc_log_line(tmp_name);
  auto select_query = make_select_query(old_muc_log_line.row(), old_muc_log_line.get_name());

  auto& select_body = select_query.body;
  auto begin = select_body.find(date_name);
  select_body.replace(begin, date_name.size(), "julianday("+date_name+", 'unixepoch')");
  select_body = "INSERT INTO " + real_name + " " + select_body;

  result = db.raw_exec(select_body);
  if (!std::get<bool>(result))
    return rollback(std::get<std::string>(result));

  result = db.raw_exec("DROP TABLE " + tmp_name);
  if (!std::get<bool>(result))
    return rollback(std::get<std::string>(result));
}

std::unique_ptr<DatabaseEngine> Sqlite3Engine::open(const std::string& filename)
{
  sqlite3* new_db;


@@ 138,19 92,9 @@ void Sqlite3Engine::extract_last_insert_rowid(Statement&)
  this->last_inserted_rowid = sqlite3_last_insert_rowid(this->db);
}

std::string Sqlite3Engine::id_column_type() const
std::string Sqlite3Engine::id_column_type()
{
  return "INTEGER PRIMARY KEY AUTOINCREMENT";
}

std::string Sqlite3Engine::datetime_column_type() const
{
  return "REAL";
}

long double Sqlite3Engine::epoch_to_floating_value(long double d) const
{
  return (d / 86400.0) + 2440587.5;
}

#endif

M src/database/sqlite3_engine.hpp => src/database/sqlite3_engine.hpp +2 -9
@@ 23,19 23,12 @@ class Sqlite3Engine: public DatabaseEngine
  ~Sqlite3Engine();

  static std::unique_ptr<DatabaseEngine> open(const std::string& string);
  EngineType engine_type() const override
  {
    return EngineType::Sqlite3;
  }

  std::map<std::string, std::string> get_all_columns_from_table(const std::string& table_name) override final;
  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() const override;
  std::string datetime_column_type() const override;
  void convert_date_format(DatabaseEngine&) override;
  long double epoch_to_floating_value(long double d) const override;
  std::string id_column_type() override;
private:
  sqlite3* const db;
};

M src/database/table.hpp => src/database/table.hpp +3 -5
@@ 18,8 18,6 @@ std::string ToSQLType(DatabaseEngine& db)
    return db.id_column_type();
  else if (std::is_same<typename T::real_type, std::string>::value)
    return "TEXT";
  else if (std::is_same<typename T::real_type, DateTime>::value)
    return db.datetime_column_type();
  else
    return "INTEGER";
}


@@ 98,16 96,16 @@ class Table

  template <std::size_t N=0>
  typename std::enable_if<N < sizeof...(T), void>::type
  add_column_if_not_exists(DatabaseEngine& db, const std::map<std::string, 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;
    if (existing_columns.find(ColumnType::name) == existing_columns.end())
    if (existing_columns.count(ColumnType::name) == 0)
      add_column_to_table<ColumnType>(db, this->name);
    add_column_if_not_exists<N+1>(db, existing_columns);
  }
  template <std::size_t N=0>
  typename std::enable_if<N == sizeof...(T), void>::type
  add_column_if_not_exists(DatabaseEngine&, const std::map<std::string, std::string>&)
  add_column_if_not_exists(DatabaseEngine&, const std::set<std::string>&)
  {}

  template <std::size_t N=0>

D src/utils/datetime.hpp => src/utils/datetime.hpp +0 -56
@@ 1,56 0,0 @@
#pragma once

#include <chrono>
#include <string>

#include <logger/logger.hpp>

class DateTime
{
public:
  enum class Engine {
    Postgresql,
    Sqlite3,
  } engine{Engine::Sqlite3};

  using time_point = std::chrono::system_clock::time_point;

  DateTime():
      s{},
      t{}
  { }

  DateTime(std::time_t t):
      t(std::chrono::seconds(t))
  {}

  DateTime(std::string s):
      s(std::move(s))
  {}

  DateTime& operator=(const std::string& s)
  {
    this->s = s;
    return *this;
  }

  DateTime& operator=(const time_point t)
  {
    this->t = t;
    return *this;
  }

  const std::string& to_string() const
  {
    return this->s;
  }

  time_point::duration epoch() const
  {
    return this->t.time_since_epoch();
  }

private:
  std::string s;
  time_point t;
};

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +1 -1
@@ 805,7 805,7 @@ void BiboumiComponent::send_archived_message(const Database::MucLogLine& log_lin

    XmlSubNode delay(forwarded, "delay");
    delay["xmlns"] = DELAY_NS;
    delay["stamp"] = log_line.col<Database::Date>().to_string();
    delay["stamp"] = utils::to_string(log_line.col<Database::Date>());

    XmlSubNode submessage(forwarded, "message");
    submessage["xmlns"] = CLIENT_NS;

M src/xmpp/xmpp_component.cpp => src/xmpp/xmpp_component.cpp +2 -2
@@ 399,7 399,7 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str
}

#ifdef USE_DATABASE
void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, const DateTime& timestamp)
void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, Database::time_point::rep timestamp)
{
  Stanza message("message");
  message["to"] = jid_to;


@@ 417,7 417,7 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std:
    XmlSubNode delay(message, "delay");
    delay["xmlns"] = DELAY_NS;
    delay["from"] = muc_name + "@" + this->served_hostname;
    delay["stamp"] = timestamp.to_string();
    delay["stamp"] = utils::to_string(timestamp);
  }

  this->send_stanza(message);

M src/xmpp/xmpp_component.hpp => src/xmpp/xmpp_component.hpp +1 -2
@@ 6,7 6,6 @@
#include <network/tcp_client_socket_handler.hpp>
#include <database/database.hpp>
#include <xmpp/xmpp_parser.hpp>
#include <utils/datetime.hpp>
#include <xmpp/body.hpp>

#include <unordered_map>


@@ 142,7 141,7 @@ public:
   * Send a message, with a <delay/> element, part of a MUC history
   */
  void send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body,
                            const std::string& jid_to, const DateTime& timestamp);
                            const std::string& jid_to, Database::time_point::rep timestamp);
#endif
  /**
   * Send an unavailable presence for this nick

M tests/end_to_end/__main__.py => tests/end_to_end/__main__.py +1 -1
@@ 1953,7 1953,7 @@ if __name__ == '__main__':
                    <query xmlns='urn:xmpp:mam:2' queryid='qid3'>
                    <x xmlns='jabber:x:data' type='submit'>
                    <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field>
                    <field var='start'><value>2222-06-07T00:00:00Z</value></field>
                    <field var='start'><value>3016-06-07T00:00:00Z</value></field>
                    </x>
                    </query></iq>"""),