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