/***************************************************************************
- * Copyright (C) 2005-07 by the Quassel IRC Team *
+ * Copyright (C) 2005-07 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
#include "logger.h"
#include <QMutexLocker>
+#include <QSqlDriver>
#include <QSqlError>
+#include <QSqlField>
#include <QSqlQuery>
+int AbstractSqlStorage::_nextConnectionId = 0;
AbstractSqlStorage::AbstractSqlStorage(QObject *parent)
: Storage(parent),
- _schemaVersion(0),
- _nextConnectionId(0)
+ _schemaVersion(0)
{
}
// 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);
}
}
if(!_connectionPool.contains(QThread::currentThread()))
addConnectionToPool();
- qDebug() << "using logDb" << _connectionPool[QThread::currentThread()]->name() << QThread::currentThread();
return QSqlDatabase::database(_connectionPool[QThread::currentThread()]->name());
}
int connectionId = _nextConnectionId++;
- Connection *connection = new Connection(QLatin1String(QString("quassel_connection_%1").arg(connectionId).toLatin1()));
- qDebug() << "new connection" << connection->name() << currentThread << QLatin1String(QString("quassel_connection_%1").arg(connectionId).toLatin1());
+ 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()));
if(!hostName().isEmpty())
db.setHostName(hostName());
+ if(port() != -1)
+ db.setPort(port());
+
if(!userName().isEmpty()) {
db.setUserName(userName());
db.setPassword(password());
if(!db.open()) {
qWarning() << "Unable to open database" << displayName() << "for thread" << QThread::currentThread();
qWarning() << "-" << db.lastError().text();
+ } else {
+ initDbSession(db);
}
}
-bool AbstractSqlStorage::init(const QVariantMap &settings) {
- Q_UNUSED(settings)
+Storage::State AbstractSqlStorage::init(const QVariantMap &settings) {
+ setConnectionProperties(settings);
+
QSqlDatabase db = logDb();
if(!db.isValid() || !db.isOpen())
- return false;
+ return NotAvailable;
if(installedSchemaVersion() == -1) {
qCritical() << "Storage Schema is missing!";
- return false;
+ return NeedsSetup;
}
if(installedSchemaVersion() > schemaVersion()) {
qCritical() << "Installed Schema is newer then any known Version.";
- return false;
+ return NotAvailable;
}
-
+
if(installedSchemaVersion() < schemaVersion()) {
- qWarning() << "Installed Schema is not up to date. Upgrading...";
- if(!upgradeDb())
- return false;
+ 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;
+ }
}
-
- quInfo() << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
- return true;
+
+ quInfo() << qPrintable(displayName()) << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
+ return IsReady;
}
QString AbstractSqlStorage::queryString(const QString &queryName, int version) {
if(version == 0)
version = schemaVersion();
-
+
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();
QString query = QTextStream(&queryFile).readAll();
queryFile.close();
-
+
return query.trimmed();
}
}
bool AbstractSqlStorage::setup(const QVariantMap &settings) {
- Q_UNUSED(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;
}
}
- return true;
+ bool success = setupSchemaVersion(schemaVersion());
+ if(success)
+ db.commit();
+ else
+ db.rollback();
+ return success;
}
QStringList AbstractSqlStorage::upgradeQueries(int version) {
}
}
}
- return true;
+ return updateSchemaVersion(schemaVersion());
}
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();
-
+ 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 Number:" << query.lastError().number();
+ qCritical() << " Error Message:" << qPrintable(query.lastError().text());
+ qCritical() << " Driver Message:" << qPrintable(query.lastError().driverText());
+ qCritical() << " DB Message:" << qPrintable(query.lastError().databaseText());
+
return false;
}
return true;
}
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::resetQuery() {
+ delete _query;
+ _query = 0;
+}
+
+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";
+ };
+ return QString();
+}
+
+QVariantList AbstractSqlMigrator::boundValues() {
+ QVariantList values;
+ if(!_query)
+ 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();
+}
+
+
+// ========================================
+// AbstractSqlMigrationReader
+// ========================================
+AbstractSqlMigrationReader::AbstractSqlMigrationReader()
+ : AbstractSqlMigrator(),
+ _writer(0)
+{
+}
+
+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();
+}
+
+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;
+}
+
+bool AbstractSqlMigrationReader::finalizeMigration() {
+ resetQuery();
+ _writer->resetQuery();
+
+ commit();
+ if(!_writer->commit()) {
+ _writer = 0;
+ return false;
+ }
+ _writer = 0;
+ 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;
+ }
+ i++;
+ if(i % 1000 == 0) {
+ file.write("*");
+ file.flush();
+ }
+ }
+ if(i > 1000) {
+ file.write("\n");
+ file.flush();
+ }
+
+ qDebug() << "Done.";
+ return true;
+}
+