Merge branch 'seezer'
[quassel.git] / src / core / abstractsqlstorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel IRC Team                         *
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 <QSqlError>
24 #include <QSqlQuery>
25
26 AbstractSqlStorage::AbstractSqlStorage(QObject *parent)
27   : Storage(parent),
28     _schemaVersion(0)
29 {
30 }
31
32 AbstractSqlStorage::~AbstractSqlStorage() {
33   QHash<QPair<QString, int>, QSqlQuery *>::iterator iter = _queryCache.begin();
34   while(iter != _queryCache.end()) {
35     delete *iter;
36     iter = _queryCache.erase(iter);
37   }
38   
39   {
40     QSqlDatabase db = QSqlDatabase::database("quassel_connection");
41     db.commit();
42     db.close();
43   }
44   QSqlDatabase::removeDatabase("quassel_connection");  
45 }
46
47 QSqlDatabase AbstractSqlStorage::logDb() {
48   QSqlDatabase db = QSqlDatabase::database("quassel_connection");
49   if(db.isValid() && db.isOpen())
50     return db;
51
52   if(!openDb()) {
53     qWarning() << "Unable to Open Database" << displayName();
54     qWarning() << " -" << db.lastError().text();
55   }
56
57   return QSqlDatabase::database("quassel_connection");
58 }
59
60 bool AbstractSqlStorage::openDb() {
61   QSqlDatabase db = QSqlDatabase::database("quassel_connection");
62   if(db.isValid() && !db.isOpen())
63     return db.open();
64
65   db = QSqlDatabase::addDatabase(driverName(), "quassel_connection");
66   db.setDatabaseName(databaseName());
67
68   if(!hostName().isEmpty())
69     db.setHostName(hostName());
70
71   if(!userName().isEmpty()) {
72     db.setUserName(userName());
73     db.setPassword(password());
74   }
75
76   return db.open();
77 }
78
79 bool AbstractSqlStorage::init(const QVariantMap &settings) {
80   Q_UNUSED(settings)
81   QSqlDatabase db = logDb();
82   if(!db.isValid() || !db.isOpen())
83     return false;
84
85   if(installedSchemaVersion() == -1) {
86     qDebug() << "Storage Schema is missing!";
87     return false;
88   }
89
90   if(installedSchemaVersion() > schemaVersion()) {
91     qWarning() << "Installed Schema is newer then any known Version.";
92     return false;
93   }
94   
95   if(installedSchemaVersion() < schemaVersion()) {
96     qWarning() << "Installed Schema is not up to date. Upgrading...";
97     if(!upgradeDb())
98       return false;
99   }
100   
101   qDebug() << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
102   return true;
103 }
104
105 void AbstractSqlStorage::sync() {
106   QHash<QPair<QString, int>, QSqlQuery *>::iterator iter = _queryCache.begin();
107   while(iter != _queryCache.end()) {
108     delete *iter;
109     iter = _queryCache.erase(iter);
110   }
111
112   logDb().commit();
113 }
114
115 QString AbstractSqlStorage::queryString(const QString &queryName, int version) {
116   if(version == 0)
117     version = schemaVersion();
118     
119   QFileInfo queryInfo(QString(":/SQL/%1/%2/%3.sql").arg(displayName()).arg(version).arg(queryName));
120   if(!queryInfo.exists() || !queryInfo.isFile() || !queryInfo.isReadable()) {
121     qWarning() << "Unable to read SQL-Query" << queryName << "for engine" << displayName();
122     return QString();
123   }
124
125   QFile queryFile(queryInfo.filePath());
126   if(!queryFile.open(QIODevice::ReadOnly | QIODevice::Text))
127     return QString();
128   QString query = QTextStream(&queryFile).readAll();
129   queryFile.close();
130   
131   return query.trimmed();
132 }
133
134 QString AbstractSqlStorage::queryString(const QString &queryName) {
135   return queryString(queryName, 0);
136 }
137
138 QSqlQuery *AbstractSqlStorage::cachedQuery(const QString &queryName, int version) {
139   QPair<QString, int> queryId = qMakePair(queryName, version);
140   if(!_queryCache.contains(queryId)) {
141     QSqlQuery *query = new QSqlQuery(logDb());
142     query->prepare(queryString(queryName, version));
143     _queryCache[queryId] = query;
144   }
145   return _queryCache[queryId];
146 }
147
148 QSqlQuery *AbstractSqlStorage::cachedQuery(const QString &queryName) {
149   return cachedQuery(queryName, 0);
150 }
151
152 QStringList AbstractSqlStorage::setupQueries() {
153   QStringList queries;
154   QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(schemaVersion()));
155   foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "setup*", QDir::NoFilter, QDir::Name)) {
156     queries << queryString(fileInfo.baseName());
157   }
158   return queries;
159 }
160
161 bool AbstractSqlStorage::setup(const QVariantMap &settings) {
162   Q_UNUSED(settings)
163   QSqlDatabase db = logDb();
164   if(!db.isOpen()) {
165     qWarning() << "Unable to setup Logging Backend!";
166     return false;
167   }
168
169   foreach(QString queryString, setupQueries()) {
170     QSqlQuery query = db.exec(queryString);
171     if(!watchQuery(&query)) {
172       qWarning() << "Unable to setup Logging Backend!";
173       return false;
174     }
175   }
176   return true;
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         qWarning() << "Unable to upgrade Logging Backend!";
199         return false;
200       }
201     }
202   }
203   return true;
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   if(query->lastError().isValid()) {
232     qWarning() << "unhandled Error in QSqlQuery!";
233     qWarning() << "                  last Query:\n" << query->lastQuery();
234     qWarning() << "              executed Query:\n" << query->executedQuery();
235     qWarning() << "                bound Values:"   << query->boundValues();
236     qWarning() << "                Error Number:"   << query->lastError().number();
237     qWarning() << "               Error Message:"   << query->lastError().text();
238     qWarning() << "              Driver Message:"   << query->lastError().driverText();
239     qWarning() << "                  DB Message:"   << query->lastError().databaseText();
240     
241     return false;
242   }
243   return true;
244 }