Yearly bump
[quassel.git] / src / core / postgresqlstorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "postgresqlstorage.h"
22
23 #include <QtSql>
24
25 #include "logger.h"
26 #include "network.h"
27 #include "quassel.h"
28
29 PostgreSqlStorage::PostgreSqlStorage(QObject *parent)
30     : AbstractSqlStorage(parent),
31     _port(-1)
32 {
33 }
34
35
36 PostgreSqlStorage::~PostgreSqlStorage()
37 {
38 }
39
40
41 AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter()
42 {
43     PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
44     QVariantMap properties;
45     properties["Username"] = _userName;
46     properties["Password"] = _password;
47     properties["Hostname"] = _hostName;
48     properties["Port"] = _port;
49     properties["Database"] = _databaseName;
50     writer->setConnectionProperties(properties);
51     return writer;
52 }
53
54
55 bool PostgreSqlStorage::isAvailable() const
56 {
57     qDebug() << QSqlDatabase::drivers();
58     if (!QSqlDatabase::isDriverAvailable("QPSQL")) return false;
59     return true;
60 }
61
62
63 QString PostgreSqlStorage::displayName() const
64 {
65     return QString("PostgreSQL");
66 }
67
68
69 QString PostgreSqlStorage::description() const
70 {
71     // FIXME: proper description
72     return tr("PostgreSQL Turbo Bomber HD!");
73 }
74
75
76 QStringList PostgreSqlStorage::setupKeys() const
77 {
78     QStringList keys;
79     keys << "Username"
80          << "Password"
81          << "Hostname"
82          << "Port"
83          << "Database";
84     return keys;
85 }
86
87
88 QVariantMap PostgreSqlStorage::setupDefaults() const
89 {
90     QVariantMap map;
91     map["Username"] = QVariant(QString("quassel"));
92     map["Hostname"] = QVariant(QString("localhost"));
93     map["Port"] = QVariant(5432);
94     map["Database"] = QVariant(QString("quassel"));
95     return map;
96 }
97
98
99 void PostgreSqlStorage::initDbSession(QSqlDatabase &db)
100 {
101     // this blows... but unfortunately Qt's PG driver forces us to this...
102     db.exec("set standard_conforming_strings = off");
103     db.exec("set escape_string_warning = off");
104 }
105
106
107 void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties)
108 {
109     _userName = properties["Username"].toString();
110     _password = properties["Password"].toString();
111     _hostName = properties["Hostname"].toString();
112     _port = properties["Port"].toInt();
113     _databaseName = properties["Database"].toString();
114 }
115
116
117 int PostgreSqlStorage::installedSchemaVersion()
118 {
119     QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
120     if (query.first())
121         return query.value(0).toInt();
122
123     // maybe it's really old... (schema version 0)
124     query = logDb().exec("SELECT MAX(version) FROM coreinfo");
125     if (query.first())
126         return query.value(0).toInt();
127
128     return AbstractSqlStorage::installedSchemaVersion();
129 }
130
131
132 bool PostgreSqlStorage::updateSchemaVersion(int newVersion)
133 {
134     QSqlQuery query(logDb());
135     query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'");
136     query.bindValue(":version", newVersion);
137     query.exec();
138
139     bool success = true;
140     if (query.lastError().isValid()) {
141         qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!";
142         success = false;
143     }
144     return success;
145 }
146
147
148 bool PostgreSqlStorage::setupSchemaVersion(int version)
149 {
150     QSqlQuery query(logDb());
151     query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaversion', :version)");
152     query.bindValue(":version", version);
153     query.exec();
154
155     bool success = true;
156     if (query.lastError().isValid()) {
157         qCritical() << "PostgreSqlStorage::setupSchemaVersion(int): Updating schema version failed!";
158         success = false;
159     }
160     return success;
161 }
162
163
164 UserId PostgreSqlStorage::addUser(const QString &user, const QString &password)
165 {
166     QSqlQuery query(logDb());
167     query.prepare(queryString("insert_quasseluser"));
168     query.bindValue(":username", user);
169     query.bindValue(":password", cryptedPassword(password));
170     safeExec(query);
171     if (!watchQuery(query))
172         return 0;
173
174     query.first();
175     UserId uid = query.value(0).toInt();
176     emit userAdded(uid, user);
177     return uid;
178 }
179
180
181 bool PostgreSqlStorage::updateUser(UserId user, const QString &password)
182 {
183     QSqlQuery query(logDb());
184     query.prepare(queryString("update_userpassword"));
185     query.bindValue(":userid", user.toInt());
186     query.bindValue(":password", cryptedPassword(password));
187     safeExec(query);
188     return query.numRowsAffected() != 0;
189 }
190
191
192 void PostgreSqlStorage::renameUser(UserId user, const QString &newName)
193 {
194     QSqlQuery query(logDb());
195     query.prepare(queryString("update_username"));
196     query.bindValue(":userid", user.toInt());
197     query.bindValue(":username", newName);
198     safeExec(query);
199     emit userRenamed(user, newName);
200 }
201
202
203 UserId PostgreSqlStorage::validateUser(const QString &user, const QString &password)
204 {
205     QSqlQuery query(logDb());
206     query.prepare(queryString("select_authuser"));
207     query.bindValue(":username", user);
208     query.bindValue(":password", cryptedPassword(password));
209     safeExec(query);
210
211     if (query.first()) {
212         return query.value(0).toInt();
213     }
214     else {
215         return 0;
216     }
217 }
218
219
220 UserId PostgreSqlStorage::getUserId(const QString &user)
221 {
222     QSqlQuery query(logDb());
223     query.prepare(queryString("select_userid"));
224     query.bindValue(":username", user);
225     safeExec(query);
226
227     if (query.first()) {
228         return query.value(0).toInt();
229     }
230     else {
231         return 0;
232     }
233 }
234
235
236 UserId PostgreSqlStorage::internalUser()
237 {
238     QSqlQuery query(logDb());
239     query.prepare(queryString("select_internaluser"));
240     safeExec(query);
241
242     if (query.first()) {
243         return query.value(0).toInt();
244     }
245     else {
246         return 0;
247     }
248 }
249
250
251 void PostgreSqlStorage::delUser(UserId user)
252 {
253     QSqlDatabase db = logDb();
254     if (!db.transaction()) {
255         qWarning() << "PostgreSqlStorage::delUser(): cannot start transaction!";
256         return;
257     }
258
259     QSqlQuery query(db);
260     query.prepare(queryString("delete_quasseluser"));
261     query.bindValue(":userid", user.toInt());
262     safeExec(query);
263     if (!watchQuery(query)) {
264         db.rollback();
265         return;
266     }
267     else {
268         db.commit();
269         emit userRemoved(user);
270     }
271 }
272
273
274 void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName, const QVariant &data)
275 {
276     QByteArray rawData;
277     QDataStream out(&rawData, QIODevice::WriteOnly);
278     out.setVersion(QDataStream::Qt_4_2);
279     out << data;
280
281     QSqlDatabase db = logDb();
282     QSqlQuery query(db);
283     query.prepare(queryString("insert_user_setting"));
284     query.bindValue(":userid", userId.toInt());
285     query.bindValue(":settingname", settingName);
286     query.bindValue(":settingvalue", rawData);
287     safeExec(query);
288
289     if (query.lastError().isValid()) {
290         QSqlQuery updateQuery(db);
291         updateQuery.prepare(queryString("update_user_setting"));
292         updateQuery.bindValue(":userid", userId.toInt());
293         updateQuery.bindValue(":settingname", settingName);
294         updateQuery.bindValue(":settingvalue", rawData);
295         safeExec(updateQuery);
296     }
297 }
298
299
300 QVariant PostgreSqlStorage::getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData)
301 {
302     QSqlQuery query(logDb());
303     query.prepare(queryString("select_user_setting"));
304     query.bindValue(":userid", userId.toInt());
305     query.bindValue(":settingname", settingName);
306     safeExec(query);
307
308     if (query.first()) {
309         QVariant data;
310         QByteArray rawData = query.value(0).toByteArray();
311         QDataStream in(&rawData, QIODevice::ReadOnly);
312         in.setVersion(QDataStream::Qt_4_2);
313         in >> data;
314         return data;
315     }
316     else {
317         return defaultData;
318     }
319 }
320
321
322 IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity)
323 {
324     IdentityId identityId;
325
326     QSqlDatabase db = logDb();
327     if (!db.transaction()) {
328         qWarning() << "PostgreSqlStorage::createIdentity(): Unable to start Transaction!";
329         qWarning() << " -" << qPrintable(db.lastError().text());
330         return identityId;
331     }
332
333     QSqlQuery query(db);
334     query.prepare(queryString("insert_identity"));
335     query.bindValue(":userid", user.toInt());
336     query.bindValue(":identityname", identity.identityName());
337     query.bindValue(":realname", identity.realName());
338     query.bindValue(":awaynick", identity.awayNick());
339     query.bindValue(":awaynickenabled", identity.awayNickEnabled());
340     query.bindValue(":awayreason", identity.awayReason());
341     query.bindValue(":awayreasonenabled", identity.awayReasonEnabled());
342     query.bindValue(":autoawayenabled", identity.awayReasonEnabled());
343     query.bindValue(":autoawaytime", identity.autoAwayTime());
344     query.bindValue(":autoawayreason", identity.autoAwayReason());
345     query.bindValue(":autoawayreasonenabled", identity.autoAwayReasonEnabled());
346     query.bindValue(":detachawayenabled", identity.detachAwayEnabled());
347     query.bindValue(":detachawayreason", identity.detachAwayReason());
348     query.bindValue(":detachawayreasonenabled", identity.detachAwayReasonEnabled());
349     query.bindValue(":ident", identity.ident());
350     query.bindValue(":kickreason", identity.kickReason());
351     query.bindValue(":partreason", identity.partReason());
352     query.bindValue(":quitreason", identity.quitReason());
353 #ifdef HAVE_SSL
354     query.bindValue(":sslcert", identity.sslCert().toPem());
355     query.bindValue(":sslkey", identity.sslKey().toPem());
356 #else
357     query.bindValue(":sslcert", QByteArray());
358     query.bindValue(":sslkey", QByteArray());
359 #endif
360     safeExec(query);
361     if (query.lastError().isValid()) {
362         watchQuery(query);
363         db.rollback();
364         return IdentityId();
365     }
366
367     query.first();
368     identityId = query.value(0).toInt();
369     identity.setId(identityId);
370
371     if (!identityId.isValid()) {
372         watchQuery(query);
373         db.rollback();
374         return IdentityId();
375     }
376
377     QSqlQuery insertNickQuery(db);
378     insertNickQuery.prepare(queryString("insert_nick"));
379     foreach(QString nick, identity.nicks()) {
380         insertNickQuery.bindValue(":identityid", identityId.toInt());
381         insertNickQuery.bindValue(":nick", nick);
382         safeExec(insertNickQuery);
383         if (!watchQuery(insertNickQuery)) {
384             db.rollback();
385             return IdentityId();
386         }
387     }
388
389     if (!db.commit()) {
390         qWarning() << "PostgreSqlStorage::createIdentity(): committing data failed!";
391         qWarning() << " -" << qPrintable(db.lastError().text());
392         return IdentityId();
393     }
394     return identityId;
395 }
396
397
398 bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity)
399 {
400     QSqlDatabase db = logDb();
401     if (!db.transaction()) {
402         qWarning() << "PostgreSqlStorage::updateIdentity(): Unable to start Transaction!";
403         qWarning() << " -" << qPrintable(db.lastError().text());
404         return false;
405     }
406
407     QSqlQuery checkQuery(db);
408     checkQuery.prepare(queryString("select_checkidentity"));
409     checkQuery.bindValue(":identityid", identity.id().toInt());
410     checkQuery.bindValue(":userid", user.toInt());
411     safeExec(checkQuery);
412
413     // there should be exactly one identity for the given id and user
414     if (!checkQuery.first() || checkQuery.value(0).toInt() != 1) {
415         db.rollback();
416         return false;
417     }
418
419     QSqlQuery query(db);
420     query.prepare(queryString("update_identity"));
421     query.bindValue(":identityname", identity.identityName());
422     query.bindValue(":realname", identity.realName());
423     query.bindValue(":awaynick", identity.awayNick());
424     query.bindValue(":awaynickenabled", identity.awayNickEnabled());
425     query.bindValue(":awayreason", identity.awayReason());
426     query.bindValue(":awayreasonenabled", identity.awayReasonEnabled());
427     query.bindValue(":autoawayenabled", identity.awayReasonEnabled());
428     query.bindValue(":autoawaytime", identity.autoAwayTime());
429     query.bindValue(":autoawayreason", identity.autoAwayReason());
430     query.bindValue(":autoawayreasonenabled", identity.autoAwayReasonEnabled());
431     query.bindValue(":detachawayenabled", identity.detachAwayEnabled());
432     query.bindValue(":detachawayreason", identity.detachAwayReason());
433     query.bindValue(":detachawayreasonenabled", identity.detachAwayReasonEnabled());
434     query.bindValue(":ident", identity.ident());
435     query.bindValue(":kickreason", identity.kickReason());
436     query.bindValue(":partreason", identity.partReason());
437     query.bindValue(":quitreason", identity.quitReason());
438 #ifdef HAVE_SSL
439     query.bindValue(":sslcert", identity.sslCert().toPem());
440     query.bindValue(":sslkey", identity.sslKey().toPem());
441 #else
442     query.bindValue(":sslcert", QByteArray());
443     query.bindValue(":sslkey", QByteArray());
444 #endif
445     query.bindValue(":identityid", identity.id().toInt());
446
447     safeExec(query);
448     if (!watchQuery(query)) {
449         db.rollback();
450         return false;
451     }
452
453     QSqlQuery deleteNickQuery(db);
454     deleteNickQuery.prepare(queryString("delete_nicks"));
455     deleteNickQuery.bindValue(":identityid", identity.id().toInt());
456     safeExec(deleteNickQuery);
457     if (!watchQuery(deleteNickQuery)) {
458         db.rollback();
459         return false;
460     }
461
462     QSqlQuery insertNickQuery(db);
463     insertNickQuery.prepare(queryString("insert_nick"));
464     foreach(QString nick, identity.nicks()) {
465         insertNickQuery.bindValue(":identityid", identity.id().toInt());
466         insertNickQuery.bindValue(":nick", nick);
467         safeExec(insertNickQuery);
468         if (!watchQuery(insertNickQuery)) {
469             db.rollback();
470             return false;
471         }
472     }
473
474     if (!db.commit()) {
475         qWarning() << "PostgreSqlStorage::updateIdentity(): committing data failed!";
476         qWarning() << " -" << qPrintable(db.lastError().text());
477         return false;
478     }
479     return true;
480 }
481
482
483 void PostgreSqlStorage::removeIdentity(UserId user, IdentityId identityId)
484 {
485     QSqlDatabase db = logDb();
486     if (!db.transaction()) {
487         qWarning() << "PostgreSqlStorage::removeIdentity(): Unable to start Transaction!";
488         qWarning() << " -" << qPrintable(db.lastError().text());
489         return;
490     }
491
492     QSqlQuery query(db);
493     query.prepare(queryString("delete_identity"));
494     query.bindValue(":identityid", identityId.toInt());
495     query.bindValue(":userid", user.toInt());
496     safeExec(query);
497     if (!watchQuery(query)) {
498         db.rollback();
499     }
500     else {
501         db.commit();
502     }
503 }
504
505
506 QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
507 {
508     QList<CoreIdentity> identities;
509
510     QSqlDatabase db = logDb();
511     if (!beginReadOnlyTransaction(db)) {
512         qWarning() << "PostgreSqlStorage::identites(): cannot start read only transaction!";
513         qWarning() << " -" << qPrintable(db.lastError().text());
514         return identities;
515     }
516
517     QSqlQuery query(db);
518     query.prepare(queryString("select_identities"));
519     query.bindValue(":userid", user.toInt());
520
521     QSqlQuery nickQuery(db);
522     nickQuery.prepare(queryString("select_nicks"));
523
524     safeExec(query);
525
526     while (query.next()) {
527         CoreIdentity identity(IdentityId(query.value(0).toInt()));
528
529         identity.setIdentityName(query.value(1).toString());
530         identity.setRealName(query.value(2).toString());
531         identity.setAwayNick(query.value(3).toString());
532         identity.setAwayNickEnabled(!!query.value(4).toInt());
533         identity.setAwayReason(query.value(5).toString());
534         identity.setAwayReasonEnabled(!!query.value(6).toInt());
535         identity.setAutoAwayEnabled(!!query.value(7).toInt());
536         identity.setAutoAwayTime(query.value(8).toInt());
537         identity.setAutoAwayReason(query.value(9).toString());
538         identity.setAutoAwayReasonEnabled(!!query.value(10).toInt());
539         identity.setDetachAwayEnabled(!!query.value(11).toInt());
540         identity.setDetachAwayReason(query.value(12).toString());
541         identity.setDetachAwayReasonEnabled(!!query.value(13).toInt());
542         identity.setIdent(query.value(14).toString());
543         identity.setKickReason(query.value(15).toString());
544         identity.setPartReason(query.value(16).toString());
545         identity.setQuitReason(query.value(17).toString());
546 #ifdef HAVE_SSL
547         identity.setSslCert(query.value(18).toByteArray());
548         identity.setSslKey(query.value(19).toByteArray());
549 #endif
550
551         nickQuery.bindValue(":identityid", identity.id().toInt());
552         QList<QString> nicks;
553         safeExec(nickQuery);
554         watchQuery(nickQuery);
555         while (nickQuery.next()) {
556             nicks << nickQuery.value(0).toString();
557         }
558         identity.setNicks(nicks);
559         identities << identity;
560     }
561     db.commit();
562     return identities;
563 }
564
565
566 NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info)
567 {
568     NetworkId networkId;
569
570     QSqlDatabase db = logDb();
571     if (!db.transaction()) {
572         qWarning() << "PostgreSqlStorage::createNetwork(): failed to begin transaction!";
573         qWarning() << " -" << qPrintable(db.lastError().text());
574         return false;
575     }
576
577     QSqlQuery query(db);
578     query.prepare(queryString("insert_network"));
579     query.bindValue(":userid", user.toInt());
580     bindNetworkInfo(query, info);
581     safeExec(query);
582     if (query.lastError().isValid()) {
583         watchQuery(query);
584         db.rollback();
585         return NetworkId();
586     }
587
588     query.first();
589     networkId = query.value(0).toInt();
590
591     if (!networkId.isValid()) {
592         watchQuery(query);
593         db.rollback();
594         return NetworkId();
595     }
596
597     QSqlQuery insertServersQuery(db);
598     insertServersQuery.prepare(queryString("insert_server"));
599     foreach(Network::Server server, info.serverList) {
600         insertServersQuery.bindValue(":userid", user.toInt());
601         insertServersQuery.bindValue(":networkid", networkId.toInt());
602         bindServerInfo(insertServersQuery, server);
603         safeExec(insertServersQuery);
604         if (!watchQuery(insertServersQuery)) {
605             db.rollback();
606             return NetworkId();
607         }
608     }
609
610     if (!db.commit()) {
611         qWarning() << "PostgreSqlStorage::createNetwork(): committing data failed!";
612         qWarning() << " -" << qPrintable(db.lastError().text());
613         return NetworkId();
614     }
615     return networkId;
616 }
617
618
619 void PostgreSqlStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &info)
620 {
621     query.bindValue(":networkname", info.networkName);
622     query.bindValue(":identityid", info.identity.isValid() ? info.identity.toInt() : QVariant());
623     query.bindValue(":encodingcodec", QString(info.codecForEncoding));
624     query.bindValue(":decodingcodec", QString(info.codecForDecoding));
625     query.bindValue(":servercodec", QString(info.codecForServer));
626     query.bindValue(":userandomserver", info.useRandomServer);
627     query.bindValue(":perform", info.perform.join("\n"));
628     query.bindValue(":useautoidentify", info.useAutoIdentify);
629     query.bindValue(":autoidentifyservice", info.autoIdentifyService);
630     query.bindValue(":autoidentifypassword", info.autoIdentifyPassword);
631     query.bindValue(":usesasl", info.useSasl);
632     query.bindValue(":saslaccount", info.saslAccount);
633     query.bindValue(":saslpassword", info.saslPassword);
634     query.bindValue(":useautoreconnect", info.useAutoReconnect);
635     query.bindValue(":autoreconnectinterval", info.autoReconnectInterval);
636     query.bindValue(":autoreconnectretries", info.autoReconnectRetries);
637     query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries);
638     query.bindValue(":rejoinchannels", info.rejoinChannels);
639     if (info.networkId.isValid())
640         query.bindValue(":networkid", info.networkId.toInt());
641 }
642
643
644 void PostgreSqlStorage::bindServerInfo(QSqlQuery &query, const Network::Server &server)
645 {
646     query.bindValue(":hostname", server.host);
647     query.bindValue(":port", server.port);
648     query.bindValue(":password", server.password);
649     query.bindValue(":ssl", server.useSsl);
650     query.bindValue(":sslversion", server.sslVersion);
651     query.bindValue(":useproxy", server.useProxy);
652     query.bindValue(":proxytype", server.proxyType);
653     query.bindValue(":proxyhost", server.proxyHost);
654     query.bindValue(":proxyport", server.proxyPort);
655     query.bindValue(":proxyuser", server.proxyUser);
656     query.bindValue(":proxypass", server.proxyPass);
657 }
658
659
660 bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info)
661 {
662     QSqlDatabase db = logDb();
663     if (!db.transaction()) {
664         qWarning() << "PostgreSqlStorage::updateNetwork(): failed to begin transaction!";
665         qWarning() << " -" << qPrintable(db.lastError().text());
666         return false;
667     }
668
669     QSqlQuery updateQuery(db);
670     updateQuery.prepare(queryString("update_network"));
671     updateQuery.bindValue(":userid", user.toInt());
672     bindNetworkInfo(updateQuery, info);
673     safeExec(updateQuery);
674     if (!watchQuery(updateQuery)) {
675         db.rollback();
676         return false;
677     }
678     if (updateQuery.numRowsAffected() != 1) {
679         // seems this is not our network...
680         db.rollback();
681         return false;
682     }
683
684     QSqlQuery dropServersQuery(db);
685     dropServersQuery.prepare("DELETE FROM ircserver WHERE networkid = :networkid");
686     dropServersQuery.bindValue(":networkid", info.networkId.toInt());
687     safeExec(dropServersQuery);
688     if (!watchQuery(dropServersQuery)) {
689         db.rollback();
690         return false;
691     }
692
693     QSqlQuery insertServersQuery(db);
694     insertServersQuery.prepare(queryString("insert_server"));
695     foreach(Network::Server server, info.serverList) {
696         insertServersQuery.bindValue(":userid", user.toInt());
697         insertServersQuery.bindValue(":networkid", info.networkId.toInt());
698         bindServerInfo(insertServersQuery, server);
699         safeExec(insertServersQuery);
700         if (!watchQuery(insertServersQuery)) {
701             db.rollback();
702             return false;
703         }
704     }
705
706     if (!db.commit()) {
707         qWarning() << "PostgreSqlStorage::updateNetwork(): committing data failed!";
708         qWarning() << " -" << qPrintable(db.lastError().text());
709         return false;
710     }
711     return true;
712 }
713
714
715 bool PostgreSqlStorage::removeNetwork(UserId user, const NetworkId &networkId)
716 {
717     QSqlDatabase db = logDb();
718     if (!db.transaction()) {
719         qWarning() << "PostgreSqlStorage::removeNetwork(): cannot start transaction!";
720         qWarning() << " -" << qPrintable(db.lastError().text());
721         return false;
722     }
723
724     QSqlQuery query(db);
725     query.prepare(queryString("delete_network"));
726     query.bindValue(":userid", user.toInt());
727     query.bindValue(":networkid", networkId.toInt());
728     safeExec(query);
729     if (!watchQuery(query)) {
730         db.rollback();
731         return false;
732     }
733
734     db.commit();
735     return true;
736 }
737
738
739 QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
740 {
741     QList<NetworkInfo> nets;
742
743     QSqlDatabase db = logDb();
744     if (!beginReadOnlyTransaction(db)) {
745         qWarning() << "PostgreSqlStorage::networks(): cannot start read only transaction!";
746         qWarning() << " -" << qPrintable(db.lastError().text());
747         return nets;
748     }
749
750     QSqlQuery networksQuery(db);
751     networksQuery.prepare(queryString("select_networks_for_user"));
752     networksQuery.bindValue(":userid", user.toInt());
753
754     QSqlQuery serversQuery(db);
755     serversQuery.prepare(queryString("select_servers_for_network"));
756
757     safeExec(networksQuery);
758     if (!watchQuery(networksQuery)) {
759         db.rollback();
760         return nets;
761     }
762
763     while (networksQuery.next()) {
764         NetworkInfo net;
765         net.networkId = networksQuery.value(0).toInt();
766         net.networkName = networksQuery.value(1).toString();
767         net.identity = networksQuery.value(2).toInt();
768         net.codecForServer = networksQuery.value(3).toString().toAscii();
769         net.codecForEncoding = networksQuery.value(4).toString().toAscii();
770         net.codecForDecoding = networksQuery.value(5).toString().toAscii();
771         net.useRandomServer = networksQuery.value(6).toBool();
772         net.perform = networksQuery.value(7).toString().split("\n");
773         net.useAutoIdentify = networksQuery.value(8).toBool();
774         net.autoIdentifyService = networksQuery.value(9).toString();
775         net.autoIdentifyPassword = networksQuery.value(10).toString();
776         net.useAutoReconnect = networksQuery.value(11).toBool();
777         net.autoReconnectInterval = networksQuery.value(12).toUInt();
778         net.autoReconnectRetries = networksQuery.value(13).toInt();
779         net.unlimitedReconnectRetries = networksQuery.value(14).toBool();
780         net.rejoinChannels = networksQuery.value(15).toBool();
781         net.useSasl = networksQuery.value(16).toBool();
782         net.saslAccount = networksQuery.value(17).toString();
783         net.saslPassword = networksQuery.value(18).toString();
784
785         serversQuery.bindValue(":networkid", net.networkId.toInt());
786         safeExec(serversQuery);
787         if (!watchQuery(serversQuery)) {
788             db.rollback();
789             return nets;
790         }
791
792         Network::ServerList servers;
793         while (serversQuery.next()) {
794             Network::Server server;
795             server.host = serversQuery.value(0).toString();
796             server.port = serversQuery.value(1).toUInt();
797             server.password = serversQuery.value(2).toString();
798             server.useSsl = serversQuery.value(3).toBool();
799             server.sslVersion = serversQuery.value(4).toInt();
800             server.useProxy = serversQuery.value(5).toBool();
801             server.proxyType = serversQuery.value(6).toInt();
802             server.proxyHost = serversQuery.value(7).toString();
803             server.proxyPort = serversQuery.value(8).toUInt();
804             server.proxyUser = serversQuery.value(9).toString();
805             server.proxyPass = serversQuery.value(10).toString();
806             servers << server;
807         }
808         net.serverList = servers;
809         nets << net;
810     }
811     db.commit();
812     return nets;
813 }
814
815
816 QList<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
817 {
818     QList<NetworkId> connectedNets;
819
820     QSqlDatabase db = logDb();
821     if (!beginReadOnlyTransaction(db)) {
822         qWarning() << "PostgreSqlStorage::connectedNetworks(): cannot start read only transaction!";
823         qWarning() << " -" << qPrintable(db.lastError().text());
824         return connectedNets;
825     }
826
827     QSqlQuery query(db);
828     query.prepare(queryString("select_connected_networks"));
829     query.bindValue(":userid", user.toInt());
830     safeExec(query);
831     watchQuery(query);
832
833     while (query.next()) {
834         connectedNets << query.value(0).toInt();
835     }
836
837     db.commit();
838     return connectedNets;
839 }
840
841
842 void PostgreSqlStorage::setNetworkConnected(UserId user, const NetworkId &networkId, bool isConnected)
843 {
844     QSqlQuery query(logDb());
845     query.prepare(queryString("update_network_connected"));
846     query.bindValue(":userid", user.toInt());
847     query.bindValue(":networkid", networkId.toInt());
848     query.bindValue(":connected", isConnected);
849     safeExec(query);
850     watchQuery(query);
851 }
852
853
854 QHash<QString, QString> PostgreSqlStorage::persistentChannels(UserId user, const NetworkId &networkId)
855 {
856     QHash<QString, QString> persistentChans;
857
858     QSqlDatabase db = logDb();
859     if (!beginReadOnlyTransaction(db)) {
860         qWarning() << "PostgreSqlStorage::persistentChannels(): cannot start read only transaction!";
861         qWarning() << " -" << qPrintable(db.lastError().text());
862         return persistentChans;
863     }
864
865     QSqlQuery query(db);
866     query.prepare(queryString("select_persistent_channels"));
867     query.bindValue(":userid", user.toInt());
868     query.bindValue(":networkid", networkId.toInt());
869     safeExec(query);
870     watchQuery(query);
871
872     while (query.next()) {
873         persistentChans[query.value(0).toString()] = query.value(1).toString();
874     }
875
876     db.commit();
877     return persistentChans;
878 }
879
880
881 void PostgreSqlStorage::setChannelPersistent(UserId user, const NetworkId &networkId, const QString &channel, bool isJoined)
882 {
883     QSqlQuery query(logDb());
884     query.prepare(queryString("update_buffer_persistent_channel"));
885     query.bindValue(":userid", user.toInt());
886     query.bindValue(":networkId", networkId.toInt());
887     query.bindValue(":buffercname", channel.toLower());
888     query.bindValue(":joined", isJoined);
889     safeExec(query);
890     watchQuery(query);
891 }
892
893
894 void PostgreSqlStorage::setPersistentChannelKey(UserId user, const NetworkId &networkId, const QString &channel, const QString &key)
895 {
896     QSqlQuery query(logDb());
897     query.prepare(queryString("update_buffer_set_channel_key"));
898     query.bindValue(":userid", user.toInt());
899     query.bindValue(":networkId", networkId.toInt());
900     query.bindValue(":buffercname", channel.toLower());
901     query.bindValue(":key", key);
902     safeExec(query);
903     watchQuery(query);
904 }
905
906
907 QString PostgreSqlStorage::awayMessage(UserId user, NetworkId networkId)
908 {
909     QSqlQuery query(logDb());
910     query.prepare(queryString("select_network_awaymsg"));
911     query.bindValue(":userid", user.toInt());
912     query.bindValue(":networkid", networkId.toInt());
913     safeExec(query);
914     watchQuery(query);
915     QString awayMsg;
916     if (query.first())
917         awayMsg = query.value(0).toString();
918     return awayMsg;
919 }
920
921
922 void PostgreSqlStorage::setAwayMessage(UserId user, NetworkId networkId, const QString &awayMsg)
923 {
924     QSqlQuery query(logDb());
925     query.prepare(queryString("update_network_set_awaymsg"));
926     query.bindValue(":userid", user.toInt());
927     query.bindValue(":networkid", networkId.toInt());
928     query.bindValue(":awaymsg", awayMsg);
929     safeExec(query);
930     watchQuery(query);
931 }
932
933
934 QString PostgreSqlStorage::userModes(UserId user, NetworkId networkId)
935 {
936     QSqlQuery query(logDb());
937     query.prepare(queryString("select_network_usermode"));
938     query.bindValue(":userid", user.toInt());
939     query.bindValue(":networkid", networkId.toInt());
940     safeExec(query);
941     watchQuery(query);
942     QString modes;
943     if (query.first())
944         modes = query.value(0).toString();
945     return modes;
946 }
947
948
949 void PostgreSqlStorage::setUserModes(UserId user, NetworkId networkId, const QString &userModes)
950 {
951     QSqlQuery query(logDb());
952     query.prepare(queryString("update_network_set_usermode"));
953     query.bindValue(":userid", user.toInt());
954     query.bindValue(":networkid", networkId.toInt());
955     query.bindValue(":usermode", userModes);
956     safeExec(query);
957     watchQuery(query);
958 }
959
960
961 BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create)
962 {
963     QSqlDatabase db = logDb();
964     if (!db.transaction()) {
965         qWarning() << "PostgreSqlStorage::bufferInfo(): cannot start read only transaction!";
966         qWarning() << " -" << qPrintable(db.lastError().text());
967         return BufferInfo();
968     }
969
970     QSqlQuery query(db);
971     query.prepare(queryString("select_bufferByName"));
972     query.bindValue(":networkid", networkId.toInt());
973     query.bindValue(":userid", user.toInt());
974     query.bindValue(":buffercname", buffer.toLower());
975     safeExec(query);
976
977     if (query.first()) {
978         BufferInfo bufferInfo = BufferInfo(query.value(0).toInt(), networkId, (BufferInfo::Type)query.value(1).toInt(), 0, buffer);
979         if (query.next()) {
980             qCritical() << "PostgreSqlStorage::bufferInfo(): received more then one Buffer!";
981             qCritical() << "         Query:" << query.lastQuery();
982             qCritical() << "  bound Values:";
983             QList<QVariant> list = query.boundValues().values();
984             for (int i = 0; i < list.size(); ++i)
985                 qCritical() << i << ":" << list.at(i).toString().toAscii().data();
986             Q_ASSERT(false);
987         }
988         db.commit();
989         return bufferInfo;
990     }
991
992     if (!create) {
993         db.rollback();
994         return BufferInfo();
995     }
996
997     QSqlQuery createQuery(db);
998     createQuery.prepare(queryString("insert_buffer"));
999     createQuery.bindValue(":userid", user.toInt());
1000     createQuery.bindValue(":networkid", networkId.toInt());
1001     createQuery.bindValue(":buffertype", (int)type);
1002     createQuery.bindValue(":buffername", buffer);
1003     createQuery.bindValue(":buffercname", buffer.toLower());
1004     createQuery.bindValue(":joined", type & BufferInfo::ChannelBuffer ? true : false);
1005
1006     safeExec(createQuery);
1007
1008     if (createQuery.lastError().isValid()) {
1009         qWarning() << "PostgreSqlStorage::bufferInfo(): unable to create buffer";
1010         watchQuery(createQuery);
1011         db.rollback();
1012         return BufferInfo();
1013     }
1014
1015     createQuery.first();
1016
1017     BufferInfo bufferInfo = BufferInfo(createQuery.value(0).toInt(), networkId, type, 0, buffer);
1018     db.commit();
1019     return bufferInfo;
1020 }
1021
1022
1023 BufferInfo PostgreSqlStorage::getBufferInfo(UserId user, const BufferId &bufferId)
1024 {
1025     QSqlQuery query(logDb());
1026     query.prepare(queryString("select_buffer_by_id"));
1027     query.bindValue(":userid", user.toInt());
1028     query.bindValue(":bufferid", bufferId.toInt());
1029     safeExec(query);
1030     if (!watchQuery(query))
1031         return BufferInfo();
1032
1033     if (!query.first())
1034         return BufferInfo();
1035
1036     BufferInfo bufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), 0, query.value(4).toString());
1037     Q_ASSERT(!query.next());
1038
1039     return bufferInfo;
1040 }
1041
1042
1043 QList<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
1044 {
1045     QList<BufferInfo> bufferlist;
1046
1047     QSqlDatabase db = logDb();
1048     if (!beginReadOnlyTransaction(db)) {
1049         qWarning() << "PostgreSqlStorage::requestBuffers(): cannot start read only transaction!";
1050         qWarning() << " -" << qPrintable(db.lastError().text());
1051         return bufferlist;
1052     }
1053
1054     QSqlQuery query(db);
1055     query.prepare(queryString("select_buffers"));
1056     query.bindValue(":userid", user.toInt());
1057
1058     safeExec(query);
1059     watchQuery(query);
1060     while (query.next()) {
1061         bufferlist << BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), query.value(3).toInt(), query.value(4).toString());
1062     }
1063     db.commit();
1064     return bufferlist;
1065 }
1066
1067
1068 QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
1069 {
1070     QList<BufferId> bufferList;
1071
1072     QSqlDatabase db = logDb();
1073     if (!beginReadOnlyTransaction(db)) {
1074         qWarning() << "PostgreSqlStorage::requestBufferIdsForNetwork(): cannot start read only transaction!";
1075         qWarning() << " -" << qPrintable(db.lastError().text());
1076         return bufferList;
1077     }
1078
1079     QSqlQuery query(db);
1080     query.prepare(queryString("select_buffers_for_network"));
1081     query.bindValue(":networkid", networkId.toInt());
1082     query.bindValue(":userid", user.toInt());
1083
1084     safeExec(query);
1085     watchQuery(query);
1086     while (query.next()) {
1087         bufferList << BufferId(query.value(0).toInt());
1088     }
1089     db.commit();
1090     return bufferList;
1091 }
1092
1093
1094 bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferId)
1095 {
1096     QSqlDatabase db = logDb();
1097     if (!db.transaction()) {
1098         qWarning() << "PostgreSqlStorage::removeBuffer(): cannot start transaction!";
1099         return false;
1100     }
1101
1102     QSqlQuery query(db);
1103     query.prepare(queryString("delete_buffer_for_bufferid"));
1104     query.bindValue(":userid", user.toInt());
1105     query.bindValue(":bufferid", bufferId.toInt());
1106     safeExec(query);
1107     if (!watchQuery(query)) {
1108         db.rollback();
1109         return false;
1110     }
1111
1112     int numRows = query.numRowsAffected();
1113     switch (numRows) {
1114     case 0:
1115         db.commit();
1116         return false;
1117     case 1:
1118         db.commit();
1119         return true;
1120     default:
1121         // there was more then one buffer deleted...
1122         qWarning() << "PostgreSqlStorage::removeBuffer(): Userid" << user << "BufferId" << "caused deletion of" << numRows << "Buffers! Rolling back transaction...";
1123         db.rollback();
1124         return false;
1125     }
1126 }
1127
1128
1129 bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferId, const QString &newName)
1130 {
1131     QSqlDatabase db = logDb();
1132     if (!db.transaction()) {
1133         qWarning() << "PostgreSqlStorage::renameBuffer(): cannot start transaction!";
1134         return false;
1135     }
1136
1137     QSqlQuery query(db);
1138     query.prepare(queryString("update_buffer_name"));
1139     query.bindValue(":buffername", newName);
1140     query.bindValue(":buffercname", newName.toLower());
1141     query.bindValue(":userid", user.toInt());
1142     query.bindValue(":bufferid", bufferId.toInt());
1143     safeExec(query);
1144     if (query.lastError().isValid()) {
1145         watchQuery(query);
1146         db.rollback();
1147         return false;
1148     }
1149
1150     int numRows = query.numRowsAffected();
1151     switch (numRows) {
1152     case 0:
1153         db.commit();
1154         return false;
1155     case 1:
1156         db.commit();
1157         return true;
1158     default:
1159         // there was more then one buffer deleted...
1160         qWarning() << "PostgreSqlStorage::renameBuffer(): Userid" << user << "BufferId" << "affected" << numRows << "Buffers! Rolling back transaction...";
1161         db.rollback();
1162         return false;
1163     }
1164 }
1165
1166
1167 bool PostgreSqlStorage::mergeBuffersPermanently(const UserId &user, const BufferId &bufferId1, const BufferId &bufferId2)
1168 {
1169     QSqlDatabase db = logDb();
1170     if (!db.transaction()) {
1171         qWarning() << "PostgreSqlStorage::mergeBuffersPermanently(): cannot start transaction!";
1172         qWarning() << " -" << qPrintable(db.lastError().text());
1173         return false;
1174     }
1175
1176     QSqlQuery checkQuery(db);
1177     checkQuery.prepare("SELECT count(*) FROM buffer "
1178                        "WHERE userid = :userid AND bufferid IN (:buffer1, :buffer2)");
1179     checkQuery.bindValue(":userid", user.toInt());
1180     checkQuery.bindValue(":buffer1", bufferId1.toInt());
1181     checkQuery.bindValue(":buffer2", bufferId2.toInt());
1182     safeExec(checkQuery);
1183     if (!watchQuery(checkQuery)) {
1184         db.rollback();
1185         return false;
1186     }
1187     checkQuery.first();
1188     if (checkQuery.value(0).toInt() != 2) {
1189         db.rollback();
1190         return false;
1191     }
1192
1193     QSqlQuery query(db);
1194     query.prepare(queryString("update_backlog_bufferid"));
1195     query.bindValue(":oldbufferid", bufferId2.toInt());
1196     query.bindValue(":newbufferid", bufferId1.toInt());
1197     safeExec(query);
1198     if (!watchQuery(query)) {
1199         db.rollback();
1200         return false;
1201     }
1202
1203     QSqlQuery delBufferQuery(logDb());
1204     delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
1205     delBufferQuery.bindValue(":userid", user.toInt());
1206     delBufferQuery.bindValue(":bufferid", bufferId2.toInt());
1207     safeExec(delBufferQuery);
1208     if (!watchQuery(delBufferQuery)) {
1209         db.rollback();
1210         return false;
1211     }
1212
1213     db.commit();
1214     return true;
1215 }
1216
1217
1218 void PostgreSqlStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, const MsgId &msgId)
1219 {
1220     QSqlQuery query(logDb());
1221     query.prepare(queryString("update_buffer_lastseen"));
1222
1223     query.bindValue(":userid", user.toInt());
1224     query.bindValue(":bufferid", bufferId.toInt());
1225     query.bindValue(":lastseenmsgid", msgId.toInt());
1226     safeExec(query);
1227     watchQuery(query);
1228 }
1229
1230
1231 QHash<BufferId, MsgId> PostgreSqlStorage::bufferLastSeenMsgIds(UserId user)
1232 {
1233     QHash<BufferId, MsgId> lastSeenHash;
1234
1235     QSqlDatabase db = logDb();
1236     if (!beginReadOnlyTransaction(db)) {
1237         qWarning() << "PostgreSqlStorage::bufferLastSeenMsgIds(): cannot start read only transaction!";
1238         qWarning() << " -" << qPrintable(db.lastError().text());
1239         return lastSeenHash;
1240     }
1241
1242     QSqlQuery query(db);
1243     query.prepare(queryString("select_buffer_lastseen_messages"));
1244     query.bindValue(":userid", user.toInt());
1245     safeExec(query);
1246     if (!watchQuery(query)) {
1247         db.rollback();
1248         return lastSeenHash;
1249     }
1250
1251     while (query.next()) {
1252         lastSeenHash[query.value(0).toInt()] = query.value(1).toInt();
1253     }
1254
1255     db.commit();
1256     return lastSeenHash;
1257 }
1258
1259
1260 void PostgreSqlStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId, const MsgId &msgId)
1261 {
1262     QSqlQuery query(logDb());
1263     query.prepare(queryString("update_buffer_markerlinemsgid"));
1264
1265     query.bindValue(":userid", user.toInt());
1266     query.bindValue(":bufferid", bufferId.toInt());
1267     query.bindValue(":markerlinemsgid", msgId.toInt());
1268     safeExec(query);
1269     watchQuery(query);
1270 }
1271
1272
1273 QHash<BufferId, MsgId> PostgreSqlStorage::bufferMarkerLineMsgIds(UserId user)
1274 {
1275     QHash<BufferId, MsgId> markerLineHash;
1276
1277     QSqlDatabase db = logDb();
1278     if (!beginReadOnlyTransaction(db)) {
1279         qWarning() << "PostgreSqlStorage::bufferMarkerLineMsgIds(): cannot start read only transaction!";
1280         qWarning() << " -" << qPrintable(db.lastError().text());
1281         return markerLineHash;
1282     }
1283
1284     QSqlQuery query(db);
1285     query.prepare(queryString("select_buffer_markerlinemsgids"));
1286     query.bindValue(":userid", user.toInt());
1287     safeExec(query);
1288     if (!watchQuery(query)) {
1289         db.rollback();
1290         return markerLineHash;
1291     }
1292
1293     while (query.next()) {
1294         markerLineHash[query.value(0).toInt()] = query.value(1).toInt();
1295     }
1296
1297     db.commit();
1298     return markerLineHash;
1299 }
1300
1301
1302 bool PostgreSqlStorage::logMessage(Message &msg)
1303 {
1304     QSqlDatabase db = logDb();
1305     if (!db.transaction()) {
1306         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
1307         qWarning() << " -" << qPrintable(db.lastError().text());
1308         return false;
1309     }
1310
1311     QSqlQuery getSenderIdQuery = executePreparedQuery("select_senderid", msg.sender(), db);
1312     int senderId;
1313     if (getSenderIdQuery.first()) {
1314         senderId = getSenderIdQuery.value(0).toInt();
1315     }
1316     else {
1317         // it's possible that the sender was already added by another thread
1318         // since the insert might fail we're setting a savepoint
1319         savePoint("sender_sp1", db);
1320         QSqlQuery addSenderQuery = executePreparedQuery("insert_sender", msg.sender(), db);
1321
1322         if (addSenderQuery.lastError().isValid()) {
1323             rollbackSavePoint("sender_sp1", db);
1324             getSenderIdQuery = db.exec(getSenderIdQuery.lastQuery());
1325             getSenderIdQuery.first();
1326             senderId = getSenderIdQuery.value(0).toInt();
1327         }
1328         else {
1329             releaseSavePoint("sender_sp1", db);
1330             addSenderQuery.first();
1331             senderId = addSenderQuery.value(0).toInt();
1332         }
1333     }
1334
1335     QVariantList params;
1336     params << msg.timestamp()
1337            << msg.bufferInfo().bufferId().toInt()
1338            << msg.type()
1339            << (int)msg.flags()
1340            << senderId
1341            << msg.contents();
1342     QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
1343
1344     if (!watchQuery(logMessageQuery)) {
1345         db.rollback();
1346         return false;
1347     }
1348
1349     logMessageQuery.first();
1350     MsgId msgId = logMessageQuery.value(0).toInt();
1351     db.commit();
1352     if (msgId.isValid()) {
1353         msg.setMsgId(msgId);
1354         return true;
1355     }
1356     else {
1357         return false;
1358     }
1359 }
1360
1361
1362 bool PostgreSqlStorage::logMessages(MessageList &msgs)
1363 {
1364     QSqlDatabase db = logDb();
1365     if (!db.transaction()) {
1366         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
1367         qWarning() << " -" << qPrintable(db.lastError().text());
1368         return false;
1369     }
1370
1371     QList<int> senderIdList;
1372     QHash<QString, int> senderIds;
1373     QSqlQuery addSenderQuery;
1374     QSqlQuery selectSenderQuery;;
1375     for (int i = 0; i < msgs.count(); i++) {
1376         const QString &sender = msgs.at(i).sender();
1377         if (senderIds.contains(sender)) {
1378             senderIdList << senderIds[sender];
1379             continue;
1380         }
1381
1382         selectSenderQuery = executePreparedQuery("select_senderid", sender, db);
1383         if (selectSenderQuery.first()) {
1384             senderIdList << selectSenderQuery.value(0).toInt();
1385             senderIds[sender] = selectSenderQuery.value(0).toInt();
1386         }
1387         else {
1388             savePoint("sender_sp", db);
1389             addSenderQuery = executePreparedQuery("insert_sender", sender, db);
1390             if (addSenderQuery.lastError().isValid()) {
1391                 // seems it was inserted meanwhile... by a different thread
1392                 rollbackSavePoint("sender_sp", db);
1393                 selectSenderQuery = db.exec(selectSenderQuery.lastQuery());
1394                 selectSenderQuery.first();
1395                 senderIdList << selectSenderQuery.value(0).toInt();
1396                 senderIds[sender] = selectSenderQuery.value(0).toInt();
1397             }
1398             else {
1399                 releaseSavePoint("sender_sp", db);
1400                 addSenderQuery.first();
1401                 senderIdList << addSenderQuery.value(0).toInt();
1402                 senderIds[sender] = addSenderQuery.value(0).toInt();
1403             }
1404         }
1405     }
1406
1407     // yes we loop twice over the same list. This avoids alternating queries.
1408     bool error = false;
1409     for (int i = 0; i < msgs.count(); i++) {
1410         Message &msg = msgs[i];
1411         QVariantList params;
1412         params << msg.timestamp()
1413                << msg.bufferInfo().bufferId().toInt()
1414                << msg.type()
1415                << (int)msg.flags()
1416                << senderIdList.at(i)
1417                << msg.contents();
1418         QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
1419         if (!watchQuery(logMessageQuery)) {
1420             db.rollback();
1421             error = true;
1422             break;
1423         }
1424         else {
1425             logMessageQuery.first();
1426             msg.setMsgId(logMessageQuery.value(0).toInt());
1427         }
1428     }
1429
1430     if (error) {
1431         // we had a rollback in the db so we need to reset all msgIds
1432         for (int i = 0; i < msgs.count(); i++) {
1433             msgs[i].setMsgId(MsgId());
1434         }
1435         return false;
1436     }
1437
1438     db.commit();
1439     return true;
1440 }
1441
1442
1443 QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
1444 {
1445     QList<Message> messagelist;
1446
1447     QSqlDatabase db = logDb();
1448     if (!beginReadOnlyTransaction(db)) {
1449         qWarning() << "PostgreSqlStorage::requestMsgs(): cannot start read only transaction!";
1450         qWarning() << " -" << qPrintable(db.lastError().text());
1451         return messagelist;
1452     }
1453
1454     BufferInfo bufferInfo = getBufferInfo(user, bufferId);
1455     if (!bufferInfo.isValid()) {
1456         db.rollback();
1457         return messagelist;
1458     }
1459
1460     QString queryName;
1461     QVariantList params;
1462     if (last == -1 && first == -1) {
1463         queryName = "select_messages";
1464     }
1465     else if (last == -1) {
1466         queryName = "select_messagesNewerThan";
1467         params << first.toInt();
1468     }
1469     else {
1470         queryName = "select_messagesRange";
1471         params << first.toInt();
1472         params << last.toInt();
1473     }
1474     params << bufferId.toInt();
1475     if (limit != -1)
1476         params << limit;
1477     else
1478         params << "ALL";
1479
1480     QSqlQuery query = executePreparedQuery(queryName, params, db);
1481
1482     if (!watchQuery(query)) {
1483         qDebug() << "select_messages failed";
1484         db.rollback();
1485         return messagelist;
1486     }
1487
1488     QDateTime timestamp;
1489     while (query.next()) {
1490         timestamp = query.value(1).toDateTime();
1491         timestamp.setTimeSpec(Qt::UTC);
1492         Message msg(timestamp,
1493             bufferInfo,
1494             (Message::Type)query.value(2).toUInt(),
1495             query.value(5).toString(),
1496             query.value(4).toString(),
1497             (Message::Flags)query.value(3).toUInt());
1498         msg.setMsgId(query.value(0).toInt());
1499         messagelist << msg;
1500     }
1501
1502     db.commit();
1503     return messagelist;
1504 }
1505
1506
1507 QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
1508 {
1509     QList<Message> messagelist;
1510
1511     // requestBuffers uses it's own transaction.
1512     QHash<BufferId, BufferInfo> bufferInfoHash;
1513     foreach(BufferInfo bufferInfo, requestBuffers(user)) {
1514         bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
1515     }
1516
1517     QSqlDatabase db = logDb();
1518     if (!beginReadOnlyTransaction(db)) {
1519         qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
1520         qWarning() << " -" << qPrintable(db.lastError().text());
1521         return messagelist;
1522     }
1523
1524     QSqlQuery query(db);
1525     if (last == -1) {
1526         query.prepare(queryString("select_messagesAllNew"));
1527     }
1528     else {
1529         query.prepare(queryString("select_messagesAll"));
1530         query.bindValue(":lastmsg", last.toInt());
1531     }
1532     query.bindValue(":userid", user.toInt());
1533     query.bindValue(":firstmsg", first.toInt());
1534     safeExec(query);
1535     if (!watchQuery(query)) {
1536         db.rollback();
1537         return messagelist;
1538     }
1539
1540     QDateTime timestamp;
1541     for (int i = 0; i < limit && query.next(); i++) {
1542         timestamp = query.value(1).toDateTime();
1543         timestamp.setTimeSpec(Qt::UTC);
1544         Message msg(timestamp,
1545             bufferInfoHash[query.value(1).toInt()],
1546             (Message::Type)query.value(3).toUInt(),
1547             query.value(6).toString(),
1548             query.value(5).toString(),
1549             (Message::Flags)query.value(4).toUInt());
1550         msg.setMsgId(query.value(0).toInt());
1551         messagelist << msg;
1552     }
1553
1554     db.commit();
1555     return messagelist;
1556 }
1557
1558
1559 // void PostgreSqlStorage::safeExec(QSqlQuery &query) {
1560 //   qDebug() << "PostgreSqlStorage::safeExec";
1561 //   qDebug() << "   executing:\n" << query.executedQuery();
1562 //   qDebug() << "   bound Values:";
1563 //   QList<QVariant> list = query.boundValues().values();
1564 //   for (int i = 0; i < list.size(); ++i)
1565 //     qCritical() << i << ": " << list.at(i).toString().toAscii().data();
1566
1567 //   query.exec();
1568
1569 //   qDebug() << "Success:" << !query.lastError().isValid();
1570 //   qDebug();
1571
1572 //   if(!query.lastError().isValid())
1573 //     return;
1574
1575 //   qDebug() << "==================== ERROR ====================";
1576 //   watchQuery(query);
1577 //   qDebug() << "===============================================";
1578 //   qDebug();
1579 //   return;
1580 // }
1581
1582 bool PostgreSqlStorage::beginReadOnlyTransaction(QSqlDatabase &db)
1583 {
1584     QSqlQuery query = db.exec("BEGIN TRANSACTION READ ONLY");
1585     return !query.lastError().isValid();
1586 }
1587
1588
1589 QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString &paramstring, const QSqlDatabase &db)
1590 {
1591     // Query preparing is done lazily. That means that instead of always checking if the query is already prepared
1592     // we just EXECUTE and catch the error
1593     QSqlQuery query;
1594
1595     db.exec("SAVEPOINT quassel_prepare_query");
1596     if (paramstring.isNull()) {
1597         query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
1598     }
1599     else {
1600         query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
1601     }
1602
1603     if (db.lastError().isValid()) {
1604         // and once again: Qt leaves us without error codes so we either parse (language dependant(!)) strings
1605         // or we just guess the error. As we're only interested in unprepared queries, this will be our guess. :)
1606         db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
1607         QSqlQuery checkQuery = db.exec(QString("SELECT count(name) FROM pg_prepared_statements WHERE name = 'quassel_%1' AND from_sql = TRUE").arg(queryname.toLower()));
1608         checkQuery.first();
1609         if (checkQuery.value(0).toInt() == 0) {
1610             db.exec(QString("PREPARE quassel_%1 AS %2").arg(queryname).arg(queryString(queryname)));
1611             if (db.lastError().isValid()) {
1612                 qWarning() << "PostgreSqlStorage::prepareQuery(): unable to prepare query:" << queryname << "AS" << queryString(queryname);
1613                 qWarning() << "  Error:" << db.lastError().text();
1614                 return QSqlQuery(db);
1615             }
1616         }
1617         // we alwas execute the query again, even if the query was already prepared.
1618         // this ensures, that the error is properly propagated to the calling function
1619         // (otherwise the last call would be the testing select to pg_prepared_statements
1620         // which always gives a proper result and the error would be lost)
1621         if (paramstring.isNull()) {
1622             query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
1623         }
1624         else {
1625             query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
1626         }
1627     }
1628     else {
1629         // only release the SAVEPOINT
1630         db.exec("RELEASE SAVEPOINT quassel_prepare_query");
1631     }
1632     return query;
1633 }
1634
1635
1636 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList &params, const QSqlDatabase &db)
1637 {
1638     QSqlDriver *driver = db.driver();
1639
1640     QStringList paramStrings;
1641     QSqlField field;
1642     for (int i = 0; i < params.count(); i++) {
1643         const QVariant &value = params.at(i);
1644         field.setType(value.type());
1645         if (value.isNull())
1646             field.clear();
1647         else
1648             field.setValue(value);
1649
1650         paramStrings << driver->formatValue(field);
1651     }
1652
1653     if (params.isEmpty()) {
1654         return prepareAndExecuteQuery(queryname, db);
1655     }
1656     else {
1657         return prepareAndExecuteQuery(queryname, paramStrings.join(", "), db);
1658     }
1659 }
1660
1661
1662 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant &param, const QSqlDatabase &db)
1663 {
1664     QSqlField field;
1665     field.setType(param.type());
1666     if (param.isNull())
1667         field.clear();
1668     else
1669         field.setValue(param);
1670
1671     QString paramString = db.driver()->formatValue(field);
1672     return prepareAndExecuteQuery(queryname, paramString, db);
1673 }
1674
1675
1676 void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlDatabase &db)
1677 {
1678     db.exec(QString("DEALLOCATE quassel_%1").arg(queryname));
1679 }
1680
1681
1682 // ========================================
1683 //  PostgreSqlMigrationWriter
1684 // ========================================
1685 PostgreSqlMigrationWriter::PostgreSqlMigrationWriter()
1686     : PostgreSqlStorage()
1687 {
1688 }
1689
1690
1691 bool PostgreSqlMigrationWriter::prepareQuery(MigrationObject mo)
1692 {
1693     QString query;
1694     switch (mo) {
1695     case QuasselUser:
1696         query = queryString("migrate_write_quasseluser");
1697         break;
1698     case Sender:
1699         query = queryString("migrate_write_sender");
1700         break;
1701     case Identity:
1702         _validIdentities.clear();
1703         query = queryString("migrate_write_identity");
1704         break;
1705     case IdentityNick:
1706         query = queryString("migrate_write_identity_nick");
1707         break;
1708     case Network:
1709         query = queryString("migrate_write_network");
1710         break;
1711     case Buffer:
1712         query = queryString("migrate_write_buffer");
1713         break;
1714     case Backlog:
1715         query = queryString("migrate_write_backlog");
1716         break;
1717     case IrcServer:
1718         query = queryString("migrate_write_ircserver");
1719         break;
1720     case UserSetting:
1721         query = queryString("migrate_write_usersetting");
1722         break;
1723     }
1724     newQuery(query, logDb());
1725     return true;
1726 }
1727
1728
1729 //bool PostgreSqlMigrationWriter::writeUser(const QuasselUserMO &user) {
1730 bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user)
1731 {
1732     bindValue(0, user.id.toInt());
1733     bindValue(1, user.username);
1734     bindValue(2, user.password);
1735     return exec();
1736 }
1737
1738
1739 //bool PostgreSqlMigrationWriter::writeSender(const SenderMO &sender) {
1740 bool PostgreSqlMigrationWriter::writeMo(const SenderMO &sender)
1741 {
1742     bindValue(0, sender.senderId);
1743     bindValue(1, sender.sender);
1744     return exec();
1745 }
1746
1747
1748 //bool PostgreSqlMigrationWriter::writeIdentity(const IdentityMO &identity) {
1749 bool PostgreSqlMigrationWriter::writeMo(const IdentityMO &identity)
1750 {
1751     _validIdentities << identity.id.toInt();
1752     bindValue(0, identity.id.toInt());
1753     bindValue(1, identity.userid.toInt());
1754     bindValue(2, identity.identityname);
1755     bindValue(3, identity.realname);
1756     bindValue(4, identity.awayNick);
1757     bindValue(5, identity.awayNickEnabled);
1758     bindValue(6, identity.awayReason);
1759     bindValue(7, identity.awayReasonEnabled);
1760     bindValue(8, identity.autoAwayEnabled);
1761     bindValue(9, identity.autoAwayTime);
1762     bindValue(10, identity.autoAwayReason);
1763     bindValue(11, identity.autoAwayReasonEnabled);
1764     bindValue(12, identity.detachAwayEnabled);
1765     bindValue(13, identity.detachAwayReason);
1766     bindValue(14, identity.detchAwayReasonEnabled);
1767     bindValue(15, identity.ident);
1768     bindValue(16, identity.kickReason);
1769     bindValue(17, identity.partReason);
1770     bindValue(18, identity.quitReason);
1771     bindValue(19, identity.sslCert);
1772     bindValue(20, identity.sslKey);
1773     return exec();
1774 }
1775
1776
1777 //bool PostgreSqlMigrationWriter::writeIdentityNick(const IdentityNickMO &identityNick) {
1778 bool PostgreSqlMigrationWriter::writeMo(const IdentityNickMO &identityNick)
1779 {
1780     bindValue(0, identityNick.nickid);
1781     bindValue(1, identityNick.identityId.toInt());
1782     bindValue(2, identityNick.nick);
1783     return exec();
1784 }
1785
1786
1787 //bool PostgreSqlMigrationWriter::writeNetwork(const NetworkMO &network) {
1788 bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network)
1789 {
1790     bindValue(0, network.networkid.toInt());
1791     bindValue(1, network.userid.toInt());
1792     bindValue(2, network.networkname);
1793     if (_validIdentities.contains(network.identityid.toInt()))
1794         bindValue(3, network.identityid.toInt());
1795     else
1796         bindValue(3, QVariant());
1797     bindValue(4, network.encodingcodec);
1798     bindValue(5, network.decodingcodec);
1799     bindValue(6, network.servercodec);
1800     bindValue(7, network.userandomserver);
1801     bindValue(8, network.perform);
1802     bindValue(9, network.useautoidentify);
1803     bindValue(10, network.autoidentifyservice);
1804     bindValue(11, network.autoidentifypassword);
1805     bindValue(12, network.useautoreconnect);
1806     bindValue(13, network.autoreconnectinterval);
1807     bindValue(14, network.autoreconnectretries);
1808     bindValue(15, network.unlimitedconnectretries);
1809     bindValue(16, network.rejoinchannels);
1810     bindValue(17, network.connected);
1811     bindValue(18, network.usermode);
1812     bindValue(19, network.awaymessage);
1813     bindValue(20, network.attachperform);
1814     bindValue(21, network.detachperform);
1815     bindValue(22, network.usesasl);
1816     bindValue(23, network.saslaccount);
1817     bindValue(24, network.saslpassword);
1818     return exec();
1819 }
1820
1821
1822 //bool PostgreSqlMigrationWriter::writeBuffer(const BufferMO &buffer) {
1823 bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
1824 {
1825     bindValue(0, buffer.bufferid.toInt());
1826     bindValue(1, buffer.userid.toInt());
1827     bindValue(2, buffer.groupid);
1828     bindValue(3, buffer.networkid.toInt());
1829     bindValue(4, buffer.buffername);
1830     bindValue(5, buffer.buffercname);
1831     bindValue(6, (int)buffer.buffertype);
1832     bindValue(7, buffer.lastseenmsgid);
1833     bindValue(8, buffer.markerlinemsgid);
1834     bindValue(9, buffer.key);
1835     bindValue(10, buffer.joined);
1836     return exec();
1837 }
1838
1839
1840 //bool PostgreSqlMigrationWriter::writeBacklog(const BacklogMO &backlog) {
1841 bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog)
1842 {
1843     bindValue(0, backlog.messageid.toInt());
1844     bindValue(1, backlog.time);
1845     bindValue(2, backlog.bufferid.toInt());
1846     bindValue(3, backlog.type);
1847     bindValue(4, (int)backlog.flags);
1848     bindValue(5, backlog.senderid);
1849     bindValue(6, backlog.message);
1850     return exec();
1851 }
1852
1853
1854 //bool PostgreSqlMigrationWriter::writeIrcServer(const IrcServerMO &ircserver) {
1855 bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)
1856 {
1857     bindValue(0, ircserver.serverid);
1858     bindValue(1, ircserver.userid.toInt());
1859     bindValue(2, ircserver.networkid.toInt());
1860     bindValue(3, ircserver.hostname);
1861     bindValue(4, ircserver.port);
1862     bindValue(5, ircserver.password);
1863     bindValue(6, ircserver.ssl);
1864     bindValue(7, ircserver.sslversion);
1865     bindValue(8, ircserver.useproxy);
1866     bindValue(9, ircserver.proxytype);
1867     bindValue(10, ircserver.proxyhost);
1868     bindValue(11, ircserver.proxyport);
1869     bindValue(12, ircserver.proxyuser);
1870     bindValue(13, ircserver.proxypass);
1871     return exec();
1872 }
1873
1874
1875 //bool PostgreSqlMigrationWriter::writeUserSetting(const UserSettingMO &userSetting) {
1876 bool PostgreSqlMigrationWriter::writeMo(const UserSettingMO &userSetting)
1877 {
1878     bindValue(0, userSetting.userid.toInt());
1879     bindValue(1, userSetting.settingname);
1880     bindValue(2, userSetting.settingvalue);
1881     return exec();
1882 }
1883
1884
1885 bool PostgreSqlMigrationWriter::postProcess()
1886 {
1887     QSqlDatabase db = logDb();
1888     QList<Sequence> sequences;
1889     sequences << Sequence("backlog", "messageid")
1890               << Sequence("buffer", "bufferid")
1891               << Sequence("identity", "identityid")
1892               << Sequence("identity_nick", "nickid")
1893               << Sequence("ircserver", "serverid")
1894               << Sequence("network", "networkid")
1895               << Sequence("quasseluser", "userid")
1896               << Sequence("sender", "senderid");
1897     QList<Sequence>::const_iterator iter;
1898     for (iter = sequences.constBegin(); iter != sequences.constEnd(); iter++) {
1899         resetQuery();
1900         newQuery(QString("SELECT setval('%1_%2_seq', max(%2)) FROM %1").arg(iter->table, iter->field), db);
1901         if (!exec())
1902             return false;
1903     }
1904     return true;
1905 }