Replace deprecated QSqlError::number with ::nativeErrorCode
[quassel.git] / src / core / abstractsqlstorage.cpp
index 6886c0f..cf9cff5 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-07 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  *
  *   You should have received a copy of the GNU General Public License     *
  *   along with this program; if not, write to the                         *
  *   Free Software Foundation, Inc.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
 #include "abstractsqlstorage.h"
 
-#include "logger.h"
-
 #include <QMutexLocker>
+#include <QSqlDriver>
 #include <QSqlError>
+#include <QSqlField>
 #include <QSqlQuery>
 
+#include "quassel.h"
+
 int AbstractSqlStorage::_nextConnectionId = 0;
-AbstractSqlStorage::AbstractSqlStorage(QObject *parent)
-  : Storage(parent),
-    _schemaVersion(0)
+AbstractSqlStorage::AbstractSqlStorage(QObject* parent)
+    : Storage(parent)
+{}
+
+AbstractSqlStorage::~AbstractSqlStorage()
 {
+    // disconnect the connections, so their deletion is no longer interessting for us
+    QHash<QThread*, Connection*>::iterator conIter;
+    for (conIter = _connectionPool.begin(); conIter != _connectionPool.end(); ++conIter) {
+        QSqlDatabase::removeDatabase(conIter.value()->name());
+        disconnect(conIter.value(), nullptr, this, nullptr);
+    }
 }
 
-AbstractSqlStorage::~AbstractSqlStorage() {
-  // disconnect the connections, so their deletion is no longer interessting for us
-  QHash<QThread *, Connection *>::iterator conIter;
-  for(conIter = _connectionPool.begin(); conIter != _connectionPool.end(); conIter++) {
-    QSqlDatabase::removeDatabase(conIter.value()->name());
-    disconnect(conIter.value(), 0, this, 0);
-  }
-}
+QSqlDatabase AbstractSqlStorage::logDb()
+{
+    if (!_connectionPool.contains(QThread::currentThread()))
+        addConnectionToPool();
 
-QSqlDatabase AbstractSqlStorage::logDb() {
-  if(!_connectionPool.contains(QThread::currentThread()))
-    addConnectionToPool();
+    QSqlDatabase db = QSqlDatabase::database(_connectionPool[QThread::currentThread()]->name(), false);
 
-  return QSqlDatabase::database(_connectionPool[QThread::currentThread()]->name());
+    if (!db.isOpen()) {
+        qWarning() << "Database connection" << displayName() << "for thread" << QThread::currentThread()
+                   << "was lost, attempting to reconnect...";
+        dbConnect(db);
+    }
+
+    return db;
 }
 
-void AbstractSqlStorage::addConnectionToPool() {
-  QMutexLocker locker(&_connectionPoolMutex);
-  // we have to recheck if the connection pool already contains a connection for
-  // this thread. Since now (after the lock) we can only tell for sure
-  if(_connectionPool.contains(QThread::currentThread()))
-    return;
+void AbstractSqlStorage::addConnectionToPool()
+{
+    QMutexLocker locker(&_connectionPoolMutex);
+    // we have to recheck if the connection pool already contains a connection for
+    // this thread. Since now (after the lock) we can only tell for sure
+    if (_connectionPool.contains(QThread::currentThread()))
+        return;
+
+    QThread* currentThread = QThread::currentThread();
 
-  QThread *currentThread = QThread::currentThread();
+    int connectionId = _nextConnectionId++;
 
-  int connectionId = _nextConnectionId++;
+    Connection* connection = new Connection(QLatin1String(QString("quassel_%1_con_%2").arg(driverName()).arg(connectionId).toLatin1()));
+    connection->moveToThread(currentThread);
+    connect(this, &QObject::destroyed, connection, &QObject::deleteLater);
+    connect(currentThread, &QObject::destroyed, connection, &QObject::deleteLater);
+    connect(connection, &QObject::destroyed, this, &AbstractSqlStorage::connectionDestroyed);
+    _connectionPool[currentThread] = connection;
 
-  Connection *connection = new Connection(QLatin1String(QString("quassel_%1_con_%2").arg(driverName()).arg(connectionId).toLatin1()));
-  connection->moveToThread(currentThread);
-  connect(this, SIGNAL(destroyed()), connection, SLOT(deleteLater()));
-  connect(currentThread, SIGNAL(destroyed()), connection, SLOT(deleteLater()));
-  connect(connection, SIGNAL(destroyed()), this, SLOT(connectionDestroyed()));
-  _connectionPool[currentThread] = connection;
+    QSqlDatabase db = QSqlDatabase::addDatabase(driverName(), connection->name());
+    db.setDatabaseName(databaseName());
 
-  QSqlDatabase db = QSqlDatabase::addDatabase(driverName(), connection->name());
-  db.setDatabaseName(databaseName());
+    if (!hostName().isEmpty())
+        db.setHostName(hostName());
 
-  if(!hostName().isEmpty())
-    db.setHostName(hostName());
+    if (port() != -1)
+        db.setPort(port());
 
-  if(port() != -1)
-    db.setPort(port());
+    if (!userName().isEmpty()) {
+        db.setUserName(userName());
+        db.setPassword(password());
+    }
 
-  if(!userName().isEmpty()) {
-    db.setUserName(userName());
-    db.setPassword(password());
-  }
+    dbConnect(db);
+}
 
-  if(!db.open()) {
-    qWarning() << "Unable to open database" << displayName() << "for thread" << QThread::currentThread();
-    qWarning() << "-" << db.lastError().text();
-  }
+void AbstractSqlStorage::dbConnect(QSqlDatabase& db)
+{
+    if (!db.open()) {
+        qWarning() << "Unable to open database" << displayName() << "for thread" << QThread::currentThread();
+        qWarning() << "-" << db.lastError().text();
+    }
+    else {
+        if (!initDbSession(db)) {
+            qWarning() << "Unable to initialize database" << displayName() << "for thread" << QThread::currentThread();
+            db.close();
+        }
+    }
 }
 
-Storage::State AbstractSqlStorage::init(const QVariantMap &settings) {
-  setConnectionProperties(settings);
+Storage::State AbstractSqlStorage::init(const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment)
+{
+    setConnectionProperties(settings, environment, loadFromEnvironment);
 
-  QSqlDatabase db = logDb();
-  if(!db.isValid() || !db.isOpen())
-    return NotAvailable;
+    _debug = Quassel::isOptionSet("debug");
 
-  if(installedSchemaVersion() == -1) {
-    qCritical() << "Storage Schema is missing!";
-    return NeedsSetup;
-  }
+    QSqlDatabase db = logDb();
+    if (!db.isValid() || !db.isOpen())
+        return NotAvailable;
 
-  if(installedSchemaVersion() > schemaVersion()) {
-    qCritical() << "Installed Schema is newer then any known Version.";
-    return NotAvailable;
-  }
+    if (installedSchemaVersion() == -1) {
+        qCritical() << "Storage Schema is missing!";
+        return NeedsSetup;
+    }
 
-  if(installedSchemaVersion() < schemaVersion()) {
-    qWarning() << qPrintable(tr("Installed Schema (version %1) is not up to date. Upgrading to version %2...").arg(installedSchemaVersion()).arg(schemaVersion()));
-    if(!upgradeDb()) {
-      qWarning() << qPrintable(tr("Upgrade failed..."));
-      return NotAvailable;
+    if (installedSchemaVersion() > schemaVersion()) {
+        qCritical() << "Installed Schema is newer then any known Version.";
+        return NotAvailable;
     }
-  }
 
-  quInfo() << qPrintable(displayName()) << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
-  return IsReady;
+    if (installedSchemaVersion() < schemaVersion()) {
+        qInfo() << qPrintable(tr("Installed database schema (version %1) is not up to date. Upgrading to "
+                                  "version %2...  This may take a while for major upgrades.")
+                                   .arg(installedSchemaVersion())
+                                   .arg(schemaVersion()));
+        emit dbUpgradeInProgress(true);
+        auto upgradeResult = upgradeDb();
+        emit dbUpgradeInProgress(false);
+        if (!upgradeResult) {
+            qWarning() << qPrintable(tr("Upgrade failed..."));
+            return NotAvailable;
+        }
+        // Add a message when migration succeeds to avoid confusing folks by implying the schema upgrade failed if
+        // later functionality does not work.
+        qInfo() << qPrintable(tr("Installed database schema successfully upgraded to version %1.").arg(schemaVersion()));
+    }
+
+    qInfo() << qPrintable(displayName()) << "storage backend is ready. Schema version:" << installedSchemaVersion();
+    return IsReady;
 }
 
-QString AbstractSqlStorage::queryString(const QString &queryName, int version) {
-  if(version == 0)
-    version = schemaVersion();
+QString AbstractSqlStorage::queryString(const QString& queryName, int version)
+{
+    QFileInfo queryInfo;
+
+    // The current schema is stored in the root folder, while upgrade queries are stored in the
+    // 'versions/##' subfolders.
+    if (version == 0) {
+        // Use the current SQL schema, not a versioned request
+        queryInfo = QFileInfo(QString(":/SQL/%1/%2.sql").arg(displayName()).arg(queryName));
+        // If version is needed later, get it via version = schemaVersion();
+    }
+    else {
+        // Use the specified schema version, not the general folder
+        queryInfo = QFileInfo(QString(":/SQL/%1/version/%2/%3.sql").arg(displayName()).arg(version).arg(queryName));
+    }
 
-  QFileInfo queryInfo(QString(":/SQL/%1/%2/%3.sql").arg(displayName()).arg(version).arg(queryName));
-  if(!queryInfo.exists() || !queryInfo.isFile() || !queryInfo.isReadable()) {
-    qCritical() << "Unable to read SQL-Query" << queryName << "for engine" << displayName();
-    return QString();
-  }
+    if (!queryInfo.exists() || !queryInfo.isFile() || !queryInfo.isReadable()) {
+        qCritical() << "Unable to read SQL-Query" << queryName << "for engine" << displayName();
+        return QString();
+    }
 
-  QFile queryFile(queryInfo.filePath());
-  if(!queryFile.open(QIODevice::ReadOnly | QIODevice::Text))
-    return QString();
-  QString query = QTextStream(&queryFile).readAll();
-  queryFile.close();
+    QFile queryFile(queryInfo.filePath());
+    if (!queryFile.open(QIODevice::ReadOnly | QIODevice::Text))
+        return QString();
+    QString query = QTextStream(&queryFile).readAll();
+    queryFile.close();
 
-  return query.trimmed();
+    return query.trimmed();
 }
 
-QStringList AbstractSqlStorage::setupQueries() {
-  QStringList queries;
-  QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(schemaVersion()));
-  foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "setup*", QDir::NoFilter, QDir::Name)) {
-    queries << queryString(fileInfo.baseName());
-  }
-  return queries;
+QList<AbstractSqlStorage::SqlQueryResource> AbstractSqlStorage::setupQueries()
+{
+    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 << SqlQueryResource(queryString(fileInfo.baseName()), fileInfo.baseName());
+    }
+    return queries;
 }
 
-bool AbstractSqlStorage::setup(const QVariantMap &settings) {
-  setConnectionProperties(settings);
-  QSqlDatabase db = logDb();
-  if(!db.isOpen()) {
-    qCritical() << "Unable to setup Logging Backend!";
-    return false;
-  }
-
-  db.transaction();
-  foreach(QString queryString, setupQueries()) {
-    QSqlQuery query = db.exec(queryString);
-    if(!watchQuery(query)) {
-      qCritical() << "Unable to setup Logging Backend!";
-      db.rollback();
-      return false;
+bool AbstractSqlStorage::setup(const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment)
+{
+    setConnectionProperties(settings, environment, loadFromEnvironment);
+    QSqlDatabase db = logDb();
+    if (!db.isOpen()) {
+        qCritical() << "Unable to setup Logging Backend!";
+        return false;
+    }
+
+    db.transaction();
+    foreach (auto queryResource, setupQueries()) {
+        QSqlQuery query = db.exec(queryResource.queryString);
+        if (!watchQuery(query)) {
+            qCritical() << qPrintable(QString("Unable to setup Logging Backend!  Setup query failed (step: %1).")
+                                      .arg(queryResource.queryFilename));
+            db.rollback();
+            return false;
+        }
     }
-  }
-  bool success = setupSchemaVersion(schemaVersion());
-  if(success)
-    db.commit();
-  else
-    db.rollback();
-  return success;
+    bool success = setupSchemaVersion(schemaVersion());
+    if (success)
+        db.commit();
+    else
+        db.rollback();
+    return success;
 }
 
-QStringList AbstractSqlStorage::upgradeQueries(int version) {
-  QStringList queries;
-  QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(version));
-  foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "upgrade*", QDir::NoFilter, QDir::Name)) {
-    queries << queryString(fileInfo.baseName(), version);
-  }
-  return queries;
+QList<AbstractSqlStorage::SqlQueryResource> AbstractSqlStorage::upgradeQueries(int version)
+{
+    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 << SqlQueryResource(queryString(fileInfo.baseName(), version), fileInfo.baseName());
+    }
+    return queries;
 }
 
-bool AbstractSqlStorage::upgradeDb() {
-  if(schemaVersion() <= installedSchemaVersion())
-    return true;
+bool AbstractSqlStorage::upgradeDb()
+{
+    if (schemaVersion() <= installedSchemaVersion())
+        return true;
+
+    QSqlDatabase db = logDb();
+
+    // TODO: For databases that support it (e.g. almost only PostgreSQL), wrap upgrades in a
+    // 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 (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() << 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 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, true)) {
+            // Updating the schema version failed, bail out
+            qCritical() << "Unable to upgrade Logging Backend!  Setting schema version" << ver << "failed.";
+            return false;
+        }
+    }
 
-  QSqlDatabase db = logDb();
+    // If we made it here, everything seems to have worked!
+    return true;
+}
 
-  for(int ver = installedSchemaVersion() + 1; ver <= schemaVersion(); ver++) {
-    foreach(QString queryString, upgradeQueries(ver)) {
-      QSqlQuery query = db.exec(queryString);
-      if(!watchQuery(query)) {
-       qCritical() << "Unable to upgrade Logging Backend!";
-       return false;
-      }
+int AbstractSqlStorage::schemaVersion()
+{
+    // returns the newest Schema Version!
+    // not the currently used one! (though it can be the same)
+    if (_schemaVersion > 0)
+        return _schemaVersion;
+
+    int version;
+    bool ok;
+    // Schema versions are stored in the 'version/##' subfolders.
+    QDir dir = QDir(QString(":/SQL/%1/version/").arg(displayName()));
+    foreach (QFileInfo fileInfo, dir.entryInfoList()) {
+        if (!fileInfo.isDir())
+            continue;
+
+        version = fileInfo.fileName().toInt(&ok);
+        if (!ok)
+            continue;
+
+        if (version > _schemaVersion)
+            _schemaVersion = version;
     }
-  }
-  return updateSchemaVersion(schemaVersion());
+    return _schemaVersion;
 }
 
 
-int AbstractSqlStorage::schemaVersion() {
-  // returns the newest Schema Version!
-  // not the currently used one! (though it can be the same)
-  if(_schemaVersion > 0)
-    return _schemaVersion;
-
-  int version;
-  bool ok;
-  QDir dir = QDir(":/SQL/" + displayName());
-  foreach(QFileInfo fileInfo, dir.entryInfoList()) {
-    if(!fileInfo.isDir())
-      continue;
-
-    version = fileInfo.fileName().toInt(&ok);
-    if(!ok)
-      continue;
-
-    if(version > _schemaVersion)
-      _schemaVersion = version;
-  }
-  return _schemaVersion;
+QString AbstractSqlStorage::schemaVersionUpgradeStep()
+{
+    // By default, assume there's no pending upgrade
+    return {};
 }
 
-bool AbstractSqlStorage::watchQuery(QSqlQuery &query) {
-  if(query.lastError().isValid()) {
-    qCritical() << "unhandled Error in QSqlQuery!";
-    qCritical() << "                  last Query:\n" << query.lastQuery();
-    qCritical() << "              executed Query:\n" << query.executedQuery();
-    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() << "                Error Number:"   << query.lastError().number();
-    qCritical() << "               Error Message:"   << query.lastError().text();
-    qCritical() << "              Driver Message:"   << query.lastError().driverText();
-    qCritical() << "                  DB Message:"   << query.lastError().databaseText();
-
-    return false;
-  }
-  return true;
+
+bool AbstractSqlStorage::watchQuery(QSqlQuery& query)
+{
+    bool queryError = query.lastError().isValid();
+    if (queryError || _debug) {
+        if (queryError)
+            qCritical() << "unhandled Error in QSqlQuery!";
+        qCritical() << "                  last Query:\n" << qPrintable(query.lastQuery());
+        qCritical() << "              executed Query:\n" << qPrintable(query.executedQuery());
+        QVariantMap boundValues = query.boundValues();
+        QStringList valueStrings;
+        QVariantMap::const_iterator iter;
+        for (iter = boundValues.constBegin(); iter != boundValues.constEnd(); ++iter) {
+            QString value;
+            QSqlField field;
+            if (query.driver()) {
+                // let the driver do the formatting
+                field.setType(iter.value().type());
+                if (iter.value().isNull())
+                    field.clear();
+                else
+                    field.setValue(iter.value());
+                value = query.driver()->formatValue(field);
+            }
+            else {
+                switch (iter.value().type()) {
+                case QVariant::Invalid:
+                    value = "NULL";
+                    break;
+                case QVariant::Int:
+                    value = iter.value().toString();
+                    break;
+                default:
+                    value = QString("'%1'").arg(iter.value().toString());
+                }
+            }
+            valueStrings << QString("%1=%2").arg(iter.key(), value);
+        }
+        qCritical() << "                bound Values:" << qPrintable(valueStrings.join(", "));
+        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());
+
+        return !queryError;
+    }
+    return true;
 }
 
-void AbstractSqlStorage::connectionDestroyed() {
-  QMutexLocker locker(&_connectionPoolMutex);
-  _connectionPool.remove(sender()->thread());
+void AbstractSqlStorage::connectionDestroyed()
+{
+    QMutexLocker locker(&_connectionPoolMutex);
+    _connectionPool.remove(sender()->thread());
 }
 
 // ========================================
 //  AbstractSqlStorage::Connection
 // ========================================
-AbstractSqlStorage::Connection::Connection(const QString &name, QObject *parent)
-  : QObject(parent),
-    _name(name.toLatin1())
-{
-}
+AbstractSqlStorage::Connection::Connection(const QString& name, QObject* parent)
+    : QObject(parent)
+    , _name(name.toLatin1())
+{}
 
-AbstractSqlStorage::Connection::~Connection() {
-  {
-    QSqlDatabase db = QSqlDatabase::database(name(), false);
-    if(db.isOpen()) {
-      db.commit();
-      db.close();
+AbstractSqlStorage::Connection::~Connection()
+{
+    {
+        QSqlDatabase db = QSqlDatabase::database(name(), false);
+        if (db.isOpen()) {
+            db.commit();
+            db.close();
+        }
     }
-  }
-  QSqlDatabase::removeDatabase(name());
+    QSqlDatabase::removeDatabase(name());
 }
 
-
-
-
 // ========================================
 //  AbstractSqlMigrator
 // ========================================
-AbstractSqlMigrator::AbstractSqlMigrator()
-  : _query(0)
+
+void AbstractSqlMigrator::newQuery(const QString& query, QSqlDatabase db)
 {
+    Q_ASSERT(!_query);
+    _query = new QSqlQuery(db);
+    _query->prepare(query);
 }
 
-void AbstractSqlMigrator::newQuery(const QString &query, QSqlDatabase db) {
-  Q_ASSERT(!_query);
-  _query = new QSqlQuery(db);
-  _query->prepare(query);
+void AbstractSqlMigrator::resetQuery()
+{
+    delete _query;
+    _query = nullptr;
 }
 
-void AbstractSqlMigrator::resetQuery() {
-  delete _query;
-  _query = 0;
+bool AbstractSqlMigrator::exec()
+{
+    Q_ASSERT(_query);
+    _query->exec();
+    return !_query->lastError().isValid();
 }
 
-bool AbstractSqlMigrator::exec() {
-  Q_ASSERT(_query);
-  _query->exec();
-  return !_query->lastError().isValid();
+QString AbstractSqlMigrator::migrationObject(MigrationObject moType)
+{
+    switch (moType) {
+    case QuasselUser:
+        return "QuasselUser";
+    case Sender:
+        return "Sender";
+    case Identity:
+        return "Identity";
+    case IdentityNick:
+        return "IdentityNick";
+    case Network:
+        return "Network";
+    case Buffer:
+        return "Buffer";
+    case Backlog:
+        return "Backlog";
+    case IrcServer:
+        return "IrcServer";
+    case UserSetting:
+        return "UserSetting";
+    case CoreState:
+        return "CoreState";
+    };
+    return QString();
 }
 
-QString AbstractSqlMigrator::migrationObject(MigrationObject moType) {
-  switch(moType) {
-  case QuasselUser:
-    return "QuasselUser";
-  case Sender:
-    return "Sender";
-  case Identity:
-    return "Identity";
-  case IdentityNick:
-    return "IdentityNick";
-  case Network:
-    return "Network";
-  case Buffer:
-    return "Buffer";
-  case Backlog:
-    return "Backlog";
-  case IrcServer:
-    return "IrcServer";
-  case UserSetting:
-    return "UserSetting";
-  };
-  return QString();
-}
+QVariantList AbstractSqlMigrator::boundValues()
+{
+    QVariantList values;
+    if (!_query)
+        return values;
 
-QVariantList AbstractSqlMigrator::boundValues() {
-  QVariantList values;
-  if(!_query)
+    int numValues = _query->boundValues().count();
+    for (int i = 0; i < numValues; i++) {
+        values << _query->boundValue(i);
+    }
     return values;
-
-  int numValues = _query->boundValues().count();
-  for(int i = 0; i < numValues; i++) {
-    values << _query->boundValue(i);
-  }
-  return values;
 }
 
-void AbstractSqlMigrator::dumpStatus() {
-  qWarning() << "  executed Query:";
-  qWarning() << qPrintable(executedQuery());
-  qWarning() << "  bound Values:";
-  QList<QVariant> list = boundValues();
-  for (int i = 0; i < list.size(); ++i)
-    qWarning() << i << ": " << list.at(i).toString().toAscii().data();
-  qWarning() << "  Error Number:"   << lastError().number();
-  qWarning() << "  Error Message:"   << lastError().text();
+void AbstractSqlMigrator::dumpStatus()
+{
+    qWarning() << "  executed Query:";
+    qWarning() << qPrintable(executedQuery());
+    qWarning() << "  bound Values:";
+    QList<QVariant> list = boundValues();
+    for (int i = 0; i < list.size(); ++i)
+        qWarning() << i << ": " << list.at(i).toString().toLatin1().data();
+    qWarning() << "  Error Code:" << qPrintable(lastError().nativeErrorCode());
+    qWarning() << "  Error Message:" << lastError().text();
 }
 
-
 // ========================================
 //  AbstractSqlMigrationReader
 // ========================================
 AbstractSqlMigrationReader::AbstractSqlMigrationReader()
-  : AbstractSqlMigrator(),
-    _writer(0)
+    : AbstractSqlMigrator()
+{}
+
+bool AbstractSqlMigrationReader::migrateTo(AbstractSqlMigrationWriter* writer)
 {
-}
+    if (!transaction()) {
+        qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start reader's transaction!";
+        return false;
+    }
+    if (!writer->transaction()) {
+        qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start writer's transaction!";
+        rollback();  // close the reader transaction;
+        return false;
+    }
+
+    _writer = writer;
+
+    // due to the incompatibility across Migration objects we can't run this in a loop... :/
+    QuasselUserMO quasselUserMo;
+    if (!transferMo(QuasselUser, quasselUserMo))
+        return false;
+
+    IdentityMO identityMo;
+    if (!transferMo(Identity, identityMo))
+        return false;
+
+    IdentityNickMO identityNickMo;
+    if (!transferMo(IdentityNick, identityNickMo))
+        return false;
+
+    NetworkMO networkMo;
+    if (!transferMo(Network, networkMo))
+        return false;
+
+    BufferMO bufferMo;
+    if (!transferMo(Buffer, bufferMo))
+        return false;
+
+    SenderMO senderMo;
+    if (!transferMo(Sender, senderMo))
+        return false;
+
+    BacklogMO backlogMo;
+    if (!transferMo(Backlog, backlogMo))
+        return false;
 
-bool AbstractSqlMigrationReader::migrateTo(AbstractSqlMigrationWriter *writer) {
-  if(!transaction()) {
-    qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start reader's transaction!";
-    return false;
-  }
-  if(!writer->transaction()) {
-    qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start writer's transaction!";
-    rollback(); // close the reader transaction;
-    return false;
-  }
-
-  _writer = writer;
-
-  // due to the incompatibility across Migration objects we can't run this in a loop... :/
-  QuasselUserMO quasselUserMo;
-  if(!transferMo(QuasselUser, quasselUserMo))
-     return false;
-
-  IdentityMO identityMo;
-  if(!transferMo(Identity, identityMo))
-    return false;
-
-  IdentityNickMO identityNickMo;
-  if(!transferMo(IdentityNick, identityNickMo))
-    return false;
-
-  NetworkMO networkMo;
-  if(!transferMo(Network, networkMo))
-    return false;
-
-  BufferMO bufferMo;
-  if(!transferMo(Buffer, bufferMo))
-    return false;
-
-  SenderMO senderMo;
-  if(!transferMo(Sender, senderMo))
-    return false;
-
-  BacklogMO backlogMo;
-  if(!transferMo(Backlog, backlogMo))
-    return false;
-
-  IrcServerMO ircServerMo;
-  if(!transferMo(IrcServer, ircServerMo))
-    return false;
-
-  UserSettingMO userSettingMo;
-  if(!transferMo(UserSetting, userSettingMo))
-    return false;
-
-  if(!_writer->postProcess())
-    abortMigration();
-  return finalizeMigration();
+    IrcServerMO ircServerMo;
+    if (!transferMo(IrcServer, ircServerMo))
+        return false;
+
+    UserSettingMO userSettingMo;
+    if (!transferMo(UserSetting, userSettingMo))
+        return false;
+
+    CoreStateMO coreStateMO;
+    if (!transferMo(CoreState, coreStateMO))
+        return false;
+
+    if (!_writer->postProcess())
+        abortMigration();
+    return finalizeMigration();
 }
 
-void AbstractSqlMigrationReader::abortMigration(const QString &errorMsg) {
-  qWarning() << "Migration Failed!";
-  if(!errorMsg.isNull()) {
-    qWarning() << qPrintable(errorMsg);
-  }
-  if(lastError().isValid()) {
-    qWarning() << "ReaderError:";
-    dumpStatus();
-  }
-
-
-  if(_writer->lastError().isValid()) {
-    qWarning() << "WriterError:";
-    _writer->dumpStatus();
-  }
-
-  rollback();
-  _writer->rollback();
-  _writer = 0;
+void AbstractSqlMigrationReader::abortMigration(const QString& errorMsg)
+{
+    qWarning() << "Migration Failed!";
+    if (!errorMsg.isNull()) {
+        qWarning() << qPrintable(errorMsg);
+    }
+    if (lastError().isValid()) {
+        qWarning() << "ReaderError:";
+        dumpStatus();
+    }
+
+    if (_writer->lastError().isValid()) {
+        qWarning() << "WriterError:";
+        _writer->dumpStatus();
+    }
+
+    rollback();
+    _writer->rollback();
+    _writer = nullptr;
 }
 
-bool AbstractSqlMigrationReader::finalizeMigration() {
-  resetQuery();
-  _writer->resetQuery();
-
-  commit();
-  if(!_writer->commit()) {
-    _writer = 0;
-    return false;
-  }
-  _writer = 0;
-  return true;
+bool AbstractSqlMigrationReader::finalizeMigration()
+{
+    resetQuery();
+    _writer->resetQuery();
+
+    commit();
+    if (!_writer->commit()) {
+        _writer = nullptr;
+        return false;
+    }
+    _writer = nullptr;
+    return true;
 }
 
 template<typename T>
-bool AbstractSqlMigrationReader::transferMo(MigrationObject moType, T &mo) {
-  resetQuery();
-  _writer->resetQuery();
-
-  if(!prepareQuery(moType)) {
-    abortMigration(QString("AbstractSqlMigrationReader::migrateTo(): unable to prepare reader query of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
-    return false;
-  }
-  if(!_writer->prepareQuery(moType)) {
-    abortMigration(QString("AbstractSqlMigrationReader::migrateTo(): unable to prepare writer query of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
-    return false;
-  }
-
-  qDebug() << qPrintable(QString("Transferring %1...").arg(AbstractSqlMigrator::migrationObject(moType)));
-  int i = 0;
-  QFile file;
-  file.open(stdout, QIODevice::WriteOnly);
-
-  while(readMo(mo)) {
-    if(!_writer->writeMo(mo)) {
-      abortMigration(QString("AbstractSqlMigrationReader::transferMo(): unable to transfer Migratable Object of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
-      return false;
+bool AbstractSqlMigrationReader::transferMo(MigrationObject moType, T& mo)
+{
+    resetQuery();
+    _writer->resetQuery();
+
+    if (!prepareQuery(moType)) {
+        abortMigration(QString("AbstractSqlMigrationReader::migrateTo(): unable to prepare reader query of type %1!")
+                           .arg(AbstractSqlMigrator::migrationObject(moType)));
+        return false;
+    }
+    if (!_writer->prepareQuery(moType)) {
+        abortMigration(QString("AbstractSqlMigrationReader::migrateTo(): unable to prepare writer query of type %1!")
+                           .arg(AbstractSqlMigrator::migrationObject(moType)));
+        return false;
     }
-    i++;
-    if(i % 1000 == 0) {
-      file.write("*");
-      file.flush();
+
+    qDebug() << qPrintable(QString("Transferring %1...").arg(AbstractSqlMigrator::migrationObject(moType)));
+    int i = 0;
+    QFile file;
+    file.open(stdout, QIODevice::WriteOnly);
+
+    while (readMo(mo)) {
+        if (!_writer->writeMo(mo)) {
+            abortMigration(QString("AbstractSqlMigrationReader::transferMo(): unable to transfer Migratable Object of type %1!")
+                               .arg(AbstractSqlMigrator::migrationObject(moType)));
+            return false;
+        }
+        i++;
+        if (i % 1000 == 0) {
+            file.write("*");
+            file.flush();
+        }
+    }
+    if (i > 1000) {
+        file.write("\n");
+        file.flush();
     }
-  }
-  if(i > 1000) {
-    file.write("\n");
-    file.flush();
-  }
-
-  qDebug() << "Done.";
-  return true;
+
+    qDebug() << "Done.";
+    return true;
+}
+
+uint qHash(const SenderData& key)
+{
+    return qHash(QString(key.sender + "\n" + key.realname + "\n" + key.avatarurl));
 }
 
+bool operator==(const SenderData& a, const SenderData& b)
+{
+    return a.sender == b.sender && a.realname == b.realname && a.avatarurl == b.avatarurl;
+}