Replace deprecated QSqlError::number with ::nativeErrorCode
[quassel.git] / src / core / abstractsqlstorage.cpp
index ba5a8f5..cf9cff5 100644 (file)
@@ -180,13 +180,13 @@ QString AbstractSqlStorage::queryString(const QString& queryName, int version)
     return query.trimmed();
 }
 
-QStringList AbstractSqlStorage::setupQueries()
+QList<AbstractSqlStorage::SqlQueryResource> AbstractSqlStorage::setupQueries()
 {
-    QStringList queries;
+    QList<SqlQueryResource> queries;
     // The current schema is stored in the root folder, including setup scripts.
     QDir dir = QDir(QString(":/SQL/%1/").arg(displayName()));
     foreach (QFileInfo fileInfo, dir.entryInfoList(QStringList() << "setup*", QDir::NoFilter, QDir::Name)) {
-        queries << queryString(fileInfo.baseName());
+        queries << SqlQueryResource(queryString(fileInfo.baseName()), fileInfo.baseName());
     }
     return queries;
 }
@@ -201,10 +201,11 @@ bool AbstractSqlStorage::setup(const QVariantMap& settings, const QProcessEnviro
     }
 
     db.transaction();
-    foreach (QString queryString, setupQueries()) {
-        QSqlQuery query = db.exec(queryString);
+    foreach (auto queryResource, setupQueries()) {
+        QSqlQuery query = db.exec(queryResource.queryString);
         if (!watchQuery(query)) {
-            qCritical() << "Unable to setup Logging Backend!";
+            qCritical() << qPrintable(QString("Unable to setup Logging Backend!  Setup query failed (step: %1).")
+                                      .arg(queryResource.queryFilename));
             db.rollback();
             return false;
         }
@@ -217,13 +218,13 @@ bool AbstractSqlStorage::setup(const QVariantMap& settings, const QProcessEnviro
     return success;
 }
 
-QStringList AbstractSqlStorage::upgradeQueries(int version)
+QList<AbstractSqlStorage::SqlQueryResource> AbstractSqlStorage::upgradeQueries(int version)
 {
-    QStringList queries;
+    QList<SqlQueryResource> queries;
     // Upgrade queries are stored in the 'version/##' subfolders.
     QDir dir = QDir(QString(":/SQL/%1/version/%2/").arg(displayName()).arg(version));
     foreach (QFileInfo fileInfo, dir.entryInfoList(QStringList() << "upgrade*", QDir::NoFilter, QDir::Name)) {
-        queries << queryString(fileInfo.baseName(), version);
+        queries << SqlQueryResource(queryString(fileInfo.baseName(), version), fileInfo.baseName());
     }
     return queries;
 }
@@ -239,38 +240,75 @@ bool AbstractSqlStorage::upgradeDb()
     // transaction.  This will need careful testing of potential additional space requirements and
     // any database modifications that might not be allowed in a transaction.
 
+    // Check if we're resuming an interrupted multi-step upgrade: is an upgrade step stored?
+    const QString previousLaunchUpgradeStep = schemaVersionUpgradeStep();
+    bool resumingUpgrade = !previousLaunchUpgradeStep.isEmpty();
+
     for (int ver = installedSchemaVersion() + 1; ver <= schemaVersion(); ver++) {
-        foreach (QString queryString, upgradeQueries(ver)) {
-            QSqlQuery query = db.exec(queryString);
+        foreach (auto queryResource, upgradeQueries(ver)) {
+            if (resumingUpgrade) {
+                // An upgrade was interrupted.  Check if this matches the the last successful query.
+                if (previousLaunchUpgradeStep == queryResource.queryFilename) {
+                    // Found the matching query!
+                    qInfo() << qPrintable(QString("Resuming interrupted upgrade for schema version %1 (last step: %2)")
+                                          .arg(QString::number(ver), previousLaunchUpgradeStep));
+
+                    // Stop searching for queries
+                    resumingUpgrade = false;
+                    // Continue past the previous query with the next not-yet-tried query
+                    continue;
+                }
+                else {
+                    // Not yet matched, keep looking
+                    continue;
+                }
+            }
+
+            // Run the upgrade query
+            QSqlQuery query = db.exec(queryResource.queryString);
             if (!watchQuery(query)) {
                 // Individual upgrade query failed, bail out
-                qCritical() << "Unable to upgrade Logging Backend!  Upgrade query in schema version" << ver << "failed.";
+                qCritical() << qPrintable(QString("Unable to upgrade Logging Backend!  Upgrade query in schema version %1 failed (step: %2).")
+                                          .arg(QString::number(ver), queryResource.queryFilename));
                 return false;
             }
+            else {
+                // Mark as successful
+                setSchemaVersionUpgradeStep(queryResource.queryFilename);
+            }
+        }
+
+        if (resumingUpgrade) {
+            // Something went wrong and the last successful SQL query to resume from couldn't be
+            // found.
+            // 1.  The storage of successful query glitched, or the database was manually changed
+            // 2.  Quassel changed the filenames of upgrade queries, and the local Quassel core
+            //     version was replaced during an interrupted schema upgrade
+            //
+            // Both are unlikely, but it's a good idea to handle it anyways.
+
+            qCritical() << qPrintable(QString("Unable to resume interrupted upgrade in Logging "
+                                              "Backend!  Missing upgrade step in schema version %1 "
+                                              "(expected step: %2)")
+                                      .arg(QString::number(ver), previousLaunchUpgradeStep));
+            return false;
         }
 
-        // Update the schema version for each intermediate step.  This ensures that any interrupted
-        // upgrades have a greater chance of resuming correctly after core restart.
+        // Update the schema version for each intermediate step and mark the step as done.  This
+        // ensures that any interrupted upgrades have a greater chance of resuming correctly after
+        // core restart.
         //
         // Almost all databases make single queries atomic (fully works or fully fails, no partial),
         // and with many of the longest migrations being a single query, this makes upgrade
         // interruptions much more likely to leave the database in a valid intermediate schema
         // version.
-        if (!updateSchemaVersion(ver)) {
+        if (!updateSchemaVersion(ver, true)) {
             // Updating the schema version failed, bail out
             qCritical() << "Unable to upgrade Logging Backend!  Setting schema version" << ver << "failed.";
             return false;
         }
     }
 
-    // Update the schema version for the final step.  Split this out to offer more informative
-    // logging (though setting schema version really should not fail).
-    if (!updateSchemaVersion(schemaVersion())) {
-        // Updating the final schema version failed, bail out
-        qCritical() << "Unable to upgrade Logging Backend!  Setting final schema version" << schemaVersion() << "failed.";
-        return false;
-    }
-
     // If we made it here, everything seems to have worked!
     return true;
 }
@@ -300,6 +338,14 @@ int AbstractSqlStorage::schemaVersion()
     return _schemaVersion;
 }
 
+
+QString AbstractSqlStorage::schemaVersionUpgradeStep()
+{
+    // By default, assume there's no pending upgrade
+    return {};
+}
+
+
 bool AbstractSqlStorage::watchQuery(QSqlQuery& query)
 {
     bool queryError = query.lastError().isValid();
@@ -338,7 +384,7 @@ bool AbstractSqlStorage::watchQuery(QSqlQuery& query)
             valueStrings << QString("%1=%2").arg(iter.key(), value);
         }
         qCritical() << "                bound Values:" << qPrintable(valueStrings.join(", "));
-        qCritical() << "                Error Number:" << query.lastError().number();
+        qCritical() << "                  Error Code:" << qPrintable(query.lastError().nativeErrorCode());
         qCritical() << "               Error Message:" << qPrintable(query.lastError().text());
         qCritical() << "              Driver Message:" << qPrintable(query.lastError().driverText());
         qCritical() << "                  DB Message:" << qPrintable(query.lastError().databaseText());
@@ -446,7 +492,7 @@ void AbstractSqlMigrator::dumpStatus()
     QList<QVariant> list = boundValues();
     for (int i = 0; i < list.size(); ++i)
         qWarning() << i << ": " << list.at(i).toString().toLatin1().data();
-    qWarning() << "  Error Number:" << lastError().number();
+    qWarning() << "  Error Code:" << qPrintable(lastError().nativeErrorCode());
     qWarning() << "  Error Message:" << lastError().text();
 }