Port IrcServerHandler::defaultHandler() to the event backend
[quassel.git] / src / core / abstractsqlstorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "abstractsqlstorage.h"
22 #include "quassel.h"
23
24 #include "logger.h"
25
26 #include <QMutexLocker>
27 #include <QSqlDriver>
28 #include <QSqlError>
29 #include <QSqlField>
30 #include <QSqlQuery>
31
32 int AbstractSqlStorage::_nextConnectionId = 0;
33 AbstractSqlStorage::AbstractSqlStorage(QObject *parent)
34   : Storage(parent),
35     _schemaVersion(0)
36 {
37 }
38
39 AbstractSqlStorage::~AbstractSqlStorage() {
40   // disconnect the connections, so their deletion is no longer interessting for us
41   QHash<QThread *, Connection *>::iterator conIter;
42   for(conIter = _connectionPool.begin(); conIter != _connectionPool.end(); conIter++) {
43     QSqlDatabase::removeDatabase(conIter.value()->name());
44     disconnect(conIter.value(), 0, this, 0);
45   }
46 }
47
48 QSqlDatabase AbstractSqlStorage::logDb() {
49   if(!_connectionPool.contains(QThread::currentThread()))
50     addConnectionToPool();
51
52   return QSqlDatabase::database(_connectionPool[QThread::currentThread()]->name());
53 }
54
55 void AbstractSqlStorage::addConnectionToPool() {
56   QMutexLocker locker(&_connectionPoolMutex);
57   // we have to recheck if the connection pool already contains a connection for
58   // this thread. Since now (after the lock) we can only tell for sure
59   if(_connectionPool.contains(QThread::currentThread()))
60     return;
61
62   QThread *currentThread = QThread::currentThread();
63
64   int connectionId = _nextConnectionId++;
65
66   Connection *connection = new Connection(QLatin1String(QString("quassel_%1_con_%2").arg(driverName()).arg(connectionId).toLatin1()));
67   connection->moveToThread(currentThread);
68   connect(this, SIGNAL(destroyed()), connection, SLOT(deleteLater()));
69   connect(currentThread, SIGNAL(destroyed()), connection, SLOT(deleteLater()));
70   connect(connection, SIGNAL(destroyed()), this, SLOT(connectionDestroyed()));
71   _connectionPool[currentThread] = connection;
72
73   QSqlDatabase db = QSqlDatabase::addDatabase(driverName(), connection->name());
74   db.setDatabaseName(databaseName());
75
76   if(!hostName().isEmpty())
77     db.setHostName(hostName());
78
79   if(port() != -1)
80     db.setPort(port());
81
82   if(!userName().isEmpty()) {
83     db.setUserName(userName());
84     db.setPassword(password());
85   }
86
87   if(!db.open()) {
88     qWarning() << "Unable to open database" << displayName() << "for thread" << QThread::currentThread();
89     qWarning() << "-" << db.lastError().text();
90   } else {
91     initDbSession(db);
92   }
93 }
94
95 Storage::State AbstractSqlStorage::init(const QVariantMap &settings) {
96   setConnectionProperties(settings);
97
98   _debug = Quassel::isOptionSet("debug");
99
100   QSqlDatabase db = logDb();
101   if(!db.isValid() || !db.isOpen())
102     return NotAvailable;
103
104   if(installedSchemaVersion() == -1) {
105     qCritical() << "Storage Schema is missing!";
106     return NeedsSetup;
107   }
108
109   if(installedSchemaVersion() > schemaVersion()) {
110     qCritical() << "Installed Schema is newer then any known Version.";
111     return NotAvailable;
112   }
113
114   if(installedSchemaVersion() < schemaVersion()) {
115     qWarning() << qPrintable(tr("Installed Schema (version %1) is not up to date. Upgrading to version %2...").arg(installedSchemaVersion()).arg(schemaVersion()));
116     if(!upgradeDb()) {
117       qWarning() << qPrintable(tr("Upgrade failed..."));
118       return NotAvailable;
119     }
120   }
121
122   quInfo() << qPrintable(displayName()) << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
123   return IsReady;
124 }
125
126 QString AbstractSqlStorage::queryString(const QString &queryName, int version) {
127   if(version == 0)
128     version = schemaVersion();
129
130   QFileInfo queryInfo(QString(":/SQL/%1/%2/%3.sql").arg(displayName()).arg(version).arg(queryName));
131   if(!queryInfo.exists() || !queryInfo.isFile() || !queryInfo.isReadable()) {
132     qCritical() << "Unable to read SQL-Query" << queryName << "for engine" << displayName();
133     return QString();
134   }
135
136   QFile queryFile(queryInfo.filePath());
137   if(!queryFile.open(QIODevice::ReadOnly | QIODevice::Text))
138     return QString();
139   QString query = QTextStream(&queryFile).readAll();
140   queryFile.close();
141
142   return query.trimmed();
143 }
144
145 QStringList AbstractSqlStorage::setupQueries() {
146   QStringList queries;
147   QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(schemaVersion()));
148   foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "setup*", QDir::NoFilter, QDir::Name)) {
149     queries << queryString(fileInfo.baseName());
150   }
151   return queries;
152 }
153
154 bool AbstractSqlStorage::setup(const QVariantMap &settings) {
155   setConnectionProperties(settings);
156   QSqlDatabase db = logDb();
157   if(!db.isOpen()) {
158     qCritical() << "Unable to setup Logging Backend!";
159     return false;
160   }
161
162   db.transaction();
163   foreach(QString queryString, setupQueries()) {
164     QSqlQuery query = db.exec(queryString);
165     if(!watchQuery(query)) {
166       qCritical() << "Unable to setup Logging Backend!";
167       db.rollback();
168       return false;
169     }
170   }
171   bool success = setupSchemaVersion(schemaVersion());
172   if(success)
173     db.commit();
174   else
175     db.rollback();
176   return success;
177 }
178
179 QStringList AbstractSqlStorage::upgradeQueries(int version) {
180   QStringList queries;
181   QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(version));
182   foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "upgrade*", QDir::NoFilter, QDir::Name)) {
183     queries << queryString(fileInfo.baseName(), version);
184   }
185   return queries;
186 }
187
188 bool AbstractSqlStorage::upgradeDb() {
189   if(schemaVersion() <= installedSchemaVersion())
190     return true;
191
192   QSqlDatabase db = logDb();
193
194   for(int ver = installedSchemaVersion() + 1; ver <= schemaVersion(); ver++) {
195     foreach(QString queryString, upgradeQueries(ver)) {
196       QSqlQuery query = db.exec(queryString);
197       if(!watchQuery(query)) {
198         qCritical() << "Unable to upgrade Logging Backend!";
199         return false;
200       }
201     }
202   }
203   return updateSchemaVersion(schemaVersion());
204 }
205
206
207 int AbstractSqlStorage::schemaVersion() {
208   // returns the newest Schema Version!
209   // not the currently used one! (though it can be the same)
210   if(_schemaVersion > 0)
211     return _schemaVersion;
212
213   int version;
214   bool ok;
215   QDir dir = QDir(":/SQL/" + displayName());
216   foreach(QFileInfo fileInfo, dir.entryInfoList()) {
217     if(!fileInfo.isDir())
218       continue;
219
220     version = fileInfo.fileName().toInt(&ok);
221     if(!ok)
222       continue;
223
224     if(version > _schemaVersion)
225       _schemaVersion = version;
226   }
227   return _schemaVersion;
228 }
229
230 bool AbstractSqlStorage::watchQuery(QSqlQuery &query) {
231   bool queryError = query.lastError().isValid();
232   if(queryError || _debug) {
233     if(queryError)
234       qCritical() << "unhandled Error in QSqlQuery!";
235     qCritical() << "                  last Query:\n" << qPrintable(query.lastQuery());
236     qCritical() << "              executed Query:\n" << qPrintable(query.executedQuery());
237     QVariantMap boundValues = query.boundValues();
238     QStringList valueStrings;
239     QVariantMap::const_iterator iter;
240     for(iter = boundValues.constBegin(); iter != boundValues.constEnd(); iter++) {
241       QString value;
242       QSqlField field;
243       if(query.driver()) {
244         // let the driver do the formatting
245         field.setType(iter.value().type());
246         if(iter.value().isNull())
247           field.clear();
248         else
249           field.setValue(iter.value());
250         value =  query.driver()->formatValue(field);
251       } else {
252         switch(iter.value().type()) {
253         case QVariant::Invalid:
254           value = "NULL";
255           break;
256         case QVariant::Int:
257           value = iter.value().toString();
258           break;
259         default:
260           value = QString("'%1'").arg(iter.value().toString());
261         }
262       }
263       valueStrings << QString("%1=%2").arg(iter.key(), value);
264     }
265     qCritical() << "                bound Values:" << qPrintable(valueStrings.join(", "));
266     qCritical() << "                Error Number:" << query.lastError().number();
267     qCritical() << "               Error Message:" << qPrintable(query.lastError().text());
268     qCritical() << "              Driver Message:" << qPrintable(query.lastError().driverText());
269     qCritical() << "                  DB Message:" << qPrintable(query.lastError().databaseText());
270
271     return !queryError;
272   }
273   return true;
274 }
275
276 void AbstractSqlStorage::connectionDestroyed() {
277   QMutexLocker locker(&_connectionPoolMutex);
278   _connectionPool.remove(sender()->thread());
279 }
280
281 // ========================================
282 //  AbstractSqlStorage::Connection
283 // ========================================
284 AbstractSqlStorage::Connection::Connection(const QString &name, QObject *parent)
285   : QObject(parent),
286     _name(name.toLatin1())
287 {
288 }
289
290 AbstractSqlStorage::Connection::~Connection() {
291   {
292     QSqlDatabase db = QSqlDatabase::database(name(), false);
293     if(db.isOpen()) {
294       db.commit();
295       db.close();
296     }
297   }
298   QSqlDatabase::removeDatabase(name());
299 }
300
301
302
303
304 // ========================================
305 //  AbstractSqlMigrator
306 // ========================================
307 AbstractSqlMigrator::AbstractSqlMigrator()
308   : _query(0)
309 {
310 }
311
312 void AbstractSqlMigrator::newQuery(const QString &query, QSqlDatabase db) {
313   Q_ASSERT(!_query);
314   _query = new QSqlQuery(db);
315   _query->prepare(query);
316 }
317
318 void AbstractSqlMigrator::resetQuery() {
319   delete _query;
320   _query = 0;
321 }
322
323 bool AbstractSqlMigrator::exec() {
324   Q_ASSERT(_query);
325   _query->exec();
326   return !_query->lastError().isValid();
327 }
328
329 QString AbstractSqlMigrator::migrationObject(MigrationObject moType) {
330   switch(moType) {
331   case QuasselUser:
332     return "QuasselUser";
333   case Sender:
334     return "Sender";
335   case Identity:
336     return "Identity";
337   case IdentityNick:
338     return "IdentityNick";
339   case Network:
340     return "Network";
341   case Buffer:
342     return "Buffer";
343   case Backlog:
344     return "Backlog";
345   case IrcServer:
346     return "IrcServer";
347   case UserSetting:
348     return "UserSetting";
349   };
350   return QString();
351 }
352
353 QVariantList AbstractSqlMigrator::boundValues() {
354   QVariantList values;
355   if(!_query)
356     return values;
357
358   int numValues = _query->boundValues().count();
359   for(int i = 0; i < numValues; i++) {
360     values << _query->boundValue(i);
361   }
362   return values;
363 }
364
365 void AbstractSqlMigrator::dumpStatus() {
366   qWarning() << "  executed Query:";
367   qWarning() << qPrintable(executedQuery());
368   qWarning() << "  bound Values:";
369   QList<QVariant> list = boundValues();
370   for (int i = 0; i < list.size(); ++i)
371     qWarning() << i << ": " << list.at(i).toString().toAscii().data();
372   qWarning() << "  Error Number:"   << lastError().number();
373   qWarning() << "  Error Message:"   << lastError().text();
374 }
375
376
377 // ========================================
378 //  AbstractSqlMigrationReader
379 // ========================================
380 AbstractSqlMigrationReader::AbstractSqlMigrationReader()
381   : AbstractSqlMigrator(),
382     _writer(0)
383 {
384 }
385
386 bool AbstractSqlMigrationReader::migrateTo(AbstractSqlMigrationWriter *writer) {
387   if(!transaction()) {
388     qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start reader's transaction!";
389     return false;
390   }
391   if(!writer->transaction()) {
392     qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start writer's transaction!";
393     rollback(); // close the reader transaction;
394     return false;
395   }
396
397   _writer = writer;
398
399   // due to the incompatibility across Migration objects we can't run this in a loop... :/
400   QuasselUserMO quasselUserMo;
401   if(!transferMo(QuasselUser, quasselUserMo))
402      return false;
403
404   IdentityMO identityMo;
405   if(!transferMo(Identity, identityMo))
406     return false;
407
408   IdentityNickMO identityNickMo;
409   if(!transferMo(IdentityNick, identityNickMo))
410     return false;
411
412   NetworkMO networkMo;
413   if(!transferMo(Network, networkMo))
414     return false;
415
416   BufferMO bufferMo;
417   if(!transferMo(Buffer, bufferMo))
418     return false;
419
420   SenderMO senderMo;
421   if(!transferMo(Sender, senderMo))
422     return false;
423
424   BacklogMO backlogMo;
425   if(!transferMo(Backlog, backlogMo))
426     return false;
427
428   IrcServerMO ircServerMo;
429   if(!transferMo(IrcServer, ircServerMo))
430     return false;
431
432   UserSettingMO userSettingMo;
433   if(!transferMo(UserSetting, userSettingMo))
434     return false;
435
436   if(!_writer->postProcess())
437     abortMigration();
438   return finalizeMigration();
439 }
440
441 void AbstractSqlMigrationReader::abortMigration(const QString &errorMsg) {
442   qWarning() << "Migration Failed!";
443   if(!errorMsg.isNull()) {
444     qWarning() << qPrintable(errorMsg);
445   }
446   if(lastError().isValid()) {
447     qWarning() << "ReaderError:";
448     dumpStatus();
449   }
450
451
452   if(_writer->lastError().isValid()) {
453     qWarning() << "WriterError:";
454     _writer->dumpStatus();
455   }
456
457   rollback();
458   _writer->rollback();
459   _writer = 0;
460 }
461
462 bool AbstractSqlMigrationReader::finalizeMigration() {
463   resetQuery();
464   _writer->resetQuery();
465
466   commit();
467   if(!_writer->commit()) {
468     _writer = 0;
469     return false;
470   }
471   _writer = 0;
472   return true;
473 }
474
475 template<typename T>
476 bool AbstractSqlMigrationReader::transferMo(MigrationObject moType, T &mo) {
477   resetQuery();
478   _writer->resetQuery();
479
480   if(!prepareQuery(moType)) {
481     abortMigration(QString("AbstractSqlMigrationReader::migrateTo(): unable to prepare reader query of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
482     return false;
483   }
484   if(!_writer->prepareQuery(moType)) {
485     abortMigration(QString("AbstractSqlMigrationReader::migrateTo(): unable to prepare writer query of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
486     return false;
487   }
488
489   qDebug() << qPrintable(QString("Transferring %1...").arg(AbstractSqlMigrator::migrationObject(moType)));
490   int i = 0;
491   QFile file;
492   file.open(stdout, QIODevice::WriteOnly);
493
494   while(readMo(mo)) {
495     if(!_writer->writeMo(mo)) {
496       abortMigration(QString("AbstractSqlMigrationReader::transferMo(): unable to transfer Migratable Object of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
497       return false;
498     }
499     i++;
500     if(i % 1000 == 0) {
501       file.write("*");
502       file.flush();
503     }
504   }
505   if(i > 1000) {
506     file.write("\n");
507     file.flush();
508   }
509
510   qDebug() << "Done.";
511   return true;
512 }
513