qa: Resolve deprecation warnings in newer Qt versions
[quassel.git] / src / core / postgresqlstorage.cpp
index 200fd14..030423f 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2018 by the Quassel Project                        *
+ *   Copyright (C) 2005-2020 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 
 #include "postgresqlstorage.h"
 
-#include <QtSql>
+#include <QByteArray>
+#include <QDataStream>
+#include <QSqlDriver>
+#include <QSqlField>
 
-#include "logmessage.h"
 #include "network.h"
 #include "quassel.h"
 
@@ -46,8 +48,8 @@ std::unique_ptr<AbstractSqlMigrationWriter> PostgreSqlStorage::createMigrationWr
 bool PostgreSqlStorage::isAvailable() const
 {
     if (!QSqlDatabase::isDriverAvailable("QPSQL")) {
-        quWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:"))
-                    << qPrintable(QSqlDatabase::drivers().join(", "));
+        qWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:"))
+                   << qPrintable(QSqlDatabase::drivers().join(", "));
         return false;
     }
     return true;
@@ -92,7 +94,7 @@ bool PostgreSqlStorage::initDbSession(QSqlDatabase& db)
         // 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)";
+        qWarning() << "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");
@@ -106,14 +108,15 @@ bool PostgreSqlStorage::initDbSession(QSqlDatabase& db)
             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!";
+                qCritical() << "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!";
+        qCritical()
+            << "Your version of Qt does something _VERY_ strange to slashes in QSqlQueries! You should consult your trusted doctor!";
         return false;
         break;
     }
@@ -121,7 +124,7 @@ bool PostgreSqlStorage::initDbSession(QSqlDatabase& db)
     // 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!";
+        qCritical() << "Failed to set timezone to UTC!";
         return false;
     }
 
@@ -165,19 +168,39 @@ int PostgreSqlStorage::installedSchemaVersion()
     return AbstractSqlStorage::installedSchemaVersion();
 }
 
-bool PostgreSqlStorage::updateSchemaVersion(int newVersion)
+bool PostgreSqlStorage::updateSchemaVersion(int newVersion, bool clearUpgradeStep)
 {
-    QSqlQuery query(logDb());
+    // Atomically update the schema version and clear the upgrade step, if specified
+    // Note: This will need reworked if "updateSchemaVersion" is ever called within a transaction.
+    QSqlDatabase db = logDb();
+    if (!beginTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::updateSchemaVersion(int, bool): cannot start transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return false;
+    }
+
+    QSqlQuery query(db);
     query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'");
     query.bindValue(":version", newVersion);
     safeExec(query);
 
-    bool success = true;
     if (!watchQuery(query)) {
-        qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!";
-        success = false;
+        qCritical() << "PostgreSqlStorage::updateSchemaVersion(int, bool): Updating schema version failed!";
+        db.rollback();
+        return false;
     }
-    return success;
+
+    if (clearUpgradeStep) {
+        // Try clearing the upgrade step if requested
+        if (!setSchemaVersionUpgradeStep("")) {
+            db.rollback();
+            return false;
+        }
+    }
+
+    // Successful, commit and return true
+    db.commit();
+    return true;
 }
 
 bool PostgreSqlStorage::setupSchemaVersion(int version)
@@ -195,6 +218,50 @@ bool PostgreSqlStorage::setupSchemaVersion(int version)
     return success;
 }
 
+QString PostgreSqlStorage::schemaVersionUpgradeStep()
+{
+    QSqlQuery query(logDb());
+    query.prepare("SELECT value FROM coreinfo WHERE key = 'schemaupgradestep'");
+    safeExec(query);
+    watchQuery(query);
+    if (query.first())
+        return query.value(0).toString();
+
+    // Fall back to the default value
+    return AbstractSqlStorage::schemaVersionUpgradeStep();
+}
+
+bool PostgreSqlStorage::setSchemaVersionUpgradeStep(QString upgradeQuery)
+{
+    // Intentionally do not wrap in a transaction so other functions can include multiple operations
+
+    QSqlQuery query(logDb());
+    query.prepare("UPDATE coreinfo SET value = :upgradestep WHERE key = 'schemaupgradestep'");
+    query.bindValue(":upgradestep", upgradeQuery);
+    safeExec(query);
+
+    // Make sure that the query didn't fail (shouldn't ever happen), and that some non-zero number
+    // of rows were affected
+    bool success = watchQuery(query) && query.numRowsAffected() != 0;
+
+    if (!success) {
+        // The key might not exist (Quassel 0.13.0 and older).  Try inserting it...
+        query = QSqlQuery(logDb());
+        query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaupgradestep', :upgradestep)");
+        query.bindValue(":upgradestep", upgradeQuery);
+        safeExec(query);
+
+        if (!watchQuery(query)) {
+            qCritical() << Q_FUNC_INFO << "Setting schema upgrade step failed!";
+            success = false;
+        }
+        else {
+            success = true;
+        }
+    }
+    return success;
+}
+
 UserId PostgreSqlStorage::addUser(const QString& user, const QString& password, const QString& authenticator)
 {
     QSqlQuery query(logDb());
@@ -461,13 +528,8 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity& identity
     query.bindValue(":kickreason", identity.kickReason());
     query.bindValue(":partreason", identity.partReason());
     query.bindValue(":quitreason", identity.quitReason());
-#ifdef HAVE_SSL
     query.bindValue(":sslcert", identity.sslCert().toPem());
     query.bindValue(":sslkey", identity.sslKey().toPem());
-#else
-    query.bindValue(":sslcert", QByteArray());
-    query.bindValue(":sslkey", QByteArray());
-#endif
     safeExec(query);
     if (!watchQuery(query)) {
         db.rollback();
@@ -544,13 +606,8 @@ bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity& identity
     query.bindValue(":kickreason", identity.kickReason());
     query.bindValue(":partreason", identity.partReason());
     query.bindValue(":quitreason", identity.quitReason());
-#ifdef HAVE_SSL
     query.bindValue(":sslcert", identity.sslCert().toPem());
     query.bindValue(":sslkey", identity.sslKey().toPem());
-#else
-    query.bindValue(":sslcert", QByteArray());
-    query.bindValue(":sslkey", QByteArray());
-#endif
     query.bindValue(":identityid", identity.id().toInt());
 
     safeExec(query);
@@ -610,9 +667,9 @@ void PostgreSqlStorage::removeIdentity(UserId user, IdentityId identityId)
     }
 }
 
-QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
+std::vector<CoreIdentity> PostgreSqlStorage::identities(UserId user)
 {
-    QList<CoreIdentity> identities;
+    std::vector<CoreIdentity> identities;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -651,10 +708,8 @@ QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
         identity.setKickReason(query.value(15).toString());
         identity.setPartReason(query.value(16).toString());
         identity.setQuitReason(query.value(17).toString());
-#ifdef HAVE_SSL
         identity.setSslCert(query.value(18).toByteArray());
         identity.setSslKey(query.value(19).toByteArray());
-#endif
 
         nickQuery.bindValue(":identityid", identity.id().toInt());
         QList<QString> nicks;
@@ -664,7 +719,7 @@ QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
             nicks << nickQuery.value(0).toString();
         }
         identity.setNicks(nicks);
-        identities << identity;
+        identities.push_back(std::move(identity));
     }
     db.commit();
     return identities;
@@ -745,6 +800,8 @@ void PostgreSqlStorage::bindNetworkInfo(QSqlQuery& query, const NetworkInfo& inf
     query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
     query.bindValue(":messageratedelay", info.messageRateDelay);
     query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate);
+    query.bindValue(":skipcaps", info.skipCapsToString());
+
     if (info.networkId.isValid())
         query.bindValue(":networkid", info.networkId.toInt());
 }
@@ -842,9 +899,9 @@ bool PostgreSqlStorage::removeNetwork(UserId user, const NetworkId& networkId)
     return true;
 }
 
-QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
+std::vector<NetworkInfo> PostgreSqlStorage::networks(UserId user)
 {
-    QList<NetworkInfo> nets;
+    std::vector<NetworkInfo> nets;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -892,6 +949,7 @@ QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
         net.messageRateBurstSize = networksQuery.value(20).toUInt();
         net.messageRateDelay = networksQuery.value(21).toUInt();
         net.unlimitedMessageRate = networksQuery.value(22).toBool();
+        net.skipCapsFromString(networksQuery.value(23).toString());
 
         serversQuery.bindValue(":networkid", net.networkId.toInt());
         safeExec(serversQuery);
@@ -918,15 +976,15 @@ QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
             servers << server;
         }
         net.serverList = servers;
-        nets << net;
+        nets.push_back(std::move(net));
     }
     db.commit();
     return nets;
 }
 
-QList<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
+std::vector<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
 {
-    QList<NetworkId> connectedNets;
+    std::vector<NetworkId> connectedNets;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -942,7 +1000,7 @@ QList<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
     watchQuery(query);
 
     while (query.next()) {
-        connectedNets << query.value(0).toInt();
+        connectedNets.emplace_back(query.value(0).toInt());
     }
 
     db.commit();
@@ -1144,9 +1202,9 @@ BufferInfo PostgreSqlStorage::getBufferInfo(UserId user, const BufferId& bufferI
     return bufferInfo;
 }
 
-QList<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
+std::vector<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
 {
-    QList<BufferInfo> bufferlist;
+    std::vector<BufferInfo> bufferlist;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -1162,19 +1220,19 @@ QList<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
     safeExec(query);
     watchQuery(query);
     while (query.next()) {
-        bufferlist << BufferInfo(query.value(0).toInt(),
-                                 query.value(1).toInt(),
-                                 (BufferInfo::Type)query.value(2).toInt(),
-                                 query.value(3).toInt(),
-                                 query.value(4).toString());
+        bufferlist.emplace_back(query.value(0).toInt(),
+                                query.value(1).toInt(),
+                                (BufferInfo::Type)query.value(2).toInt(),
+                                query.value(3).toInt(),
+                                query.value(4).toString());
     }
     db.commit();
     return bufferlist;
 }
 
-QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
+std::vector<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
 {
-    QList<BufferId> bufferList;
+    std::vector<BufferId> bufferList;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -1191,7 +1249,7 @@ QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, Netwo
     safeExec(query);
     watchQuery(query);
     while (query.next()) {
-        bufferList << BufferId(query.value(0).toInt());
+        bufferList.emplace_back(query.value(0).toInt());
     }
     db.commit();
     return bufferList;
@@ -1319,6 +1377,34 @@ bool PostgreSqlStorage::mergeBuffersPermanently(const UserId& user, const Buffer
     return true;
 }
 
+QHash<BufferId, MsgId> PostgreSqlStorage::bufferLastMsgIds(UserId user)
+{
+    QHash<BufferId, MsgId> lastMsgHash;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::bufferLastMsgIds(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return lastMsgHash;
+    }
+
+    QSqlQuery query(db);
+    query.prepare(queryString("select_buffer_last_messages"));
+    query.bindValue(":userid", user.toInt());
+    safeExec(query);
+    if (!watchQuery(query)) {
+        db.rollback();
+        return lastMsgHash;
+    }
+
+    while (query.next()) {
+        lastMsgHash[query.value(0).toInt()] = query.value(1).toLongLong();
+    }
+
+    db.commit();
+    return lastMsgHash;
+}
+
 void PostgreSqlStorage::setBufferLastSeenMsg(UserId user, const BufferId& bufferId, const MsgId& msgId)
 {
     QSqlQuery query(logDb());
@@ -1447,7 +1533,7 @@ Message::Types PostgreSqlStorage::bufferActivity(BufferId bufferId, MsgId lastSe
     query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
     safeExec(query);
     watchQuery(query);
-    Message::Types result = Message::Types(nullptr);
+    Message::Types result{};
     if (query.first())
         result = Message::Types(query.value(0).toInt());
     return result;
@@ -1689,9 +1775,9 @@ bool PostgreSqlStorage::logMessages(MessageList& msgs)
     return true;
 }
 
-QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
+std::vector<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
 {
-    QList<Message> messagelist;
+    std::vector<Message> messagelist;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -1750,17 +1836,17 @@ QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms
                     query.value(7).toString(),
                     (Message::Flags)query.value(3).toInt());
         msg.setMsgId(query.value(0).toLongLong());
-        messagelist << msg;
+        messagelist.push_back(std::move(msg));
     }
 
     db.commit();
     return messagelist;
 }
 
-QList<Message> PostgreSqlStorage::requestMsgsFiltered(
+std::vector<Message> PostgreSqlStorage::requestMsgsFiltered(
     UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
 {
-    QList<Message> messagelist;
+    std::vector<Message> messagelist;
 
     QSqlDatabase db = logDb();
     if (!beginReadOnlyTransaction(db)) {
@@ -1818,16 +1904,92 @@ QList<Message> PostgreSqlStorage::requestMsgsFiltered(
                     query.value(7).toString(),
                     Message::Flags{query.value(3).toInt()});
         msg.setMsgId(query.value(0).toLongLong());
-        messagelist << msg;
+        messagelist.push_back(std::move(msg));
+    }
+
+    db.commit();
+    return messagelist;
+}
+
+std::vector<Message> PostgreSqlStorage::requestMsgsForward(
+    UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+    std::vector<Message> messagelist;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::requestMsgsForward(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return messagelist;
+    }
+
+    BufferInfo bufferInfo = getBufferInfo(user, bufferId);
+    if (!bufferInfo.isValid()) {
+        db.rollback();
+        return messagelist;
+    }
+
+    QString queryName;
+    QVariantList params;
+
+    if (first == -1) {
+        params << std::numeric_limits<qint64>::min();
+    } else {
+        params << first.toQint64();
+    }
+
+    if (last == -1) {
+        params << std::numeric_limits<qint64>::max();
+    } else {
+        params << last.toQint64();
+    }
+
+    params << bufferId.toInt();
+
+    int typeRaw = type;
+    int flagsRaw = flags;
+    params << typeRaw;
+    params << flagsRaw;
+
+    if (limit != -1)
+        params << limit;
+    else
+        params << QVariant(QVariant::Int);
+
+    QSqlQuery query = executePreparedQuery("select_messagesForward", params, db);
+
+    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.push_back(std::move(msg));
     }
 
     db.commit();
     return messagelist;
 }
 
-QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
+std::vector<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
 {
-    QList<Message> messagelist;
+    std::vector<Message> messagelist;
 
     // requestBuffers uses it's own transaction.
     QHash<BufferId, BufferInfo> bufferInfoHash;
@@ -1874,17 +2036,17 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
                     query.value(8).toString(),
                     (Message::Flags)query.value(4).toInt());
         msg.setMsgId(query.value(0).toLongLong());
-        messagelist << msg;
+        messagelist.push_back(std::move(msg));
     }
 
     db.commit();
     return messagelist;
 }
 
-QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(
+std::vector<Message> PostgreSqlStorage::requestAllMsgsFiltered(
     UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
 {
-    QList<Message> messagelist;
+    std::vector<Message> messagelist;
 
     // requestBuffers uses it's own transaction.
     QHash<BufferId, BufferInfo> bufferInfoHash;
@@ -1938,7 +2100,7 @@ QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(
                     query.value(8).toString(),
                     Message::Flags{query.value(4).toInt()});
         msg.setMsgId(query.value(0).toLongLong());
-        messagelist << msg;
+        messagelist.push_back(std::move(msg));
     }
 
     db.commit();
@@ -2265,6 +2427,8 @@ bool PostgreSqlMigrationWriter::writeMo(const NetworkMO& network)
     bindValue(26, network.messagerateburstsize);
     bindValue(27, network.messageratedelay);
     bindValue(28, network.unlimitedmessagerate);
+    // Skipped IRCv3 caps
+    bindValue(29, network.skipcaps);
     return exec();
 }