modernize: Use '= default' instead of empty ctor/dtor bodies
[quassel.git] / src / core / postgresqlstorage.cpp
index 3965704..6b97104 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   Copyright (C) 2005-2018 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 
 #include <QtSql>
 
-#include "logger.h"
+#include "logmessage.h"
 #include "network.h"
 #include "quassel.h"
 
 PostgreSqlStorage::PostgreSqlStorage(QObject *parent)
-    : AbstractSqlStorage(parent),
-    _port(-1)
+    : AbstractSqlStorage(parent)
 {
 }
 
 
-PostgreSqlStorage::~PostgreSqlStorage()
+std::unique_ptr<AbstractSqlMigrationWriter> PostgreSqlStorage::createMigrationWriter()
 {
-}
-
-
-AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter()
-{
-    PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
+    auto writer = new PostgreSqlMigrationWriter();
     QVariantMap properties;
     properties["Username"] = _userName;
     properties["Password"] = _password;
     properties["Hostname"] = _hostName;
     properties["Port"] = _port;
     properties["Database"] = _databaseName;
-    writer->setConnectionProperties(properties);
-    return writer;
+    writer->setConnectionProperties(properties, {}, false);
+    return std::unique_ptr<AbstractSqlMigrationWriter>{writer};
 }
 
 
 bool PostgreSqlStorage::isAvailable() const
 {
-    qDebug() << QSqlDatabase::drivers();
-    if (!QSqlDatabase::isDriverAvailable("QPSQL")) return false;
+    if (!QSqlDatabase::isDriverAvailable("QPSQL")) {
+        quWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:"))
+                    << qPrintable(QSqlDatabase::drivers().join(", "));
+        return false;
+    }
     return true;
 }
 
 
-QString PostgreSqlStorage::displayName() const
+QString PostgreSqlStorage::backendId() const
 {
     return QString("PostgreSQL");
 }
 
 
+QString PostgreSqlStorage::displayName() const
+{
+    return backendId(); // Note: Pre-0.13 clients use the displayName property for backend idenfication
+}
+
+
 QString PostgreSqlStorage::description() const
 {
     // FIXME: proper description
@@ -73,55 +76,104 @@ QString PostgreSqlStorage::description() const
 }
 
 
-QStringList PostgreSqlStorage::setupKeys() const
+QVariantList PostgreSqlStorage::setupData() const
 {
-    QStringList keys;
-    keys << "Username"
-         << "Password"
-         << "Hostname"
-         << "Port"
-         << "Database";
-    return keys;
+    QVariantList data;
+    data << "Username" << tr("Username") << QString("quassel")
+         << "Password" << tr("Password") << QString()
+         << "Hostname" << tr("Hostname") << QString("localhost")
+         << "Port"     << tr("Port")     << 5432
+         << "Database" << tr("Database") << QString("quassel")
+         ;
+    return data;
 }
 
 
-QVariantMap PostgreSqlStorage::setupDefaults() const
+bool PostgreSqlStorage::initDbSession(QSqlDatabase &db)
 {
-    QVariantMap map;
-    map["Username"] = QVariant(QString("quassel"));
-    map["Hostname"] = QVariant(QString("localhost"));
-    map["Port"] = QVariant(5432);
-    map["Database"] = QVariant(QString("quassel"));
-    return map;
-}
+    // check whether the Qt driver performs string escaping or not.
+    // i.e. test if it doubles slashes.
+    QSqlField testField;
+    testField.setType(QVariant::String);
+    testField.setValue("\\");
+    QString formattedString = db.driver()->formatValue(testField);
+    switch(formattedString.count('\\')) {
+    case 2:
+        // yes it does... and we cannot do anything to change the behavior of Qt.
+        // If this is a legacy DB (Postgres < 8.2), then everything is already ok,
+        // as this is the expected behavior.
+        // If it is a newer version, switch to legacy mode.
+
+        quWarning() << "Switching Postgres to legacy mode. (set standard conforming strings to off)";
+        // If the following calls fail, it is a legacy DB anyways, so it doesn't matter
+        // and no need to check the outcome.
+        db.exec("set standard_conforming_strings = off");
+        db.exec("set escape_string_warning = off");
+        break;
+    case 1:
+        // ok, so Qt does not escape...
+        // That means we have to ensure that postgres uses standard conforming strings...
+        {
+            QSqlQuery query = db.exec("set standard_conforming_strings = on");
+            if (query.lastError().isValid()) {
+                // We cannot enable standard conforming strings...
+                // since Quassel does no escaping by itself, this would yield a major vulnerability.
+                quError() << "Failed to enable standard_conforming_strings for the Postgres db!";
+                return false;
+            }
+        }
+        break;
+    default:
+        // The slash got replaced with 0 or more than 2 slashes! o_O
+        quError() << "Your version of Qt does something _VERY_ strange to slashes in QSqlQueries! You should consult your trusted doctor!";
+        return false;
+        break;
+    }
 
+    // Set the PostgreSQL session timezone to UTC, since we want timestamps stored in UTC
+    QSqlQuery tzQuery = db.exec("SET timezone = 'UTC'");
+    if (tzQuery.lastError().isValid()) {
+        quError() << "Failed to set timezone to UTC!";
+        return false;
+    }
 
-void PostgreSqlStorage::initDbSession(QSqlDatabase &db)
-{
-    // this blows... but unfortunately Qt's PG driver forces us to this...
-    db.exec("set standard_conforming_strings = off");
-    db.exec("set escape_string_warning = off");
+    return true;
 }
 
 
-void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties)
+void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties,
+                                                const QProcessEnvironment &environment,
+                                                bool loadFromEnvironment)
 {
-    _userName = properties["Username"].toString();
-    _password = properties["Password"].toString();
-    _hostName = properties["Hostname"].toString();
-    _port = properties["Port"].toInt();
-    _databaseName = properties["Database"].toString();
+    if (loadFromEnvironment) {
+        _userName = environment.value("DB_PGSQL_USERNAME");
+        _password = environment.value("DB_PGSQL_PASSWORD");
+        _hostName = environment.value("DB_PGSQL_HOSTNAME");
+        _port = environment.value("DB_PGSQL_PORT").toInt();
+        _databaseName = environment.value("DB_PGSQL_DATABASE");
+    } else {
+        _userName = properties["Username"].toString();
+        _password = properties["Password"].toString();
+        _hostName = properties["Hostname"].toString();
+        _port = properties["Port"].toInt();
+        _databaseName = properties["Database"].toString();
+    }
 }
 
 
 int PostgreSqlStorage::installedSchemaVersion()
 {
-    QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
+    QSqlQuery query(logDb());
+    query.prepare("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
+    safeExec(query);
+    watchQuery(query);
     if (query.first())
         return query.value(0).toInt();
 
     // maybe it's really old... (schema version 0)
-    query = logDb().exec("SELECT MAX(version) FROM coreinfo");
+    query.prepare("SELECT MAX(version) FROM coreinfo");
+    safeExec(query);
+    watchQuery(query);
     if (query.first())
         return query.value(0).toInt();
 
@@ -134,10 +186,10 @@ bool PostgreSqlStorage::updateSchemaVersion(int newVersion)
     QSqlQuery query(logDb());
     query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'");
     query.bindValue(":version", newVersion);
-    query.exec();
+    safeExec(query);
 
     bool success = true;
-    if (query.lastError().isValid()) {
+    if (!watchQuery(query)) {
         qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!";
         success = false;
     }
@@ -150,10 +202,10 @@ bool PostgreSqlStorage::setupSchemaVersion(int version)
     QSqlQuery query(logDb());
     query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaversion', :version)");
     query.bindValue(":version", version);
-    query.exec();
+    safeExec(query);
 
     bool success = true;
-    if (query.lastError().isValid()) {
+    if (!watchQuery(query)) {
         qCritical() << "PostgreSqlStorage::setupSchemaVersion(int): Updating schema version failed!";
         success = false;
     }
@@ -161,12 +213,14 @@ bool PostgreSqlStorage::setupSchemaVersion(int version)
 }
 
 
-UserId PostgreSqlStorage::addUser(const QString &user, const QString &password)
+UserId PostgreSqlStorage::addUser(const QString &user, const QString &password, const QString &authenticator)
 {
     QSqlQuery query(logDb());
     query.prepare(queryString("insert_quasseluser"));
     query.bindValue(":username", user);
-    query.bindValue(":password", cryptedPassword(password));
+    query.bindValue(":password", hashPassword(password));
+    query.bindValue(":hashversion", Storage::HashVersion::Latest);
+    query.bindValue(":authenticator", authenticator);
     safeExec(query);
     if (!watchQuery(query))
         return 0;
@@ -183,8 +237,10 @@ bool PostgreSqlStorage::updateUser(UserId user, const QString &password)
     QSqlQuery query(logDb());
     query.prepare(queryString("update_userpassword"));
     query.bindValue(":userid", user.toInt());
-    query.bindValue(":password", cryptedPassword(password));
+    query.bindValue(":password", hashPassword(password));
+    query.bindValue(":hashversion", Storage::HashVersion::Latest);
     safeExec(query);
+    watchQuery(query);
     return query.numRowsAffected() != 0;
 }
 
@@ -196,6 +252,7 @@ void PostgreSqlStorage::renameUser(UserId user, const QString &newName)
     query.bindValue(":userid", user.toInt());
     query.bindValue(":username", newName);
     safeExec(query);
+    watchQuery(query);
     emit userRenamed(user, newName);
 }
 
@@ -205,10 +262,10 @@ UserId PostgreSqlStorage::validateUser(const QString &user, const QString &passw
     QSqlQuery query(logDb());
     query.prepare(queryString("select_authuser"));
     query.bindValue(":username", user);
-    query.bindValue(":password", cryptedPassword(password));
     safeExec(query);
+    watchQuery(query);
 
-    if (query.first()) {
+    if (query.first() && checkHashedPassword(query.value(0).toInt(), password, query.value(1).toString(), static_cast<Storage::HashVersion>(query.value(2).toInt()))) {
         return query.value(0).toInt();
     }
     else {
@@ -223,6 +280,7 @@ UserId PostgreSqlStorage::getUserId(const QString &user)
     query.prepare(queryString("select_userid"));
     query.bindValue(":username", user);
     safeExec(query);
+    watchQuery(query);
 
     if (query.first()) {
         return query.value(0).toInt();
@@ -232,12 +290,28 @@ UserId PostgreSqlStorage::getUserId(const QString &user)
     }
 }
 
+QString PostgreSqlStorage::getUserAuthenticator(const UserId userid)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("select_authenticator"));
+    query.bindValue(":userid", userid.toInt());
+    safeExec(query);
+    watchQuery(query);
+
+    if (query.first()) {
+        return query.value(0).toString();
+    }
+    else {
+        return QString("");
+    }
+}
 
 UserId PostgreSqlStorage::internalUser()
 {
     QSqlQuery query(logDb());
     query.prepare(queryString("select_internaluser"));
     safeExec(query);
+    watchQuery(query);
 
     if (query.first()) {
         return query.value(0).toInt();
@@ -251,7 +325,7 @@ UserId PostgreSqlStorage::internalUser()
 void PostgreSqlStorage::delUser(UserId user)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::delUser(): cannot start transaction!";
         return;
     }
@@ -284,6 +358,7 @@ void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName
     selectQuery.bindValue(":userid", userId.toInt());
     selectQuery.bindValue(":settingname", settingName);
     safeExec(selectQuery);
+    watchQuery(selectQuery);
 
     QString setQueryString;
     if (!selectQuery.first()) {
@@ -299,6 +374,7 @@ void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName
     setQuery.bindValue(":settingname", settingName);
     setQuery.bindValue(":settingvalue", rawData);
     safeExec(setQuery);
+    watchQuery(setQuery);
 }
 
 
@@ -309,6 +385,7 @@ QVariant PostgreSqlStorage::getUserSetting(UserId userId, const QString &setting
     query.bindValue(":userid", userId.toInt());
     query.bindValue(":settingname", settingName);
     safeExec(query);
+    watchQuery(query);
 
     if (query.first()) {
         QVariant data;
@@ -324,12 +401,64 @@ QVariant PostgreSqlStorage::getUserSetting(UserId userId, const QString &setting
 }
 
 
+void PostgreSqlStorage::setCoreState(const QVariantList &data)
+{
+    QByteArray rawData;
+    QDataStream out(&rawData, QIODevice::WriteOnly);
+    out.setVersion(QDataStream::Qt_4_2);
+    out << data;
+
+    QSqlDatabase db = logDb();
+    QSqlQuery selectQuery(db);
+    selectQuery.prepare(queryString("select_core_state"));
+    selectQuery.bindValue(":key", "active_sessions");
+    safeExec(selectQuery);
+    watchQuery(selectQuery);
+
+    QString setQueryString;
+    if (!selectQuery.first()) {
+        setQueryString = queryString("insert_core_state");
+    }
+    else {
+        setQueryString = queryString("update_core_state");
+    }
+
+    QSqlQuery setQuery(db);
+    setQuery.prepare(setQueryString);
+    setQuery.bindValue(":key", "active_sessions");
+    setQuery.bindValue(":value", rawData);
+    safeExec(setQuery);
+    watchQuery(setQuery);
+}
+
+
+QVariantList PostgreSqlStorage::getCoreState(const QVariantList &defaultData)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("select_core_state"));
+    query.bindValue(":key", "active_sessions");
+    safeExec(query);
+    watchQuery(query);
+
+    if (query.first()) {
+        QVariantList data;
+        QByteArray rawData = query.value(0).toByteArray();
+        QDataStream in(&rawData, QIODevice::ReadOnly);
+        in.setVersion(QDataStream::Qt_4_2);
+        in >> data;
+        return data;
+    } else {
+        return defaultData;
+    }
+}
+
+
 IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity)
 {
     IdentityId identityId;
 
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::createIdentity(): Unable to start Transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return identityId;
@@ -363,8 +492,7 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity
     query.bindValue(":sslkey", QByteArray());
 #endif
     safeExec(query);
-    if (query.lastError().isValid()) {
-        watchQuery(query);
+    if (!watchQuery(query)) {
         db.rollback();
         return IdentityId();
     }
@@ -374,7 +502,6 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity
     identity.setId(identityId);
 
     if (!identityId.isValid()) {
-        watchQuery(query);
         db.rollback();
         return IdentityId();
     }
@@ -403,7 +530,7 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity
 bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::updateIdentity(): Unable to start Transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
@@ -414,6 +541,7 @@ bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity
     checkQuery.bindValue(":identityid", identity.id().toInt());
     checkQuery.bindValue(":userid", user.toInt());
     safeExec(checkQuery);
+    watchQuery(checkQuery);
 
     // there should be exactly one identity for the given id and user
     if (!checkQuery.first() || checkQuery.value(0).toInt() != 1) {
@@ -488,7 +616,7 @@ bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity
 void PostgreSqlStorage::removeIdentity(UserId user, IdentityId identityId)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::removeIdentity(): Unable to start Transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return;
@@ -527,6 +655,7 @@ QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
     nickQuery.prepare(queryString("select_nicks"));
 
     safeExec(query);
+    watchQuery(query);
 
     while (query.next()) {
         CoreIdentity identity(IdentityId(query.value(0).toInt()));
@@ -573,7 +702,7 @@ NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info)
     NetworkId networkId;
 
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::createNetwork(): failed to begin transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
@@ -584,8 +713,7 @@ NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info)
     query.bindValue(":userid", user.toInt());
     bindNetworkInfo(query, info);
     safeExec(query);
-    if (query.lastError().isValid()) {
-        watchQuery(query);
+    if (!watchQuery(query)) {
         db.rollback();
         return NetworkId();
     }
@@ -594,7 +722,6 @@ NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info)
     networkId = query.value(0).toInt();
 
     if (!networkId.isValid()) {
-        watchQuery(query);
         db.rollback();
         return NetworkId();
     }
@@ -641,6 +768,11 @@ void PostgreSqlStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &inf
     query.bindValue(":autoreconnectretries", info.autoReconnectRetries);
     query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries);
     query.bindValue(":rejoinchannels", info.rejoinChannels);
+    // Custom rate limiting
+    query.bindValue(":usecustomessagerate", info.useCustomMessageRate);
+    query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
+    query.bindValue(":messageratedelay", info.messageRateDelay);
+    query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate);
     if (info.networkId.isValid())
         query.bindValue(":networkid", info.networkId.toInt());
 }
@@ -659,13 +791,14 @@ void PostgreSqlStorage::bindServerInfo(QSqlQuery &query, const Network::Server &
     query.bindValue(":proxyport", server.proxyPort);
     query.bindValue(":proxyuser", server.proxyUser);
     query.bindValue(":proxypass", server.proxyPass);
+    query.bindValue(":sslverify", server.sslVerify);
 }
 
 
 bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::updateNetwork(): failed to begin transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
@@ -720,7 +853,7 @@ bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info)
 bool PostgreSqlStorage::removeNetwork(UserId user, const NetworkId &networkId)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::removeNetwork(): cannot start transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
@@ -770,9 +903,9 @@ QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
         net.networkId = networksQuery.value(0).toInt();
         net.networkName = networksQuery.value(1).toString();
         net.identity = networksQuery.value(2).toInt();
-        net.codecForServer = networksQuery.value(3).toString().toAscii();
-        net.codecForEncoding = networksQuery.value(4).toString().toAscii();
-        net.codecForDecoding = networksQuery.value(5).toString().toAscii();
+        net.codecForServer = networksQuery.value(3).toString().toLatin1();
+        net.codecForEncoding = networksQuery.value(4).toString().toLatin1();
+        net.codecForDecoding = networksQuery.value(5).toString().toLatin1();
         net.useRandomServer = networksQuery.value(6).toBool();
         net.perform = networksQuery.value(7).toString().split("\n");
         net.useAutoIdentify = networksQuery.value(8).toBool();
@@ -786,6 +919,11 @@ QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
         net.useSasl = networksQuery.value(16).toBool();
         net.saslAccount = networksQuery.value(17).toString();
         net.saslPassword = networksQuery.value(18).toString();
+        // Custom rate limiting
+        net.useCustomMessageRate = networksQuery.value(19).toBool();
+        net.messageRateBurstSize = networksQuery.value(20).toUInt();
+        net.messageRateDelay = networksQuery.value(21).toUInt();
+        net.unlimitedMessageRate = networksQuery.value(22).toBool();
 
         serversQuery.bindValue(":networkid", net.networkId.toInt());
         safeExec(serversQuery);
@@ -808,6 +946,7 @@ QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
             server.proxyPort = serversQuery.value(8).toUInt();
             server.proxyUser = serversQuery.value(9).toString();
             server.proxyPass = serversQuery.value(10).toString();
+            server.sslVerify = serversQuery.value(11).toBool();
             servers << server;
         }
         net.serverList = servers;
@@ -888,7 +1027,7 @@ void PostgreSqlStorage::setChannelPersistent(UserId user, const NetworkId &netwo
     QSqlQuery query(logDb());
     query.prepare(queryString("update_buffer_persistent_channel"));
     query.bindValue(":userid", user.toInt());
-    query.bindValue(":networkId", networkId.toInt());
+    query.bindValue(":networkid", networkId.toInt());
     query.bindValue(":buffercname", channel.toLower());
     query.bindValue(":joined", isJoined);
     safeExec(query);
@@ -901,7 +1040,7 @@ void PostgreSqlStorage::setPersistentChannelKey(UserId user, const NetworkId &ne
     QSqlQuery query(logDb());
     query.prepare(queryString("update_buffer_set_channel_key"));
     query.bindValue(":userid", user.toInt());
-    query.bindValue(":networkId", networkId.toInt());
+    query.bindValue(":networkid", networkId.toInt());
     query.bindValue(":buffercname", channel.toLower());
     query.bindValue(":key", key);
     safeExec(query);
@@ -966,7 +1105,7 @@ void PostgreSqlStorage::setUserModes(UserId user, NetworkId networkId, const QSt
 BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::bufferInfo(): cannot start read only transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return BufferInfo();
@@ -978,6 +1117,7 @@ BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId
     query.bindValue(":userid", user.toInt());
     query.bindValue(":buffercname", buffer.toLower());
     safeExec(query);
+    watchQuery(query);
 
     if (query.first()) {
         BufferInfo bufferInfo = BufferInfo(query.value(0).toInt(), networkId, (BufferInfo::Type)query.value(1).toInt(), 0, buffer);
@@ -987,7 +1127,7 @@ BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId
             qCritical() << "  bound Values:";
             QList<QVariant> list = query.boundValues().values();
             for (int i = 0; i < list.size(); ++i)
-                qCritical() << i << ":" << list.at(i).toString().toAscii().data();
+                qCritical() << i << ":" << list.at(i).toString().toLatin1().data();
             Q_ASSERT(false);
         }
         db.commit();
@@ -1010,9 +1150,8 @@ BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId
 
     safeExec(createQuery);
 
-    if (createQuery.lastError().isValid()) {
+    if (!watchQuery(createQuery)) {
         qWarning() << "PostgreSqlStorage::bufferInfo(): unable to create buffer";
-        watchQuery(createQuery);
         db.rollback();
         return BufferInfo();
     }
@@ -1099,7 +1238,7 @@ QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, Netwo
 bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferId)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::removeBuffer(): cannot start transaction!";
         return false;
     }
@@ -1134,7 +1273,7 @@ bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferI
 bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferId, const QString &newName)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::renameBuffer(): cannot start transaction!";
         return false;
     }
@@ -1146,8 +1285,7 @@ bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferI
     query.bindValue(":userid", user.toInt());
     query.bindValue(":bufferid", bufferId.toInt());
     safeExec(query);
-    if (query.lastError().isValid()) {
-        watchQuery(query);
+    if (!watchQuery(query)) {
         db.rollback();
         return false;
     }
@@ -1172,7 +1310,7 @@ bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferI
 bool PostgreSqlStorage::mergeBuffersPermanently(const UserId &user, const BufferId &bufferId1, const BufferId &bufferId2)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::mergeBuffersPermanently(): cannot start transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
@@ -1227,7 +1365,7 @@ void PostgreSqlStorage::setBufferLastSeenMsg(UserId user, const BufferId &buffer
 
     query.bindValue(":userid", user.toInt());
     query.bindValue(":bufferid", bufferId.toInt());
-    query.bindValue(":lastseenmsgid", msgId.toInt());
+    query.bindValue(":lastseenmsgid", msgId.toQint64());
     safeExec(query);
     watchQuery(query);
 }
@@ -1254,7 +1392,7 @@ QHash<BufferId, MsgId> PostgreSqlStorage::bufferLastSeenMsgIds(UserId user)
     }
 
     while (query.next()) {
-        lastSeenHash[query.value(0).toInt()] = query.value(1).toInt();
+        lastSeenHash[query.value(0).toInt()] = query.value(1).toLongLong();
     }
 
     db.commit();
@@ -1269,7 +1407,7 @@ void PostgreSqlStorage::setBufferMarkerLineMsg(UserId user, const BufferId &buff
 
     query.bindValue(":userid", user.toInt());
     query.bindValue(":bufferid", bufferId.toInt());
-    query.bindValue(":markerlinemsgid", msgId.toInt());
+    query.bindValue(":markerlinemsgid", msgId.toQint64());
     safeExec(query);
     watchQuery(query);
 }
@@ -1296,7 +1434,7 @@ QHash<BufferId, MsgId> PostgreSqlStorage::bufferMarkerLineMsgIds(UserId user)
     }
 
     while (query.next()) {
-        markerLineHash[query.value(0).toInt()] = query.value(1).toInt();
+        markerLineHash[query.value(0).toInt()] = query.value(1).toLongLong();
     }
 
     db.commit();
@@ -1304,45 +1442,200 @@ QHash<BufferId, MsgId> PostgreSqlStorage::bufferMarkerLineMsgIds(UserId user)
 }
 
 
+void PostgreSqlStorage::setBufferActivity(UserId user, BufferId bufferId, Message::Types bufferActivity)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("update_buffer_bufferactivity"));
+
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":bufferid", bufferId.toInt());
+    query.bindValue(":bufferactivity", (int) bufferActivity);
+    safeExec(query);
+    watchQuery(query);
+}
+
+QHash<BufferId, Message::Types> PostgreSqlStorage::bufferActivities(UserId user)
+{
+    QHash<BufferId, Message::Types> bufferActivityHash;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::bufferActivities(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return bufferActivityHash;
+    }
+
+    QSqlQuery query(db);
+    query.prepare(queryString("select_buffer_bufferactivities"));
+    query.bindValue(":userid", user.toInt());
+    safeExec(query);
+    if (!watchQuery(query)) {
+        db.rollback();
+        return bufferActivityHash;
+    }
+
+    while (query.next()) {
+        bufferActivityHash[query.value(0).toInt()] = Message::Types(query.value(1).toInt());
+    }
+
+    db.commit();
+    return bufferActivityHash;
+}
+
+Message::Types PostgreSqlStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMsgId)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("select_buffer_bufferactivity"));
+    query.bindValue(":bufferid", bufferId.toInt());
+    query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
+    safeExec(query);
+    watchQuery(query);
+    Message::Types result = Message::Types(nullptr);
+    if (query.first())
+        result = Message::Types(query.value(0).toInt());
+    return result;
+}
+
+QHash<QString, QByteArray> PostgreSqlStorage::bufferCiphers(UserId user, const NetworkId &networkId)
+{
+    QHash<QString, QByteArray> bufferCiphers;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::persistentChannels(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return bufferCiphers;
+    }
+
+    QSqlQuery query(db);
+    query.prepare(queryString("select_buffer_ciphers"));
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":networkid", networkId.toInt());
+    safeExec(query);
+    watchQuery(query);
+
+    while (query.next()) {
+        bufferCiphers[query.value(0).toString()] = QByteArray::fromHex(query.value(1).toString().toUtf8());
+    }
+
+    db.commit();
+    return bufferCiphers;
+}
+
+void PostgreSqlStorage::setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("update_buffer_cipher"));
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":networkid", networkId.toInt());
+    query.bindValue(":buffercname", bufferName.toLower());
+    query.bindValue(":cipher", QString(cipher.toHex()));
+    safeExec(query);
+    watchQuery(query);
+}
+
+
+void PostgreSqlStorage::setHighlightCount(UserId user, BufferId bufferId, int highlightcount)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("update_buffer_highlightcount"));
+
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":bufferid", bufferId.toInt());
+    query.bindValue(":highlightcount", highlightcount);
+    safeExec(query);
+    watchQuery(query);
+}
+
+QHash<BufferId, int> PostgreSqlStorage::highlightCounts(UserId user)
+{
+    QHash<BufferId, int> highlightCountHash;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::highlightCounts(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return highlightCountHash;
+    }
+
+    QSqlQuery query(db);
+    query.prepare(queryString("select_buffer_highlightcounts"));
+    query.bindValue(":userid", user.toInt());
+    safeExec(query);
+    if (!watchQuery(query)) {
+        db.rollback();
+        return highlightCountHash;
+    }
+
+    while (query.next()) {
+        highlightCountHash[query.value(0).toInt()] = query.value(1).toInt();
+    }
+
+    db.commit();
+    return highlightCountHash;
+}
+
+int PostgreSqlStorage::highlightCount(BufferId bufferId, MsgId lastSeenMsgId)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("select_buffer_highlightcount"));
+    query.bindValue(":bufferid", bufferId.toInt());
+    query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
+    safeExec(query);
+    watchQuery(query);
+    int result = int(0);
+    if (query.first())
+        result = query.value(0).toInt();
+    return result;
+}
+
 bool PostgreSqlStorage::logMessage(Message &msg)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
     }
 
-    QSqlQuery getSenderIdQuery = executePreparedQuery("select_senderid", msg.sender(), db);
-    int senderId;
+    QVariantList senderParams;
+    senderParams << msg.sender()
+                 << msg.realName()
+                 << msg.avatarUrl();
+    QSqlQuery getSenderIdQuery = executePreparedQuery("select_senderid", senderParams, db);
+    qint64 senderId;
     if (getSenderIdQuery.first()) {
-        senderId = getSenderIdQuery.value(0).toInt();
+        senderId = getSenderIdQuery.value(0).toLongLong();
     }
     else {
         // it's possible that the sender was already added by another thread
         // since the insert might fail we're setting a savepoint
         savePoint("sender_sp1", db);
-        QSqlQuery addSenderQuery = executePreparedQuery("insert_sender", msg.sender(), db);
+        QSqlQuery addSenderQuery = executePreparedQuery("insert_sender", senderParams, db);
 
         if (addSenderQuery.lastError().isValid()) {
             rollbackSavePoint("sender_sp1", db);
-            getSenderIdQuery = db.exec(getSenderIdQuery.lastQuery());
+            getSenderIdQuery = executePreparedQuery("select_senderid", senderParams, db);
+            watchQuery(getSenderIdQuery);
             getSenderIdQuery.first();
-            senderId = getSenderIdQuery.value(0).toInt();
+            senderId = getSenderIdQuery.value(0).toLongLong();
         }
         else {
             releaseSavePoint("sender_sp1", db);
             addSenderQuery.first();
-            senderId = addSenderQuery.value(0).toInt();
+            senderId = addSenderQuery.value(0).toLongLong();
         }
     }
 
     QVariantList params;
+    // PostgreSQL handles QDateTime()'s serialized format by default, and QDateTime() serializes
+    // to a 64-bit time compatible format by default.
     params << msg.timestamp()
            << msg.bufferInfo().bufferId().toInt()
            << msg.type()
            << (int)msg.flags()
            << senderId
+           << msg.senderPrefixes()
            << msg.contents();
     QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
 
@@ -1352,7 +1645,7 @@ bool PostgreSqlStorage::logMessage(Message &msg)
     }
 
     logMessageQuery.first();
-    MsgId msgId = logMessageQuery.value(0).toInt();
+    MsgId msgId = logMessageQuery.value(0).toLongLong();
     db.commit();
     if (msgId.isValid()) {
         msg.setMsgId(msgId);
@@ -1367,44 +1660,51 @@ bool PostgreSqlStorage::logMessage(Message &msg)
 bool PostgreSqlStorage::logMessages(MessageList &msgs)
 {
     QSqlDatabase db = logDb();
-    if (!db.transaction()) {
+    if (!beginTransaction(db)) {
         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
         qWarning() << " -" << qPrintable(db.lastError().text());
         return false;
     }
 
     QList<int> senderIdList;
-    QHash<QString, int> senderIds;
+    QHash<SenderData, qint64> senderIds;
     QSqlQuery addSenderQuery;
     QSqlQuery selectSenderQuery;;
     for (int i = 0; i < msgs.count(); i++) {
-        const QString &sender = msgs.at(i).sender();
+        auto &msg = msgs.at(i);
+        SenderData sender = { msg.sender(), msg.realName(), msg.avatarUrl() };
         if (senderIds.contains(sender)) {
             senderIdList << senderIds[sender];
             continue;
         }
 
-        selectSenderQuery = executePreparedQuery("select_senderid", sender, db);
+        QVariantList senderParams;
+        senderParams << sender.sender
+                     << sender.realname
+                     << sender.avatarurl;
+
+        selectSenderQuery = executePreparedQuery("select_senderid", senderParams, db);
         if (selectSenderQuery.first()) {
-            senderIdList << selectSenderQuery.value(0).toInt();
-            senderIds[sender] = selectSenderQuery.value(0).toInt();
+            senderIdList << selectSenderQuery.value(0).toLongLong();
+            senderIds[sender] = selectSenderQuery.value(0).toLongLong();
         }
         else {
             savePoint("sender_sp", db);
-            addSenderQuery = executePreparedQuery("insert_sender", sender, db);
+            addSenderQuery = executePreparedQuery("insert_sender", senderParams, db);
             if (addSenderQuery.lastError().isValid()) {
                 // seems it was inserted meanwhile... by a different thread
                 rollbackSavePoint("sender_sp", db);
-                selectSenderQuery = db.exec(selectSenderQuery.lastQuery());
+                selectSenderQuery = executePreparedQuery("select_senderid", senderParams, db);
+                watchQuery(selectSenderQuery);
                 selectSenderQuery.first();
-                senderIdList << selectSenderQuery.value(0).toInt();
-                senderIds[sender] = selectSenderQuery.value(0).toInt();
+                senderIdList << selectSenderQuery.value(0).toLongLong();
+                senderIds[sender] = selectSenderQuery.value(0).toLongLong();
             }
             else {
                 releaseSavePoint("sender_sp", db);
                 addSenderQuery.first();
-                senderIdList << addSenderQuery.value(0).toInt();
-                senderIds[sender] = addSenderQuery.value(0).toInt();
+                senderIdList << addSenderQuery.value(0).toLongLong();
+                senderIds[sender] = addSenderQuery.value(0).toLongLong();
             }
         }
     }
@@ -1414,11 +1714,14 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
     for (int i = 0; i < msgs.count(); i++) {
         Message &msg = msgs[i];
         QVariantList params;
+        // PostgreSQL handles QDateTime()'s serialized format by default, and QDateTime() serializes
+        // to a 64-bit time compatible format by default.
         params << msg.timestamp()
                << msg.bufferInfo().bufferId().toInt()
                << msg.type()
                << (int)msg.flags()
                << senderIdList.at(i)
+               << msg.senderPrefixes()
                << msg.contents();
         QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
         if (!watchQuery(logMessageQuery)) {
@@ -1428,7 +1731,7 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
         }
         else {
             logMessageQuery.first();
-            msg.setMsgId(logMessageQuery.value(0).toInt());
+            msg.setMsgId(logMessageQuery.value(0).toLongLong());
         }
     }
 
@@ -1465,22 +1768,22 @@ QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms
     QString queryName;
     QVariantList params;
     if (last == -1 && first == -1) {
-        queryName = "select_messages";
+        queryName = "select_messagesNewestK";
     }
     else if (last == -1) {
         queryName = "select_messagesNewerThan";
-        params << first.toInt();
+        params << first.toQint64();
     }
     else {
         queryName = "select_messagesRange";
-        params << first.toInt();
-        params << last.toInt();
+        params << first.toQint64();
+        params << last.toQint64();
     }
     params << bufferId.toInt();
     if (limit != -1)
         params << limit;
     else
-        params << "ALL";
+        params << QVariant(QVariant::Int);
 
     QSqlQuery query = executePreparedQuery(queryName, params, db);
 
@@ -1492,15 +1795,86 @@ QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms
 
     QDateTime timestamp;
     while (query.next()) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
         timestamp = query.value(1).toDateTime();
         timestamp.setTimeSpec(Qt::UTC);
         Message msg(timestamp,
             bufferInfo,
-            (Message::Type)query.value(2).toUInt(),
-            query.value(5).toString(),
+            (Message::Type)query.value(2).toInt(),
+            query.value(8).toString(),
             query.value(4).toString(),
-            (Message::Flags)query.value(3).toUInt());
-        msg.setMsgId(query.value(0).toInt());
+            query.value(5).toString(),
+            query.value(6).toString(),
+            query.value(7).toString(),
+            (Message::Flags)query.value(3).toInt());
+        msg.setMsgId(query.value(0).toLongLong());
+        messagelist << msg;
+    }
+
+    db.commit();
+    return messagelist;
+}
+
+
+QList<Message> PostgreSqlStorage::requestMsgsFiltered(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+    QList<Message> messagelist;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::requestMsgs(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return messagelist;
+    }
+
+    BufferInfo bufferInfo = getBufferInfo(user, bufferId);
+    if (!bufferInfo.isValid()) {
+        db.rollback();
+        return messagelist;
+    }
+
+    QSqlQuery query(db);
+    if (last == -1 && first == -1) {
+        query.prepare(queryString("select_messagesNewestK_filtered"));
+    } else if (last == -1) {
+        query.prepare(queryString("select_messagesNewerThan_filtered"));
+        query.bindValue(":first", first.toQint64());
+    } else {
+        query.prepare(queryString("select_messagesRange_filtered"));
+        query.bindValue(":last", last.toQint64());
+        query.bindValue(":first", first.toQint64());
+    }
+    query.bindValue(":buffer", bufferId.toInt());
+    query.bindValue(":limit", limit);
+    int typeRaw = type;
+    query.bindValue(":type", typeRaw);
+    int flagsRaw = flags;
+    query.bindValue(":flags", flagsRaw);
+
+    safeExec(query);
+    if (!watchQuery(query)) {
+        qDebug() << "select_messages failed";
+        db.rollback();
+        return messagelist;
+    }
+
+    QDateTime timestamp;
+    while (query.next()) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
+        timestamp = query.value(1).toDateTime();
+        timestamp.setTimeSpec(Qt::UTC);
+        Message msg(timestamp,
+                    bufferInfo,
+                    (Message::Type)query.value(2).toInt(),
+                    query.value(8).toString(),
+                    query.value(4).toString(),
+                    query.value(5).toString(),
+                    query.value(6).toString(),
+                    query.value(7).toString(),
+                    Message::Flags{query.value(3).toInt()});
+        msg.setMsgId(query.value(0).toLongLong());
         messagelist << msg;
     }
 
@@ -1532,10 +1906,10 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
     }
     else {
         query.prepare(queryString("select_messagesAll"));
-        query.bindValue(":lastmsg", last.toInt());
+        query.bindValue(":lastmsg", last.toQint64());
     }
     query.bindValue(":userid", user.toInt());
-    query.bindValue(":firstmsg", first.toInt());
+    query.bindValue(":firstmsg", first.toQint64());
     safeExec(query);
     if (!watchQuery(query)) {
         db.rollback();
@@ -1544,15 +1918,20 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
 
     QDateTime timestamp;
     for (int i = 0; i < limit && query.next(); i++) {
-        timestamp = query.value(1).toDateTime();
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
+        timestamp = query.value(2).toDateTime();
         timestamp.setTimeSpec(Qt::UTC);
         Message msg(timestamp,
             bufferInfoHash[query.value(1).toInt()],
-            (Message::Type)query.value(3).toUInt(),
-            query.value(6).toString(),
+            (Message::Type)query.value(3).toInt(),
+            query.value(9).toString(),
             query.value(5).toString(),
-            (Message::Flags)query.value(4).toUInt());
-        msg.setMsgId(query.value(0).toInt());
+            query.value(6).toString(),
+            query.value(7).toString(),
+            query.value(8).toString(),
+            (Message::Flags)query.value(4).toInt());
+        msg.setMsgId(query.value(0).toLongLong());
         messagelist << msg;
     }
 
@@ -1561,13 +1940,91 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
 }
 
 
+QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+    QList<Message> messagelist;
+
+    // requestBuffers uses it's own transaction.
+    QHash<BufferId, BufferInfo> bufferInfoHash;
+            foreach(BufferInfo bufferInfo, requestBuffers(user)) {
+            bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
+        }
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return messagelist;
+    }
+
+    QSqlQuery query(db);
+    if (last == -1) {
+        query.prepare(queryString("select_messagesAllNew_filtered"));
+    }
+    else {
+        query.prepare(queryString("select_messagesAll_filtered"));
+        query.bindValue(":lastmsg", last.toQint64());
+    }
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":firstmsg", first.toQint64());
+
+    int typeRaw = type;
+    query.bindValue(":type", typeRaw);
+
+    int flagsRaw = flags;
+    query.bindValue(":flags", flagsRaw);
+
+    safeExec(query);
+    if (!watchQuery(query)) {
+        db.rollback();
+        return messagelist;
+    }
+
+    QDateTime timestamp;
+    for (int i = 0; i < limit && query.next(); i++) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
+        timestamp = query.value(2).toDateTime();
+        timestamp.setTimeSpec(Qt::UTC);
+        Message msg(timestamp,
+                    bufferInfoHash[query.value(1).toInt()],
+                    (Message::Type)query.value(3).toInt(),
+                    query.value(9).toString(),
+                    query.value(5).toString(),
+                    query.value(6).toString(),
+                    query.value(7).toString(),
+                    query.value(8).toString(),
+                    Message::Flags{query.value(4).toInt()});
+        msg.setMsgId(query.value(0).toLongLong());
+        messagelist << msg;
+    }
+
+    db.commit();
+    return messagelist;
+}
+
+QMap<UserId, QString> PostgreSqlStorage::getAllAuthUserNames()
+{
+    QMap<UserId, QString> authusernames;
+    QSqlQuery query(logDb());
+    query.prepare(queryString("select_all_authusernames"));
+    safeExec(query);
+    watchQuery(query);
+
+    while (query.next()) {
+        authusernames[query.value(0).toInt()] = query.value(1).toString();
+    }
+    return authusernames;
+}
+
+
 // void PostgreSqlStorage::safeExec(QSqlQuery &query) {
 //   qDebug() << "PostgreSqlStorage::safeExec";
 //   qDebug() << "   executing:\n" << query.executedQuery();
 //   qDebug() << "   bound Values:";
 //   QList<QVariant> list = query.boundValues().values();
 //   for (int i = 0; i < list.size(); ++i)
-//     qCritical() << i << ": " << list.at(i).toString().toAscii().data();
+//     qCritical() << i << ": " << list.at(i).toString().toLatin1().data();
 
 //   query.exec();
 
@@ -1584,14 +2041,29 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
 //   return;
 // }
 
+
+bool PostgreSqlStorage::beginTransaction(QSqlDatabase &db)
+{
+    bool result = db.transaction();
+    if (!db.isOpen()) {
+        db = logDb();
+        result = db.transaction();
+    }
+    return result;
+}
+
 bool PostgreSqlStorage::beginReadOnlyTransaction(QSqlDatabase &db)
 {
     QSqlQuery query = db.exec("BEGIN TRANSACTION READ ONLY");
+    if (!db.isOpen()) {
+        db = logDb();
+        query = db.exec("BEGIN TRANSACTION READ ONLY");
+    }
     return !query.lastError().isValid();
 }
 
 
-QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString &paramstring, const QSqlDatabase &db)
+QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString &paramstring, QSqlDatabase &db)
 {
     // Query preparing is done lazily. That means that instead of always checking if the query is already prepared
     // we just EXECUTE and catch the error
@@ -1605,10 +2077,22 @@ QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, co
         query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
     }
 
-    if (db.lastError().isValid()) {
-        // and once again: Qt leaves us without error codes so we either parse (language dependant(!)) strings
+    if (!db.isOpen() || db.lastError().isValid()) {
+        // If the query failed because the DB connection was down, reopen the connection and start a new transaction.
+        if (!db.isOpen()) {
+            db = logDb();
+            if (!beginTransaction(db)) {
+                qWarning() << "PostgreSqlStorage::prepareAndExecuteQuery(): cannot start transaction while recovering from connection loss!";
+                qWarning() << " -" << qPrintable(db.lastError().text());
+                return query;
+            }
+            db.exec("SAVEPOINT quassel_prepare_query");
+        } else {
+            db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
+        }
+
+        // and once again: Qt leaves us without error codes so we either parse (language dependent(!)) strings
         // or we just guess the error. As we're only interested in unprepared queries, this will be our guess. :)
-        db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
         QSqlQuery checkQuery = db.exec(QString("SELECT count(name) FROM pg_prepared_statements WHERE name = 'quassel_%1' AND from_sql = TRUE").arg(queryname.toLower()));
         checkQuery.first();
         if (checkQuery.value(0).toInt() == 0) {
@@ -1619,7 +2103,7 @@ QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, co
                 return QSqlQuery(db);
             }
         }
-        // we alwas execute the query again, even if the query was already prepared.
+        // we always execute the query again, even if the query was already prepared.
         // this ensures, that the error is properly propagated to the calling function
         // (otherwise the last call would be the testing select to pg_prepared_statements
         // which always gives a proper result and the error would be lost)
@@ -1638,7 +2122,7 @@ QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, co
 }
 
 
-QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList &params, const QSqlDatabase &db)
+QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList &params, QSqlDatabase &db)
 {
     QSqlDriver *driver = db.driver();
 
@@ -1664,7 +2148,7 @@ QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, cons
 }
 
 
-QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant &param, const QSqlDatabase &db)
+QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant &param, QSqlDatabase &db)
 {
     QSqlField field;
     field.setType(param.type());
@@ -1684,6 +2168,26 @@ void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlData
 }
 
 
+void PostgreSqlStorage::safeExec(QSqlQuery &query)
+{
+    // If the query fails due to the connection being gone, it seems to cause
+    // exec() to return false but no lastError to be set
+    if(!query.exec() && !query.lastError().isValid())
+    {
+        QSqlDatabase db = logDb();
+        QSqlQuery retryQuery(db);
+        retryQuery.prepare(query.lastQuery());
+        QMapIterator<QString, QVariant> i(query.boundValues());
+        while (i.hasNext())
+        {
+            i.next();
+            retryQuery.bindValue(i.key(),i.value());
+        }
+        query = retryQuery;
+        query.exec();
+    }
+}
+
 // ========================================
 //  PostgreSqlMigrationWriter
 // ========================================
@@ -1725,6 +2229,9 @@ bool PostgreSqlMigrationWriter::prepareQuery(MigrationObject mo)
     case UserSetting:
         query = queryString("migrate_write_usersetting");
         break;
+    case CoreState:
+        query = queryString("migrate_write_corestate");
+        break;
     }
     newQuery(query, logDb());
     return true;
@@ -1737,6 +2244,8 @@ bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user)
     bindValue(0, user.id.toInt());
     bindValue(1, user.username);
     bindValue(2, user.password);
+    bindValue(3, user.hashversion);
+    bindValue(4, user.authenticator);
     return exec();
 }
 
@@ -1746,6 +2255,8 @@ bool PostgreSqlMigrationWriter::writeMo(const SenderMO &sender)
 {
     bindValue(0, sender.senderId);
     bindValue(1, sender.sender);
+    bindValue(2, sender.realname);
+    bindValue(3, sender.avatarurl);
     return exec();
 }
 
@@ -1768,7 +2279,7 @@ bool PostgreSqlMigrationWriter::writeMo(const IdentityMO &identity)
     bindValue(11, identity.autoAwayReasonEnabled);
     bindValue(12, identity.detachAwayEnabled);
     bindValue(13, identity.detachAwayReason);
-    bindValue(14, identity.detchAwayReasonEnabled);
+    bindValue(14, identity.detachAwayReasonEnabled);
     bindValue(15, identity.ident);
     bindValue(16, identity.kickReason);
     bindValue(17, identity.partReason);
@@ -1820,6 +2331,11 @@ bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network)
     bindValue(22, network.usesasl);
     bindValue(23, network.saslaccount);
     bindValue(24, network.saslpassword);
+    // Custom rate limiting
+    bindValue(25, network.usecustommessagerate);
+    bindValue(26, network.messagerateburstsize);
+    bindValue(27, network.messageratedelay);
+    bindValue(28, network.unlimitedmessagerate);
     return exec();
 }
 
@@ -1834,10 +2350,14 @@ bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
     bindValue(4, buffer.buffername);
     bindValue(5, buffer.buffercname);
     bindValue(6, (int)buffer.buffertype);
-    bindValue(7, buffer.lastseenmsgid);
-    bindValue(8, buffer.markerlinemsgid);
-    bindValue(9, buffer.key);
-    bindValue(10, buffer.joined);
+    bindValue(7, buffer.lastmsgid);
+    bindValue(8, buffer.lastseenmsgid);
+    bindValue(9, buffer.markerlinemsgid);
+    bindValue(10, buffer.bufferactivity);
+    bindValue(11, buffer.highlightcount);
+    bindValue(12, buffer.key);
+    bindValue(13, buffer.joined);
+    bindValue(14, buffer.cipher);
     return exec();
 }
 
@@ -1845,13 +2365,14 @@ bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
 //bool PostgreSqlMigrationWriter::writeBacklog(const BacklogMO &backlog) {
 bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog)
 {
-    bindValue(0, backlog.messageid.toInt());
+    bindValue(0, backlog.messageid.toQint64());
     bindValue(1, backlog.time);
     bindValue(2, backlog.bufferid.toInt());
     bindValue(3, backlog.type);
     bindValue(4, (int)backlog.flags);
     bindValue(5, backlog.senderid);
-    bindValue(6, backlog.message);
+    bindValue(6, backlog.senderprefixes);
+    bindValue(7, backlog.message);
     return exec();
 }
 
@@ -1873,6 +2394,7 @@ bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)
     bindValue(11, ircserver.proxyport);
     bindValue(12, ircserver.proxyuser);
     bindValue(13, ircserver.proxypass);
+    bindValue(14, ircserver.sslverify);
     return exec();
 }
 
@@ -1886,6 +2408,13 @@ bool PostgreSqlMigrationWriter::writeMo(const UserSettingMO &userSetting)
     return exec();
 }
 
+bool PostgreSqlMigrationWriter::writeMo(const CoreStateMO &coreState)
+{
+    bindValue(0, coreState.key);
+    bindValue(1, coreState.value);
+    return exec();
+}
+
 
 bool PostgreSqlMigrationWriter::postProcess()
 {
@@ -1900,11 +2429,17 @@ bool PostgreSqlMigrationWriter::postProcess()
               << Sequence("quasseluser", "userid")
               << Sequence("sender", "senderid");
     QList<Sequence>::const_iterator iter;
-    for (iter = sequences.constBegin(); iter != sequences.constEnd(); iter++) {
+    for (iter = sequences.constBegin(); iter != sequences.constEnd(); ++iter) {
         resetQuery();
         newQuery(QString("SELECT setval('%1_%2_seq', max(%2)) FROM %1").arg(iter->table, iter->field), db);
         if (!exec())
             return false;
     }
+
+    // Update the lastmsgid for all existing buffers.
+    resetQuery();
+    newQuery(QString("SELECT populate_lastmsgid()"), db);
+    if (!exec())
+        return false;
     return true;
 }