core: Migrate SQLite schema to MSec, warn of slow
[quassel.git] / src / core / sqlitestorage.cpp
index c8f57d1..db174cb 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2016 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  *
@@ -46,12 +46,19 @@ bool SqliteStorage::isAvailable() const
 }
 
 
+QString SqliteStorage::backendId() const
+{
+    return QString("SQLite");
+}
+
+
 QString SqliteStorage::displayName() const
 {
+    // Note: Pre-0.13 clients use the displayName property for backend idenfication
     // We identify the backend to use for the monolithic core by its displayname.
     // so only change this string if you _really_ have to and make sure the core
     // setup for the mono client still works ;)
-    return QString("SQLite");
+    return backendId();
 }
 
 
@@ -116,7 +123,7 @@ bool SqliteStorage::setupSchemaVersion(int version)
 }
 
 
-UserId SqliteStorage::addUser(const QString &user, const QString &password)
+UserId SqliteStorage::addUser(const QString &user, const QString &password, const QString &authenticator)
 {
     QSqlDatabase db = logDb();
     UserId uid;
@@ -131,6 +138,7 @@ UserId SqliteStorage::addUser(const QString &user, const QString &password)
         query.bindValue(":username", user);
         query.bindValue(":password", hashPassword(password));
         query.bindValue(":hashversion", Storage::HashVersion::Latest);
+        query.bindValue(":authenticator", authenticator);
         lockForWrite();
         safeExec(query);
         if (query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error...
@@ -240,6 +248,26 @@ UserId SqliteStorage::getUserId(const QString &username)
     return userId;
 }
 
+QString SqliteStorage::getUserAuthenticator(const UserId userid)
+{
+    QString authenticator = QString("");
+
+    {
+        QSqlQuery query(logDb());
+        query.prepare(queryString("select_authenticator"));
+        query.bindValue(":userid", userid.toInt());
+
+        lockForRead();
+        safeExec(query);
+
+        if (query.first()) {
+            authenticator = query.value(0).toString();
+        }
+    }
+    unlock();
+
+    return authenticator;
+}
 
 UserId SqliteStorage::internalUser()
 {
@@ -348,6 +376,60 @@ QVariant SqliteStorage::getUserSetting(UserId userId, const QString &settingName
 }
 
 
+void SqliteStorage::setCoreState(const QVariantList &data)
+{
+    QByteArray rawData;
+    QDataStream out(&rawData, QIODevice::WriteOnly);
+    out.setVersion(QDataStream::Qt_4_2);
+    out << data;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("insert_core_state"));
+        query.bindValue(":key", "active_sessions");
+        query.bindValue(":value", rawData);
+        lockForWrite();
+        safeExec(query);
+
+        if (query.lastError().isValid()) {
+            QSqlQuery updateQuery(db);
+            updateQuery.prepare(queryString("update_core_state"));
+            updateQuery.bindValue(":key", "active_sessions");
+            updateQuery.bindValue(":value", rawData);
+            safeExec(updateQuery);
+        }
+        db.commit();
+    }
+    unlock();
+}
+
+
+QVariantList SqliteStorage::getCoreState(const QVariantList &defaultData)
+{
+    QVariantList data;
+    {
+        QSqlQuery query(logDb());
+        query.prepare(queryString("select_core_state"));
+        query.bindValue(":key", "active_sessions");
+        lockForRead();
+        safeExec(query);
+
+        if (query.first()) {
+            QByteArray rawData = query.value(0).toByteArray();
+            QDataStream in(&rawData, QIODevice::ReadOnly);
+            in.setVersion(QDataStream::Qt_4_2);
+            in >> data;
+        } else {
+            data = defaultData;
+        }
+    }
+    unlock();
+    return data;
+}
+
+
 IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity &identity)
 {
     IdentityId identityId;
@@ -658,6 +740,11 @@ void SqliteStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &info)
     query.bindValue(":autoreconnectretries", info.autoReconnectRetries);
     query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries ? 1 : 0);
     query.bindValue(":rejoinchannels", info.rejoinChannels ? 1 : 0);
+    // Custom rate limiting
+    query.bindValue(":usecustomessagerate", info.useCustomMessageRate ? 1 : 0);
+    query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
+    query.bindValue(":messageratedelay", info.messageRateDelay);
+    query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate ? 1 : 0);
     if (info.networkId.isValid())
         query.bindValue(":networkid", info.networkId.toInt());
 }
@@ -854,6 +941,11 @@ QList<NetworkInfo> SqliteStorage::networks(UserId user)
                 net.useSasl = networksQuery.value(16).toInt() == 1 ? true : false;
                 net.saslAccount = networksQuery.value(17).toString();
                 net.saslPassword = networksQuery.value(18).toString();
+                // Custom rate limiting
+                net.useCustomMessageRate = networksQuery.value(19).toInt() == 1 ? true : false;
+                net.messageRateBurstSize = networksQuery.value(20).toUInt();
+                net.messageRateDelay = networksQuery.value(21).toUInt();
+                net.unlimitedMessageRate = networksQuery.value(22).toInt() == 1 ? true : false;
 
                 serversQuery.bindValue(":networkid", net.networkId.toInt());
                 safeExec(serversQuery);
@@ -1376,7 +1468,7 @@ void SqliteStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId,
         query.prepare(queryString("update_buffer_lastseen"));
         query.bindValue(":userid", user.toInt());
         query.bindValue(":bufferid", bufferId.toInt());
-        query.bindValue(":lastseenmsgid", msgId.toInt());
+        query.bindValue(":lastseenmsgid", msgId.toQint64());
 
         lockForWrite();
         safeExec(query);
@@ -1405,7 +1497,7 @@ QHash<BufferId, MsgId> SqliteStorage::bufferLastSeenMsgIds(UserId user)
         error = !watchQuery(query);
         if (!error) {
             while (query.next()) {
-                lastSeenHash[query.value(0).toInt()] = query.value(1).toInt();
+                lastSeenHash[query.value(0).toInt()] = query.value(1).toLongLong();
             }
         }
     }
@@ -1426,7 +1518,7 @@ void SqliteStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId
         query.prepare(queryString("update_buffer_markerlinemsgid"));
         query.bindValue(":userid", user.toInt());
         query.bindValue(":bufferid", bufferId.toInt());
-        query.bindValue(":markerlinemsgid", msgId.toInt());
+        query.bindValue(":markerlinemsgid", msgId.toQint64());
 
         lockForWrite();
         safeExec(query);
@@ -1455,7 +1547,7 @@ QHash<BufferId, MsgId> SqliteStorage::bufferMarkerLineMsgIds(UserId user)
         error = !watchQuery(query);
         if (!error) {
             while (query.next()) {
-                markerLineHash[query.value(0).toInt()] = query.value(1).toInt();
+                markerLineHash[query.value(0).toInt()] = query.value(1).toLongLong();
             }
         }
     }
@@ -1465,6 +1557,195 @@ QHash<BufferId, MsgId> SqliteStorage::bufferMarkerLineMsgIds(UserId user)
     return markerLineHash;
 }
 
+void SqliteStorage::setBufferActivity(UserId user, BufferId bufferId, Message::Types bufferActivity)
+{
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("update_buffer_bufferactivity"));
+        query.bindValue(":userid", user.toInt());
+        query.bindValue(":bufferid", bufferId.toInt());
+        query.bindValue(":bufferactivity", (int) bufferActivity);
+
+        lockForWrite();
+        safeExec(query);
+        watchQuery(query);
+    }
+    db.commit();
+    unlock();
+}
+
+
+QHash<BufferId, Message::Types> SqliteStorage::bufferActivities(UserId user)
+{
+    QHash<BufferId, Message::Types> bufferActivityHash;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    bool error = false;
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_buffer_bufferactivities"));
+        query.bindValue(":userid", user.toInt());
+
+        lockForRead();
+        safeExec(query);
+        error = !watchQuery(query);
+        if (!error) {
+            while (query.next()) {
+                bufferActivityHash[query.value(0).toInt()] = Message::Types(query.value(1).toInt());
+            }
+        }
+    }
+
+    db.commit();
+    unlock();
+    return bufferActivityHash;
+}
+
+
+Message::Types SqliteStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMsgId)
+{
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    Message::Types result = Message::Types(0);
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_buffer_bufferactivity"));
+        query.bindValue(":bufferid", bufferId.toInt());
+        query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
+
+        lockForRead();
+        safeExec(query);
+        if (query.first())
+            result = Message::Types(query.value(0).toInt());
+    }
+
+    db.commit();
+    unlock();
+    return result;
+}
+
+QHash<QString, QByteArray> SqliteStorage::bufferCiphers(UserId user, const NetworkId &networkId)
+{
+    QHash<QString, QByteArray> bufferCiphers;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_buffer_ciphers"));
+        query.bindValue(":userid", user.toInt());
+        query.bindValue(":networkid", networkId.toInt());
+
+        lockForRead();
+        safeExec(query);
+        watchQuery(query);
+        while (query.next()) {
+            bufferCiphers[query.value(0).toString()] = QByteArray::fromHex(query.value(1).toString().toUtf8());
+        }
+    }
+    unlock();
+    return bufferCiphers;
+}
+
+void SqliteStorage::setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher)
+{
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    {
+        QSqlQuery query(db);
+        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()));
+
+        lockForWrite();
+        safeExec(query);
+        watchQuery(query);
+        db.commit();
+    }
+    unlock();
+}
+
+void SqliteStorage::setHighlightCount(UserId user, BufferId bufferId, int count)
+{
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("update_buffer_highlightcount"));
+        query.bindValue(":userid", user.toInt());
+        query.bindValue(":bufferid", bufferId.toInt());
+        query.bindValue(":highlightcount", count);
+
+        lockForWrite();
+        safeExec(query);
+        watchQuery(query);
+    }
+    db.commit();
+    unlock();
+}
+
+
+QHash<BufferId, int> SqliteStorage::highlightCounts(UserId user)
+{
+    QHash<BufferId, int> highlightCountHash;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    bool error = false;
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_buffer_highlightcounts"));
+        query.bindValue(":userid", user.toInt());
+
+        lockForRead();
+        safeExec(query);
+        error = !watchQuery(query);
+        if (!error) {
+            while (query.next()) {
+                highlightCountHash[query.value(0).toInt()] = query.value(1).toInt();
+            }
+        }
+    }
+
+    db.commit();
+    unlock();
+    return highlightCountHash;
+}
+
+
+int SqliteStorage::highlightCount(BufferId bufferId, MsgId lastSeenMsgId)
+{
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    int result = 0;
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_buffer_highlightcount"));
+        query.bindValue(":bufferid", bufferId.toInt());
+        query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
+
+        lockForRead();
+        safeExec(query);
+        if (query.first())
+            result = query.value(0).toInt();
+    }
+
+    db.commit();
+    unlock();
+    return result;
+}
 
 bool SqliteStorage::logMessage(Message &msg)
 {
@@ -1475,12 +1756,16 @@ bool SqliteStorage::logMessage(Message &msg)
     {
         QSqlQuery logMessageQuery(db);
         logMessageQuery.prepare(queryString("insert_message"));
-
-        logMessageQuery.bindValue(":time", msg.timestamp().toTime_t());
+        // As of SQLite schema version 31, timestamps are stored in milliseconds instead of
+        // seconds.  This nets us more precision as well as simplifying 64-bit time.
+        logMessageQuery.bindValue(":time", msg.timestamp().toMSecsSinceEpoch());
         logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
         logMessageQuery.bindValue(":type", msg.type());
         logMessageQuery.bindValue(":flags", (int)msg.flags());
         logMessageQuery.bindValue(":sender", msg.sender());
+        logMessageQuery.bindValue(":realname", msg.realName());
+        logMessageQuery.bindValue(":avatarurl", msg.avatarUrl());
+        logMessageQuery.bindValue(":senderprefixes", msg.senderPrefixes());
         logMessageQuery.bindValue(":message", msg.contents());
 
         lockForWrite();
@@ -1492,6 +1777,8 @@ bool SqliteStorage::logMessage(Message &msg)
                 QSqlQuery addSenderQuery(db);
                 addSenderQuery.prepare(queryString("insert_sender"));
                 addSenderQuery.bindValue(":sender", msg.sender());
+                addSenderQuery.bindValue(":realname", msg.realName());
+                addSenderQuery.bindValue(":avatarurl", msg.avatarUrl());
                 safeExec(addSenderQuery);
                 safeExec(logMessageQuery);
                 error = !watchQuery(logMessageQuery);
@@ -1501,7 +1788,7 @@ bool SqliteStorage::logMessage(Message &msg)
             }
         }
         if (!error) {
-            MsgId msgId = logMessageQuery.lastInsertId().toInt();
+            MsgId msgId = logMessageQuery.lastInsertId().toLongLong();
             if (msgId.isValid()) {
                 msg.setMsgId(msgId);
             }
@@ -1529,17 +1816,20 @@ bool SqliteStorage::logMessages(MessageList &msgs)
     db.transaction();
 
     {
-        QSet<QString> senders;
+        QSet<SenderData> senders;
         QSqlQuery addSenderQuery(db);
         addSenderQuery.prepare(queryString("insert_sender"));
         lockForWrite();
         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 (senders.contains(sender))
                 continue;
             senders << sender;
 
-            addSenderQuery.bindValue(":sender", sender);
+            addSenderQuery.bindValue(":sender", sender.sender);
+            addSenderQuery.bindValue(":realname", sender.realname);
+            addSenderQuery.bindValue(":avatarurl", sender.avatarurl);
             safeExec(addSenderQuery);
         }
     }
@@ -1550,12 +1840,16 @@ bool SqliteStorage::logMessages(MessageList &msgs)
         logMessageQuery.prepare(queryString("insert_message"));
         for (int i = 0; i < msgs.count(); i++) {
             Message &msg = msgs[i];
-
-            logMessageQuery.bindValue(":time", msg.timestamp().toTime_t());
+            // As of SQLite schema version 31, timestamps are stored in milliseconds instead of
+            // seconds.  This nets us more precision as well as simplifying 64-bit time.
+            logMessageQuery.bindValue(":time", msg.timestamp().toMSecsSinceEpoch());
             logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
             logMessageQuery.bindValue(":type", msg.type());
             logMessageQuery.bindValue(":flags", (int)msg.flags());
             logMessageQuery.bindValue(":sender", msg.sender());
+            logMessageQuery.bindValue(":realname", msg.realName());
+            logMessageQuery.bindValue(":avatarurl", msg.avatarUrl());
+            logMessageQuery.bindValue(":senderprefixes", msg.senderPrefixes());
             logMessageQuery.bindValue(":message", msg.contents());
 
             safeExec(logMessageQuery);
@@ -1564,7 +1858,7 @@ bool SqliteStorage::logMessages(MessageList &msgs)
                 break;
             }
             else {
-                msg.setMsgId(logMessageQuery.lastInsertId().toInt());
+                msg.setMsgId(logMessageQuery.lastInsertId().toLongLong());
             }
         }
     }
@@ -1595,7 +1889,7 @@ QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId
     bool error = false;
     BufferInfo bufferInfo;
     {
-        // code dupication from getBufferInfo:
+        // code duplication from getBufferInfo:
         // this is due to the impossibility of nesting transactions and recursive locking
         QSqlQuery bufferInfoQuery(db);
         bufferInfoQuery.prepare(queryString("select_buffer_by_id"));
@@ -1623,12 +1917,12 @@ QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId
         }
         else if (last == -1) {
             query.prepare(queryString("select_messagesNewerThan"));
-            query.bindValue(":firstmsg", first.toInt());
+            query.bindValue(":firstmsg", first.toQint64());
         }
         else {
-            query.prepare(queryString("select_messages"));
-            query.bindValue(":lastmsg", last.toInt());
-            query.bindValue(":firstmsg", first.toInt());
+            query.prepare(queryString("select_messagesRange"));
+            query.bindValue(":lastmsg", last.toQint64());
+            query.bindValue(":firstmsg", first.toQint64());
         }
         query.bindValue(":bufferid", bufferId.toInt());
         query.bindValue(":limit", limit);
@@ -1637,13 +1931,99 @@ QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId
         watchQuery(query);
 
         while (query.next()) {
-            Message msg(QDateTime::fromTime_t(query.value(1).toInt()),
+            Message msg(
+                // As of SQLite schema version 31, timestamps are stored in milliseconds instead of
+                // seconds.  This nets us more precision as well as simplifying 64-bit time.
+                QDateTime::fromMSecsSinceEpoch(query.value(1).toLongLong()),
                 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();
+    unlock();
+
+    return messagelist;
+}
+
+
+QList<Message> SqliteStorage::requestMsgsFiltered(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+    QList<Message> messagelist;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    bool error = false;
+    BufferInfo bufferInfo;
+    {
+        // code dupication from getBufferInfo:
+        // this is due to the impossibility of nesting transactions and recursive locking
+        QSqlQuery bufferInfoQuery(db);
+        bufferInfoQuery.prepare(queryString("select_buffer_by_id"));
+        bufferInfoQuery.bindValue(":userid", user.toInt());
+        bufferInfoQuery.bindValue(":bufferid", bufferId.toInt());
+
+        lockForRead();
+        safeExec(bufferInfoQuery);
+        error = !watchQuery(bufferInfoQuery) || !bufferInfoQuery.first();
+        if (!error) {
+            bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), bufferInfoQuery.value(1).toInt(), (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), 0, bufferInfoQuery.value(4).toString());
+            error = !bufferInfo.isValid();
+        }
+    }
+    if (error) {
+        db.rollback();
+        unlock();
+        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(":firstmsg", first.toQint64());
+        }
+        else {
+            query.prepare(queryString("select_messagesRange_filtered"));
+            query.bindValue(":lastmsg", last.toQint64());
+            query.bindValue(":firstmsg", first.toQint64());
+        }
+        query.bindValue(":bufferid", bufferId.toInt());
+        query.bindValue(":limit", limit);
+        int typeRaw = type;
+        query.bindValue(":type", typeRaw);
+        int flagsRaw = flags;
+        query.bindValue(":flags", flagsRaw);
+
+        safeExec(query);
+        watchQuery(query);
+
+        while (query.next()) {
+            Message msg(
+                        // As of SQLite schema version 31, timestamps are stored in milliseconds
+                        // instead of seconds.  This nets us more precision as well as simplifying
+                        // 64-bit time.
+                        QDateTime::fromMSecsSinceEpoch(query.value(1).toLongLong()),
+                        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;
         }
     }
@@ -1681,23 +2061,92 @@ QList<Message> SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId las
         }
         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());
         query.bindValue(":limit", limit);
         safeExec(query);
 
         watchQuery(query);
 
         while (query.next()) {
-            Message msg(QDateTime::fromTime_t(query.value(2).toInt()),
+            Message msg(
+                // As of SQLite schema version 31, timestamps are stored in milliseconds instead of
+                // seconds.  This nets us more precision as well as simplifying 64-bit time.
+                QDateTime::fromMSecsSinceEpoch(query.value(2).toLongLong()),
                 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;
+        }
+    }
+    db.commit();
+    unlock();
+    return messagelist;
+}
+
+QList<Message> SqliteStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+    QList<Message> messagelist;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    QHash<BufferId, BufferInfo> bufferInfoHash;
+    {
+        QSqlQuery bufferInfoQuery(db);
+        bufferInfoQuery.prepare(queryString("select_buffers"));
+        bufferInfoQuery.bindValue(":userid", user.toInt());
+
+        lockForRead();
+        safeExec(bufferInfoQuery);
+        watchQuery(bufferInfoQuery);
+        while (bufferInfoQuery.next()) {
+            BufferInfo bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), bufferInfoQuery.value(1).toInt(), (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), bufferInfoQuery.value(3).toInt(), bufferInfoQuery.value(4).toString());
+            bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
+        }
+
+        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());
+        query.bindValue(":limit", limit);
+        int typeRaw = type;
+        query.bindValue(":type", typeRaw);
+        int flagsRaw = flags;
+        query.bindValue(":flags", flagsRaw);
+        safeExec(query);
+
+        watchQuery(query);
+
+        while (query.next()) {
+            Message msg(
+                        // As of SQLite schema version 31, timestamps are stored in milliseconds
+                        // instead of seconds.  This nets us more precision as well as simplifying
+                        // 64-bit time.
+                        QDateTime::fromMSecsSinceEpoch(query.value(2).toLongLong()),
+                        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;
         }
     }
@@ -1706,6 +2155,47 @@ QList<Message> SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId las
     return messagelist;
 }
 
+QMap<UserId, QString> SqliteStorage::getAllAuthUserNames()
+{
+    QMap<UserId, QString> authusernames;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_all_authusernames"));
+
+        lockForRead();
+        safeExec(query);
+        watchQuery(query);
+        while (query.next()) {
+            authusernames[query.value(0).toInt()] = query.value(1).toString();
+        }
+    }
+    db.commit();
+    unlock();
+    return authusernames;
+}
+
+
+QString SqliteStorage::getAuthUserName(UserId user) {
+    QString authusername;
+    QSqlQuery query(logDb());
+    query.prepare(queryString("select_authusername"));
+    query.bindValue(":userid", user.toInt());
+
+    lockForRead();
+    safeExec(query);
+    watchQuery(query);
+    unlock();
+
+    if (query.first()) {
+        authusername = query.value(0).toString();
+    }
+
+    return authusername;
+}
+
 
 QString SqliteStorage::backlogFile()
 {
@@ -1722,12 +2212,15 @@ bool SqliteStorage::safeExec(QSqlQuery &query, int retryCount)
 
     switch (query.lastError().number()) {
     case 5: // SQLITE_BUSY         5   /* The database file is locked */
+        [[clang::fallthrough]];
     case 6: // SQLITE_LOCKED       6   /* A table in the database is locked */
         if (retryCount < _maxRetryCount)
             return safeExec(query, retryCount + 1);
+        break;
     default:
-        return false;
+        ;
     }
+    return false;
 }
 
 
@@ -1757,7 +2250,7 @@ void SqliteMigrationReader::setMaxId(MigrationObject mo)
     }
     QSqlQuery query = logDb().exec(queryString);
     query.first();
-    _maxId = query.value(0).toInt();
+    _maxId = query.value(0).toLongLong();
 }
 
 
@@ -1797,6 +2290,9 @@ bool SqliteMigrationReader::prepareQuery(MigrationObject mo)
     case UserSetting:
         newQuery(queryString("migrate_read_usersetting"), logDb());
         break;
+    case CoreState:
+        newQuery(queryString("migrate_read_corestate"), logDb());
+        break;
     }
     return exec();
 }
@@ -1811,6 +2307,7 @@ bool SqliteMigrationReader::readMo(QuasselUserMO &user)
     user.username = value(1).toString();
     user.password = value(2).toString();
     user.hashversion = value(3).toInt();
+    user.authenticator = value(4).toString();
     return true;
 }
 
@@ -1834,7 +2331,7 @@ bool SqliteMigrationReader::readMo(IdentityMO &identity)
     identity.autoAwayReasonEnabled = value(11).toInt() == 1 ? true : false;
     identity.detachAwayEnabled = value(12).toInt() == 1 ? true : false;
     identity.detachAwayReason = value(13).toString();
-    identity.detchAwayReasonEnabled = value(14).toInt() == 1 ? true : false;
+    identity.detachAwayReasonEnabled = value(14).toInt() == 1 ? true : false;
     identity.ident = value(15).toString();
     identity.kickReason = value(16).toString();
     identity.partReason = value(17).toString();
@@ -1887,6 +2384,11 @@ bool SqliteMigrationReader::readMo(NetworkMO &network)
     network.usesasl = value(22).toInt() == 1 ? true : false;
     network.saslaccount = value(23).toString();
     network.saslpassword = value(24).toString();
+    // Custom rate limiting
+    network.usecustommessagerate = value(25).toInt() == 1 ? true : false;
+    network.messagerateburstsize = value(26).toInt();
+    network.messageratedelay = value(27).toUInt();
+    network.unlimitedmessagerate = value(28).toInt() == 1 ? true : false;
     return true;
 }
 
@@ -1903,10 +2405,14 @@ bool SqliteMigrationReader::readMo(BufferMO &buffer)
     buffer.buffername = value(4).toString();
     buffer.buffercname = value(5).toString();
     buffer.buffertype = value(6).toInt();
-    buffer.lastseenmsgid = value(7).toInt();
-    buffer.markerlinemsgid = value(8).toInt();
-    buffer.key = value(9).toString();
-    buffer.joined = value(10).toInt() == 1 ? true : false;
+    buffer.lastmsgid = value(7).toLongLong();
+    buffer.lastseenmsgid = value(8).toLongLong();
+    buffer.markerlinemsgid = value(9).toLongLong();
+    buffer.bufferactivity = value(10).toInt();
+    buffer.highlightcount = value(11).toInt();
+    buffer.key = value(12).toString();
+    buffer.joined = value(13).toInt() == 1 ? true : false;
+    buffer.cipher = value(14).toString();
     return true;
 }
 
@@ -1927,19 +2433,21 @@ bool SqliteMigrationReader::readMo(SenderMO &sender)
         }
     }
 
-    sender.senderId = value(0).toInt();
+    sender.senderId = value(0).toLongLong();
     sender.sender = value(1).toString();
+    sender.realname = value(2).toString();
+    sender.avatarurl = value(3).toString();
     return true;
 }
 
 
 bool SqliteMigrationReader::readMo(BacklogMO &backlog)
 {
-    int skipSteps = 0;
+    qint64 skipSteps = 0;
     while (!next()) {
         if (backlog.messageid < _maxId) {
-            bindValue(0, backlog.messageid.toInt() + (skipSteps * stepSize()));
-            bindValue(1, backlog.messageid.toInt() + ((skipSteps + 1) * stepSize()));
+            bindValue(0, backlog.messageid.toQint64() + (skipSteps * stepSize()));
+            bindValue(1, backlog.messageid.toQint64() + ((skipSteps + 1) * stepSize()));
             skipSteps++;
             if (!exec())
                 return false;
@@ -1949,13 +2457,16 @@ bool SqliteMigrationReader::readMo(BacklogMO &backlog)
         }
     }
 
-    backlog.messageid = value(0).toInt();
-    backlog.time = QDateTime::fromTime_t(value(1).toInt()).toUTC();
+    backlog.messageid = value(0).toLongLong();
+    // As of SQLite schema version 31, timestamps are stored in milliseconds instead of
+    // seconds.  This nets us more precision as well as simplifying 64-bit time.
+    backlog.time = QDateTime::fromMSecsSinceEpoch(value(1).toLongLong()).toUTC();
     backlog.bufferid = value(2).toInt();
     backlog.type = value(3).toInt();
     backlog.flags = value(4).toInt();
-    backlog.senderid = value(5).toInt();
-    backlog.message = value(6).toString();
+    backlog.senderid = value(5).toLongLong();
+    backlog.senderprefixes = value(6).toString();
+    backlog.message = value(7).toString();
     return true;
 }
 
@@ -1995,3 +2506,15 @@ bool SqliteMigrationReader::readMo(UserSettingMO &userSetting)
 
     return true;
 }
+
+
+bool SqliteMigrationReader::readMo(CoreStateMO &coreState)
+{
+    if (!next())
+        return false;
+
+    coreState.key = value(0).toString();
+    coreState.value = value(1).toByteArray();
+
+    return true;
+}