core: Workaround Qt 4 SQL bindValue() duplicates
[quassel.git] / src / core / postgresqlstorage.cpp
index a6f2a72..9ed72e3 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2018 by the Quassel Project                        *
+ *   Copyright (C) 2005-2019 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -22,7 +22,7 @@
 
 #include <QtSql>
 
-#include "logger.h"
+#include "logmessage.h"
 #include "network.h"
 #include "quassel.h"
 
@@ -47,7 +47,7 @@ std::unique_ptr<AbstractSqlMigrationWriter> PostgreSqlStorage::createMigrationWr
     properties["Hostname"] = _hostName;
     properties["Port"] = _port;
     properties["Database"] = _databaseName;
-    writer->setConnectionProperties(properties);
+    writer->setConnectionProperties(properties, {}, false);
     return std::unique_ptr<AbstractSqlMigrationWriter>{writer};
 }
 
@@ -147,13 +147,23 @@ bool PostgreSqlStorage::initDbSession(QSqlDatabase &db)
 }
 
 
-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();
+    }
 }
 
 
@@ -177,19 +187,39 @@ int PostgreSqlStorage::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;
 }
 
 
@@ -209,6 +239,52 @@ bool PostgreSqlStorage::setupSchemaVersion(int version)
 }
 
 
+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());
@@ -1361,7 +1437,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);
 }
@@ -1388,7 +1464,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();
@@ -1403,7 +1479,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);
 }
@@ -1430,7 +1506,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();
@@ -1483,7 +1559,7 @@ Message::Types PostgreSqlStorage::bufferActivity(BufferId bufferId, MsgId lastSe
     QSqlQuery query(logDb());
     query.prepare(queryString("select_buffer_bufferactivity"));
     query.bindValue(":bufferid", bufferId.toInt());
-    query.bindValue(":lastseenmsgid", lastSeenMsgId.toInt());
+    query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
     safeExec(query);
     watchQuery(query);
     Message::Types result = Message::Types(0);
@@ -1576,7 +1652,7 @@ 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.toInt());
+    query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
     safeExec(query);
     watchQuery(query);
     int result = int(0);
@@ -1599,9 +1675,9 @@ bool PostgreSqlStorage::logMessage(Message &msg)
                  << msg.realName()
                  << msg.avatarUrl();
     QSqlQuery getSenderIdQuery = executePreparedQuery("select_senderid", senderParams, db);
-    int senderId;
+    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
@@ -1614,16 +1690,18 @@ bool PostgreSqlStorage::logMessage(Message &msg)
             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()
@@ -1639,7 +1717,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);
@@ -1661,7 +1739,7 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
     }
 
     QList<int> senderIdList;
-    QHash<SenderData, int> senderIds;
+    QHash<SenderData, qint64> senderIds;
     QSqlQuery addSenderQuery;
     QSqlQuery selectSenderQuery;;
     for (int i = 0; i < msgs.count(); i++) {
@@ -1679,8 +1757,8 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
 
         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);
@@ -1691,14 +1769,14 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
                 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();
             }
         }
     }
@@ -1708,6 +1786,8 @@ 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()
@@ -1723,7 +1803,7 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
         }
         else {
             logMessageQuery.first();
-            msg.setMsgId(logMessageQuery.value(0).toInt());
+            msg.setMsgId(logMessageQuery.value(0).toLongLong());
         }
     }
 
@@ -1764,12 +1844,12 @@ QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms
     }
     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)
@@ -1787,18 +1867,20 @@ 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(),
+            (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).toUInt());
-        msg.setMsgId(query.value(0).toInt());
+            (Message::Flags)query.value(3).toInt());
+        msg.setMsgId(query.value(0).toLongLong());
         messagelist << msg;
     }
 
@@ -1827,13 +1909,18 @@ QList<Message> PostgreSqlStorage::requestMsgsFiltered(UserId user, BufferId buff
     QSqlQuery query(db);
     if (last == -1 && first == -1) {
         query.prepare(queryString("select_messagesNewestK_filtered"));
+        // Workaround for Qt 4 QSqlQuery::bindValue() not supporting repeated placeholder names
+        query.bindValue(":bufferDup1", bufferId.toInt());
     } else if (last == -1) {
         query.prepare(queryString("select_messagesNewerThan_filtered"));
-        query.bindValue(":first", first.toInt());
+        query.bindValue(":first", first.toQint64());
+        // Workaround for Qt 4 QSqlQuery::bindValue() not supporting repeated placeholder names
+        query.bindValue(":bufferDup1", bufferId.toInt());
     } else {
         query.prepare(queryString("select_messagesRange_filtered"));
-        query.bindValue(":last", last.toInt());
-        query.bindValue(":first", first.toInt());
+        query.bindValue(":last", last.toQint64());
+        query.bindValue(":first", first.toQint64());
+        // Workaround for Qt 4 QSqlQuery::bindValue() not needed, only has one ":buffer"
     }
     query.bindValue(":buffer", bufferId.toInt());
     query.bindValue(":limit", limit);
@@ -1841,6 +1928,8 @@ QList<Message> PostgreSqlStorage::requestMsgsFiltered(UserId user, BufferId buff
     query.bindValue(":type", typeRaw);
     int flagsRaw = flags;
     query.bindValue(":flags", flagsRaw);
+    // Workaround for Qt 4 QSqlQuery::bindValue() not supporting repeated placeholder names
+    query.bindValue(":flagsDup1", flagsRaw);
 
     safeExec(query);
     if (!watchQuery(query)) {
@@ -1851,18 +1940,20 @@ QList<Message> PostgreSqlStorage::requestMsgsFiltered(UserId user, BufferId buff
 
     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(),
+                    (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).toInt());
+        msg.setMsgId(query.value(0).toLongLong());
         messagelist << msg;
     }
 
@@ -1894,10 +1985,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();
@@ -1906,18 +1997,20 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
 
     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).toUInt(),
+            (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).toUInt());
-        msg.setMsgId(query.value(0).toInt());
+            (Message::Flags)query.value(4).toInt());
+        msg.setMsgId(query.value(0).toLongLong());
         messagelist << msg;
     }
 
@@ -1949,16 +2042,18 @@ QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(UserId user, MsgId firs
     }
     else {
         query.prepare(queryString("select_messagesAll_filtered"));
-        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());
 
     int typeRaw = type;
     query.bindValue(":type", typeRaw);
 
     int flagsRaw = flags;
     query.bindValue(":flags", flagsRaw);
+    // Workaround for Qt 4 QSqlQuery::bindValue() not supporting repeated placeholder names
+    query.bindValue(":flagsDup1", flagsRaw);
 
     safeExec(query);
     if (!watchQuery(query)) {
@@ -1968,18 +2063,20 @@ QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(UserId user, MsgId firs
 
     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).toUInt(),
+                    (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).toInt());
+        msg.setMsgId(query.value(0).toLongLong());
         messagelist << msg;
     }
 
@@ -2002,21 +2099,6 @@ QMap<UserId, QString> PostgreSqlStorage::getAllAuthUserNames()
 }
 
 
-QString PostgreSqlStorage::getAuthUserName(UserId user)
-{
-    QString authusername;
-    QSqlQuery query(logDb());
-    query.prepare(queryString("select_authusername"));
-    query.bindValue(":userid", user.toInt());
-    safeExec(query);
-    watchQuery(query);
-
-    if (query.first()) {
-         authusername = query.value(0).toString();
-    }
-    return authusername;
-}
-
 // void PostgreSqlStorage::safeExec(QSqlQuery &query) {
 //   qDebug() << "PostgreSqlStorage::safeExec";
 //   qDebug() << "   executing:\n" << query.executedQuery();
@@ -2278,7 +2360,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);
@@ -2364,7 +2446,7 @@ 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);