modernize: Prefer default member init over ctor init
[quassel.git] / src / core / postgresqlstorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 "logmessage.h"
26 #include "network.h"
27 #include "quassel.h"
28
29 PostgreSqlStorage::PostgreSqlStorage(QObject *parent)
30     : AbstractSqlStorage(parent)
31 {
32 }
33
34
35 PostgreSqlStorage::~PostgreSqlStorage()
36 {
37 }
38
39
40 std::unique_ptr<AbstractSqlMigrationWriter> PostgreSqlStorage::createMigrationWriter()
41 {
42     auto writer = new PostgreSqlMigrationWriter();
43     QVariantMap properties;
44     properties["Username"] = _userName;
45     properties["Password"] = _password;
46     properties["Hostname"] = _hostName;
47     properties["Port"] = _port;
48     properties["Database"] = _databaseName;
49     writer->setConnectionProperties(properties, {}, false);
50     return std::unique_ptr<AbstractSqlMigrationWriter>{writer};
51 }
52
53
54 bool PostgreSqlStorage::isAvailable() const
55 {
56     if (!QSqlDatabase::isDriverAvailable("QPSQL")) {
57         quWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:"))
58                     << qPrintable(QSqlDatabase::drivers().join(", "));
59         return false;
60     }
61     return true;
62 }
63
64
65 QString PostgreSqlStorage::backendId() const
66 {
67     return QString("PostgreSQL");
68 }
69
70
71 QString PostgreSqlStorage::displayName() const
72 {
73     return backendId(); // Note: Pre-0.13 clients use the displayName property for backend idenfication
74 }
75
76
77 QString PostgreSqlStorage::description() const
78 {
79     // FIXME: proper description
80     return tr("PostgreSQL Turbo Bomber HD!");
81 }
82
83
84 QVariantList PostgreSqlStorage::setupData() const
85 {
86     QVariantList data;
87     data << "Username" << tr("Username") << QString("quassel")
88          << "Password" << tr("Password") << QString()
89          << "Hostname" << tr("Hostname") << QString("localhost")
90          << "Port"     << tr("Port")     << 5432
91          << "Database" << tr("Database") << QString("quassel")
92          ;
93     return data;
94 }
95
96
97 bool PostgreSqlStorage::initDbSession(QSqlDatabase &db)
98 {
99     // check whether the Qt driver performs string escaping or not.
100     // i.e. test if it doubles slashes.
101     QSqlField testField;
102     testField.setType(QVariant::String);
103     testField.setValue("\\");
104     QString formattedString = db.driver()->formatValue(testField);
105     switch(formattedString.count('\\')) {
106     case 2:
107         // yes it does... and we cannot do anything to change the behavior of Qt.
108         // If this is a legacy DB (Postgres < 8.2), then everything is already ok,
109         // as this is the expected behavior.
110         // If it is a newer version, switch to legacy mode.
111
112         quWarning() << "Switching Postgres to legacy mode. (set standard conforming strings to off)";
113         // If the following calls fail, it is a legacy DB anyways, so it doesn't matter
114         // and no need to check the outcome.
115         db.exec("set standard_conforming_strings = off");
116         db.exec("set escape_string_warning = off");
117         break;
118     case 1:
119         // ok, so Qt does not escape...
120         // That means we have to ensure that postgres uses standard conforming strings...
121         {
122             QSqlQuery query = db.exec("set standard_conforming_strings = on");
123             if (query.lastError().isValid()) {
124                 // We cannot enable standard conforming strings...
125                 // since Quassel does no escaping by itself, this would yield a major vulnerability.
126                 quError() << "Failed to enable standard_conforming_strings for the Postgres db!";
127                 return false;
128             }
129         }
130         break;
131     default:
132         // The slash got replaced with 0 or more than 2 slashes! o_O
133         quError() << "Your version of Qt does something _VERY_ strange to slashes in QSqlQueries! You should consult your trusted doctor!";
134         return false;
135         break;
136     }
137
138     // Set the PostgreSQL session timezone to UTC, since we want timestamps stored in UTC
139     QSqlQuery tzQuery = db.exec("SET timezone = 'UTC'");
140     if (tzQuery.lastError().isValid()) {
141         quError() << "Failed to set timezone to UTC!";
142         return false;
143     }
144
145     return true;
146 }
147
148
149 void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties,
150                                                 const QProcessEnvironment &environment,
151                                                 bool loadFromEnvironment)
152 {
153     if (loadFromEnvironment) {
154         _userName = environment.value("DB_PGSQL_USERNAME");
155         _password = environment.value("DB_PGSQL_PASSWORD");
156         _hostName = environment.value("DB_PGSQL_HOSTNAME");
157         _port = environment.value("DB_PGSQL_PORT").toInt();
158         _databaseName = environment.value("DB_PGSQL_DATABASE");
159     } else {
160         _userName = properties["Username"].toString();
161         _password = properties["Password"].toString();
162         _hostName = properties["Hostname"].toString();
163         _port = properties["Port"].toInt();
164         _databaseName = properties["Database"].toString();
165     }
166 }
167
168
169 int PostgreSqlStorage::installedSchemaVersion()
170 {
171     QSqlQuery query(logDb());
172     query.prepare("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
173     safeExec(query);
174     watchQuery(query);
175     if (query.first())
176         return query.value(0).toInt();
177
178     // maybe it's really old... (schema version 0)
179     query.prepare("SELECT MAX(version) FROM coreinfo");
180     safeExec(query);
181     watchQuery(query);
182     if (query.first())
183         return query.value(0).toInt();
184
185     return AbstractSqlStorage::installedSchemaVersion();
186 }
187
188
189 bool PostgreSqlStorage::updateSchemaVersion(int newVersion)
190 {
191     QSqlQuery query(logDb());
192     query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'");
193     query.bindValue(":version", newVersion);
194     safeExec(query);
195
196     bool success = true;
197     if (!watchQuery(query)) {
198         qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!";
199         success = false;
200     }
201     return success;
202 }
203
204
205 bool PostgreSqlStorage::setupSchemaVersion(int version)
206 {
207     QSqlQuery query(logDb());
208     query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaversion', :version)");
209     query.bindValue(":version", version);
210     safeExec(query);
211
212     bool success = true;
213     if (!watchQuery(query)) {
214         qCritical() << "PostgreSqlStorage::setupSchemaVersion(int): Updating schema version failed!";
215         success = false;
216     }
217     return success;
218 }
219
220
221 UserId PostgreSqlStorage::addUser(const QString &user, const QString &password, const QString &authenticator)
222 {
223     QSqlQuery query(logDb());
224     query.prepare(queryString("insert_quasseluser"));
225     query.bindValue(":username", user);
226     query.bindValue(":password", hashPassword(password));
227     query.bindValue(":hashversion", Storage::HashVersion::Latest);
228     query.bindValue(":authenticator", authenticator);
229     safeExec(query);
230     if (!watchQuery(query))
231         return 0;
232
233     query.first();
234     UserId uid = query.value(0).toInt();
235     emit userAdded(uid, user);
236     return uid;
237 }
238
239
240 bool PostgreSqlStorage::updateUser(UserId user, const QString &password)
241 {
242     QSqlQuery query(logDb());
243     query.prepare(queryString("update_userpassword"));
244     query.bindValue(":userid", user.toInt());
245     query.bindValue(":password", hashPassword(password));
246     query.bindValue(":hashversion", Storage::HashVersion::Latest);
247     safeExec(query);
248     watchQuery(query);
249     return query.numRowsAffected() != 0;
250 }
251
252
253 void PostgreSqlStorage::renameUser(UserId user, const QString &newName)
254 {
255     QSqlQuery query(logDb());
256     query.prepare(queryString("update_username"));
257     query.bindValue(":userid", user.toInt());
258     query.bindValue(":username", newName);
259     safeExec(query);
260     watchQuery(query);
261     emit userRenamed(user, newName);
262 }
263
264
265 UserId PostgreSqlStorage::validateUser(const QString &user, const QString &password)
266 {
267     QSqlQuery query(logDb());
268     query.prepare(queryString("select_authuser"));
269     query.bindValue(":username", user);
270     safeExec(query);
271     watchQuery(query);
272
273     if (query.first() && checkHashedPassword(query.value(0).toInt(), password, query.value(1).toString(), static_cast<Storage::HashVersion>(query.value(2).toInt()))) {
274         return query.value(0).toInt();
275     }
276     else {
277         return 0;
278     }
279 }
280
281
282 UserId PostgreSqlStorage::getUserId(const QString &user)
283 {
284     QSqlQuery query(logDb());
285     query.prepare(queryString("select_userid"));
286     query.bindValue(":username", user);
287     safeExec(query);
288     watchQuery(query);
289
290     if (query.first()) {
291         return query.value(0).toInt();
292     }
293     else {
294         return 0;
295     }
296 }
297
298 QString PostgreSqlStorage::getUserAuthenticator(const UserId userid)
299 {
300     QSqlQuery query(logDb());
301     query.prepare(queryString("select_authenticator"));
302     query.bindValue(":userid", userid.toInt());
303     safeExec(query);
304     watchQuery(query);
305
306     if (query.first()) {
307         return query.value(0).toString();
308     }
309     else {
310         return QString("");
311     }
312 }
313
314 UserId PostgreSqlStorage::internalUser()
315 {
316     QSqlQuery query(logDb());
317     query.prepare(queryString("select_internaluser"));
318     safeExec(query);
319     watchQuery(query);
320
321     if (query.first()) {
322         return query.value(0).toInt();
323     }
324     else {
325         return 0;
326     }
327 }
328
329
330 void PostgreSqlStorage::delUser(UserId user)
331 {
332     QSqlDatabase db = logDb();
333     if (!beginTransaction(db)) {
334         qWarning() << "PostgreSqlStorage::delUser(): cannot start transaction!";
335         return;
336     }
337
338     QSqlQuery query(db);
339     query.prepare(queryString("delete_quasseluser"));
340     query.bindValue(":userid", user.toInt());
341     safeExec(query);
342     if (!watchQuery(query)) {
343         db.rollback();
344         return;
345     }
346     else {
347         db.commit();
348         emit userRemoved(user);
349     }
350 }
351
352
353 void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName, const QVariant &data)
354 {
355     QByteArray rawData;
356     QDataStream out(&rawData, QIODevice::WriteOnly);
357     out.setVersion(QDataStream::Qt_4_2);
358     out << data;
359
360     QSqlDatabase db = logDb();
361     QSqlQuery selectQuery(db);
362     selectQuery.prepare(queryString("select_user_setting"));
363     selectQuery.bindValue(":userid", userId.toInt());
364     selectQuery.bindValue(":settingname", settingName);
365     safeExec(selectQuery);
366     watchQuery(selectQuery);
367
368     QString setQueryString;
369     if (!selectQuery.first()) {
370         setQueryString = queryString("insert_user_setting");
371     }
372     else {
373         setQueryString = queryString("update_user_setting");
374     }
375
376     QSqlQuery setQuery(db);
377     setQuery.prepare(setQueryString);
378     setQuery.bindValue(":userid", userId.toInt());
379     setQuery.bindValue(":settingname", settingName);
380     setQuery.bindValue(":settingvalue", rawData);
381     safeExec(setQuery);
382     watchQuery(setQuery);
383 }
384
385
386 QVariant PostgreSqlStorage::getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData)
387 {
388     QSqlQuery query(logDb());
389     query.prepare(queryString("select_user_setting"));
390     query.bindValue(":userid", userId.toInt());
391     query.bindValue(":settingname", settingName);
392     safeExec(query);
393     watchQuery(query);
394
395     if (query.first()) {
396         QVariant data;
397         QByteArray rawData = query.value(0).toByteArray();
398         QDataStream in(&rawData, QIODevice::ReadOnly);
399         in.setVersion(QDataStream::Qt_4_2);
400         in >> data;
401         return data;
402     }
403     else {
404         return defaultData;
405     }
406 }
407
408
409 void PostgreSqlStorage::setCoreState(const QVariantList &data)
410 {
411     QByteArray rawData;
412     QDataStream out(&rawData, QIODevice::WriteOnly);
413     out.setVersion(QDataStream::Qt_4_2);
414     out << data;
415
416     QSqlDatabase db = logDb();
417     QSqlQuery selectQuery(db);
418     selectQuery.prepare(queryString("select_core_state"));
419     selectQuery.bindValue(":key", "active_sessions");
420     safeExec(selectQuery);
421     watchQuery(selectQuery);
422
423     QString setQueryString;
424     if (!selectQuery.first()) {
425         setQueryString = queryString("insert_core_state");
426     }
427     else {
428         setQueryString = queryString("update_core_state");
429     }
430
431     QSqlQuery setQuery(db);
432     setQuery.prepare(setQueryString);
433     setQuery.bindValue(":key", "active_sessions");
434     setQuery.bindValue(":value", rawData);
435     safeExec(setQuery);
436     watchQuery(setQuery);
437 }
438
439
440 QVariantList PostgreSqlStorage::getCoreState(const QVariantList &defaultData)
441 {
442     QSqlQuery query(logDb());
443     query.prepare(queryString("select_core_state"));
444     query.bindValue(":key", "active_sessions");
445     safeExec(query);
446     watchQuery(query);
447
448     if (query.first()) {
449         QVariantList data;
450         QByteArray rawData = query.value(0).toByteArray();
451         QDataStream in(&rawData, QIODevice::ReadOnly);
452         in.setVersion(QDataStream::Qt_4_2);
453         in >> data;
454         return data;
455     } else {
456         return defaultData;
457     }
458 }
459
460
461 IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity)
462 {
463     IdentityId identityId;
464
465     QSqlDatabase db = logDb();
466     if (!beginTransaction(db)) {
467         qWarning() << "PostgreSqlStorage::createIdentity(): Unable to start Transaction!";
468         qWarning() << " -" << qPrintable(db.lastError().text());
469         return identityId;
470     }
471
472     QSqlQuery query(db);
473     query.prepare(queryString("insert_identity"));
474     query.bindValue(":userid", user.toInt());
475     query.bindValue(":identityname", identity.identityName());
476     query.bindValue(":realname", identity.realName());
477     query.bindValue(":awaynick", identity.awayNick());
478     query.bindValue(":awaynickenabled", identity.awayNickEnabled());
479     query.bindValue(":awayreason", identity.awayReason());
480     query.bindValue(":awayreasonenabled", identity.awayReasonEnabled());
481     query.bindValue(":autoawayenabled", identity.awayReasonEnabled());
482     query.bindValue(":autoawaytime", identity.autoAwayTime());
483     query.bindValue(":autoawayreason", identity.autoAwayReason());
484     query.bindValue(":autoawayreasonenabled", identity.autoAwayReasonEnabled());
485     query.bindValue(":detachawayenabled", identity.detachAwayEnabled());
486     query.bindValue(":detachawayreason", identity.detachAwayReason());
487     query.bindValue(":detachawayreasonenabled", identity.detachAwayReasonEnabled());
488     query.bindValue(":ident", identity.ident());
489     query.bindValue(":kickreason", identity.kickReason());
490     query.bindValue(":partreason", identity.partReason());
491     query.bindValue(":quitreason", identity.quitReason());
492 #ifdef HAVE_SSL
493     query.bindValue(":sslcert", identity.sslCert().toPem());
494     query.bindValue(":sslkey", identity.sslKey().toPem());
495 #else
496     query.bindValue(":sslcert", QByteArray());
497     query.bindValue(":sslkey", QByteArray());
498 #endif
499     safeExec(query);
500     if (!watchQuery(query)) {
501         db.rollback();
502         return IdentityId();
503     }
504
505     query.first();
506     identityId = query.value(0).toInt();
507     identity.setId(identityId);
508
509     if (!identityId.isValid()) {
510         db.rollback();
511         return IdentityId();
512     }
513
514     QSqlQuery insertNickQuery(db);
515     insertNickQuery.prepare(queryString("insert_nick"));
516     foreach(QString nick, identity.nicks()) {
517         insertNickQuery.bindValue(":identityid", identityId.toInt());
518         insertNickQuery.bindValue(":nick", nick);
519         safeExec(insertNickQuery);
520         if (!watchQuery(insertNickQuery)) {
521             db.rollback();
522             return IdentityId();
523         }
524     }
525
526     if (!db.commit()) {
527         qWarning() << "PostgreSqlStorage::createIdentity(): committing data failed!";
528         qWarning() << " -" << qPrintable(db.lastError().text());
529         return IdentityId();
530     }
531     return identityId;
532 }
533
534
535 bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity)
536 {
537     QSqlDatabase db = logDb();
538     if (!beginTransaction(db)) {
539         qWarning() << "PostgreSqlStorage::updateIdentity(): Unable to start Transaction!";
540         qWarning() << " -" << qPrintable(db.lastError().text());
541         return false;
542     }
543
544     QSqlQuery checkQuery(db);
545     checkQuery.prepare(queryString("select_checkidentity"));
546     checkQuery.bindValue(":identityid", identity.id().toInt());
547     checkQuery.bindValue(":userid", user.toInt());
548     safeExec(checkQuery);
549     watchQuery(checkQuery);
550
551     // there should be exactly one identity for the given id and user
552     if (!checkQuery.first() || checkQuery.value(0).toInt() != 1) {
553         db.rollback();
554         return false;
555     }
556
557     QSqlQuery query(db);
558     query.prepare(queryString("update_identity"));
559     query.bindValue(":identityname", identity.identityName());
560     query.bindValue(":realname", identity.realName());
561     query.bindValue(":awaynick", identity.awayNick());
562     query.bindValue(":awaynickenabled", identity.awayNickEnabled());
563     query.bindValue(":awayreason", identity.awayReason());
564     query.bindValue(":awayreasonenabled", identity.awayReasonEnabled());
565     query.bindValue(":autoawayenabled", identity.awayReasonEnabled());
566     query.bindValue(":autoawaytime", identity.autoAwayTime());
567     query.bindValue(":autoawayreason", identity.autoAwayReason());
568     query.bindValue(":autoawayreasonenabled", identity.autoAwayReasonEnabled());
569     query.bindValue(":detachawayenabled", identity.detachAwayEnabled());
570     query.bindValue(":detachawayreason", identity.detachAwayReason());
571     query.bindValue(":detachawayreasonenabled", identity.detachAwayReasonEnabled());
572     query.bindValue(":ident", identity.ident());
573     query.bindValue(":kickreason", identity.kickReason());
574     query.bindValue(":partreason", identity.partReason());
575     query.bindValue(":quitreason", identity.quitReason());
576 #ifdef HAVE_SSL
577     query.bindValue(":sslcert", identity.sslCert().toPem());
578     query.bindValue(":sslkey", identity.sslKey().toPem());
579 #else
580     query.bindValue(":sslcert", QByteArray());
581     query.bindValue(":sslkey", QByteArray());
582 #endif
583     query.bindValue(":identityid", identity.id().toInt());
584
585     safeExec(query);
586     if (!watchQuery(query)) {
587         db.rollback();
588         return false;
589     }
590
591     QSqlQuery deleteNickQuery(db);
592     deleteNickQuery.prepare(queryString("delete_nicks"));
593     deleteNickQuery.bindValue(":identityid", identity.id().toInt());
594     safeExec(deleteNickQuery);
595     if (!watchQuery(deleteNickQuery)) {
596         db.rollback();
597         return false;
598     }
599
600     QSqlQuery insertNickQuery(db);
601     insertNickQuery.prepare(queryString("insert_nick"));
602     foreach(QString nick, identity.nicks()) {
603         insertNickQuery.bindValue(":identityid", identity.id().toInt());
604         insertNickQuery.bindValue(":nick", nick);
605         safeExec(insertNickQuery);
606         if (!watchQuery(insertNickQuery)) {
607             db.rollback();
608             return false;
609         }
610     }
611
612     if (!db.commit()) {
613         qWarning() << "PostgreSqlStorage::updateIdentity(): committing data failed!";
614         qWarning() << " -" << qPrintable(db.lastError().text());
615         return false;
616     }
617     return true;
618 }
619
620
621 void PostgreSqlStorage::removeIdentity(UserId user, IdentityId identityId)
622 {
623     QSqlDatabase db = logDb();
624     if (!beginTransaction(db)) {
625         qWarning() << "PostgreSqlStorage::removeIdentity(): Unable to start Transaction!";
626         qWarning() << " -" << qPrintable(db.lastError().text());
627         return;
628     }
629
630     QSqlQuery query(db);
631     query.prepare(queryString("delete_identity"));
632     query.bindValue(":identityid", identityId.toInt());
633     query.bindValue(":userid", user.toInt());
634     safeExec(query);
635     if (!watchQuery(query)) {
636         db.rollback();
637     }
638     else {
639         db.commit();
640     }
641 }
642
643
644 QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
645 {
646     QList<CoreIdentity> identities;
647
648     QSqlDatabase db = logDb();
649     if (!beginReadOnlyTransaction(db)) {
650         qWarning() << "PostgreSqlStorage::identites(): cannot start read only transaction!";
651         qWarning() << " -" << qPrintable(db.lastError().text());
652         return identities;
653     }
654
655     QSqlQuery query(db);
656     query.prepare(queryString("select_identities"));
657     query.bindValue(":userid", user.toInt());
658
659     QSqlQuery nickQuery(db);
660     nickQuery.prepare(queryString("select_nicks"));
661
662     safeExec(query);
663     watchQuery(query);
664
665     while (query.next()) {
666         CoreIdentity identity(IdentityId(query.value(0).toInt()));
667
668         identity.setIdentityName(query.value(1).toString());
669         identity.setRealName(query.value(2).toString());
670         identity.setAwayNick(query.value(3).toString());
671         identity.setAwayNickEnabled(!!query.value(4).toInt());
672         identity.setAwayReason(query.value(5).toString());
673         identity.setAwayReasonEnabled(!!query.value(6).toInt());
674         identity.setAutoAwayEnabled(!!query.value(7).toInt());
675         identity.setAutoAwayTime(query.value(8).toInt());
676         identity.setAutoAwayReason(query.value(9).toString());
677         identity.setAutoAwayReasonEnabled(!!query.value(10).toInt());
678         identity.setDetachAwayEnabled(!!query.value(11).toInt());
679         identity.setDetachAwayReason(query.value(12).toString());
680         identity.setDetachAwayReasonEnabled(!!query.value(13).toInt());
681         identity.setIdent(query.value(14).toString());
682         identity.setKickReason(query.value(15).toString());
683         identity.setPartReason(query.value(16).toString());
684         identity.setQuitReason(query.value(17).toString());
685 #ifdef HAVE_SSL
686         identity.setSslCert(query.value(18).toByteArray());
687         identity.setSslKey(query.value(19).toByteArray());
688 #endif
689
690         nickQuery.bindValue(":identityid", identity.id().toInt());
691         QList<QString> nicks;
692         safeExec(nickQuery);
693         watchQuery(nickQuery);
694         while (nickQuery.next()) {
695             nicks << nickQuery.value(0).toString();
696         }
697         identity.setNicks(nicks);
698         identities << identity;
699     }
700     db.commit();
701     return identities;
702 }
703
704
705 NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info)
706 {
707     NetworkId networkId;
708
709     QSqlDatabase db = logDb();
710     if (!beginTransaction(db)) {
711         qWarning() << "PostgreSqlStorage::createNetwork(): failed to begin transaction!";
712         qWarning() << " -" << qPrintable(db.lastError().text());
713         return false;
714     }
715
716     QSqlQuery query(db);
717     query.prepare(queryString("insert_network"));
718     query.bindValue(":userid", user.toInt());
719     bindNetworkInfo(query, info);
720     safeExec(query);
721     if (!watchQuery(query)) {
722         db.rollback();
723         return NetworkId();
724     }
725
726     query.first();
727     networkId = query.value(0).toInt();
728
729     if (!networkId.isValid()) {
730         db.rollback();
731         return NetworkId();
732     }
733
734     QSqlQuery insertServersQuery(db);
735     insertServersQuery.prepare(queryString("insert_server"));
736     foreach(Network::Server server, info.serverList) {
737         insertServersQuery.bindValue(":userid", user.toInt());
738         insertServersQuery.bindValue(":networkid", networkId.toInt());
739         bindServerInfo(insertServersQuery, server);
740         safeExec(insertServersQuery);
741         if (!watchQuery(insertServersQuery)) {
742             db.rollback();
743             return NetworkId();
744         }
745     }
746
747     if (!db.commit()) {
748         qWarning() << "PostgreSqlStorage::createNetwork(): committing data failed!";
749         qWarning() << " -" << qPrintable(db.lastError().text());
750         return NetworkId();
751     }
752     return networkId;
753 }
754
755
756 void PostgreSqlStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &info)
757 {
758     query.bindValue(":networkname", info.networkName);
759     query.bindValue(":identityid", info.identity.isValid() ? info.identity.toInt() : QVariant());
760     query.bindValue(":encodingcodec", QString(info.codecForEncoding));
761     query.bindValue(":decodingcodec", QString(info.codecForDecoding));
762     query.bindValue(":servercodec", QString(info.codecForServer));
763     query.bindValue(":userandomserver", info.useRandomServer);
764     query.bindValue(":perform", info.perform.join("\n"));
765     query.bindValue(":useautoidentify", info.useAutoIdentify);
766     query.bindValue(":autoidentifyservice", info.autoIdentifyService);
767     query.bindValue(":autoidentifypassword", info.autoIdentifyPassword);
768     query.bindValue(":usesasl", info.useSasl);
769     query.bindValue(":saslaccount", info.saslAccount);
770     query.bindValue(":saslpassword", info.saslPassword);
771     query.bindValue(":useautoreconnect", info.useAutoReconnect);
772     query.bindValue(":autoreconnectinterval", info.autoReconnectInterval);
773     query.bindValue(":autoreconnectretries", info.autoReconnectRetries);
774     query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries);
775     query.bindValue(":rejoinchannels", info.rejoinChannels);
776     // Custom rate limiting
777     query.bindValue(":usecustomessagerate", info.useCustomMessageRate);
778     query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
779     query.bindValue(":messageratedelay", info.messageRateDelay);
780     query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate);
781     if (info.networkId.isValid())
782         query.bindValue(":networkid", info.networkId.toInt());
783 }
784
785
786 void PostgreSqlStorage::bindServerInfo(QSqlQuery &query, const Network::Server &server)
787 {
788     query.bindValue(":hostname", server.host);
789     query.bindValue(":port", server.port);
790     query.bindValue(":password", server.password);
791     query.bindValue(":ssl", server.useSsl);
792     query.bindValue(":sslversion", server.sslVersion);
793     query.bindValue(":useproxy", server.useProxy);
794     query.bindValue(":proxytype", server.proxyType);
795     query.bindValue(":proxyhost", server.proxyHost);
796     query.bindValue(":proxyport", server.proxyPort);
797     query.bindValue(":proxyuser", server.proxyUser);
798     query.bindValue(":proxypass", server.proxyPass);
799     query.bindValue(":sslverify", server.sslVerify);
800 }
801
802
803 bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info)
804 {
805     QSqlDatabase db = logDb();
806     if (!beginTransaction(db)) {
807         qWarning() << "PostgreSqlStorage::updateNetwork(): failed to begin transaction!";
808         qWarning() << " -" << qPrintable(db.lastError().text());
809         return false;
810     }
811
812     QSqlQuery updateQuery(db);
813     updateQuery.prepare(queryString("update_network"));
814     updateQuery.bindValue(":userid", user.toInt());
815     bindNetworkInfo(updateQuery, info);
816     safeExec(updateQuery);
817     if (!watchQuery(updateQuery)) {
818         db.rollback();
819         return false;
820     }
821     if (updateQuery.numRowsAffected() != 1) {
822         // seems this is not our network...
823         db.rollback();
824         return false;
825     }
826
827     QSqlQuery dropServersQuery(db);
828     dropServersQuery.prepare("DELETE FROM ircserver WHERE networkid = :networkid");
829     dropServersQuery.bindValue(":networkid", info.networkId.toInt());
830     safeExec(dropServersQuery);
831     if (!watchQuery(dropServersQuery)) {
832         db.rollback();
833         return false;
834     }
835
836     QSqlQuery insertServersQuery(db);
837     insertServersQuery.prepare(queryString("insert_server"));
838     foreach(Network::Server server, info.serverList) {
839         insertServersQuery.bindValue(":userid", user.toInt());
840         insertServersQuery.bindValue(":networkid", info.networkId.toInt());
841         bindServerInfo(insertServersQuery, server);
842         safeExec(insertServersQuery);
843         if (!watchQuery(insertServersQuery)) {
844             db.rollback();
845             return false;
846         }
847     }
848
849     if (!db.commit()) {
850         qWarning() << "PostgreSqlStorage::updateNetwork(): committing data failed!";
851         qWarning() << " -" << qPrintable(db.lastError().text());
852         return false;
853     }
854     return true;
855 }
856
857
858 bool PostgreSqlStorage::removeNetwork(UserId user, const NetworkId &networkId)
859 {
860     QSqlDatabase db = logDb();
861     if (!beginTransaction(db)) {
862         qWarning() << "PostgreSqlStorage::removeNetwork(): cannot start transaction!";
863         qWarning() << " -" << qPrintable(db.lastError().text());
864         return false;
865     }
866
867     QSqlQuery query(db);
868     query.prepare(queryString("delete_network"));
869     query.bindValue(":userid", user.toInt());
870     query.bindValue(":networkid", networkId.toInt());
871     safeExec(query);
872     if (!watchQuery(query)) {
873         db.rollback();
874         return false;
875     }
876
877     db.commit();
878     return true;
879 }
880
881
882 QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
883 {
884     QList<NetworkInfo> nets;
885
886     QSqlDatabase db = logDb();
887     if (!beginReadOnlyTransaction(db)) {
888         qWarning() << "PostgreSqlStorage::networks(): cannot start read only transaction!";
889         qWarning() << " -" << qPrintable(db.lastError().text());
890         return nets;
891     }
892
893     QSqlQuery networksQuery(db);
894     networksQuery.prepare(queryString("select_networks_for_user"));
895     networksQuery.bindValue(":userid", user.toInt());
896
897     QSqlQuery serversQuery(db);
898     serversQuery.prepare(queryString("select_servers_for_network"));
899
900     safeExec(networksQuery);
901     if (!watchQuery(networksQuery)) {
902         db.rollback();
903         return nets;
904     }
905
906     while (networksQuery.next()) {
907         NetworkInfo net;
908         net.networkId = networksQuery.value(0).toInt();
909         net.networkName = networksQuery.value(1).toString();
910         net.identity = networksQuery.value(2).toInt();
911         net.codecForServer = networksQuery.value(3).toString().toLatin1();
912         net.codecForEncoding = networksQuery.value(4).toString().toLatin1();
913         net.codecForDecoding = networksQuery.value(5).toString().toLatin1();
914         net.useRandomServer = networksQuery.value(6).toBool();
915         net.perform = networksQuery.value(7).toString().split("\n");
916         net.useAutoIdentify = networksQuery.value(8).toBool();
917         net.autoIdentifyService = networksQuery.value(9).toString();
918         net.autoIdentifyPassword = networksQuery.value(10).toString();
919         net.useAutoReconnect = networksQuery.value(11).toBool();
920         net.autoReconnectInterval = networksQuery.value(12).toUInt();
921         net.autoReconnectRetries = networksQuery.value(13).toInt();
922         net.unlimitedReconnectRetries = networksQuery.value(14).toBool();
923         net.rejoinChannels = networksQuery.value(15).toBool();
924         net.useSasl = networksQuery.value(16).toBool();
925         net.saslAccount = networksQuery.value(17).toString();
926         net.saslPassword = networksQuery.value(18).toString();
927         // Custom rate limiting
928         net.useCustomMessageRate = networksQuery.value(19).toBool();
929         net.messageRateBurstSize = networksQuery.value(20).toUInt();
930         net.messageRateDelay = networksQuery.value(21).toUInt();
931         net.unlimitedMessageRate = networksQuery.value(22).toBool();
932
933         serversQuery.bindValue(":networkid", net.networkId.toInt());
934         safeExec(serversQuery);
935         if (!watchQuery(serversQuery)) {
936             db.rollback();
937             return nets;
938         }
939
940         Network::ServerList servers;
941         while (serversQuery.next()) {
942             Network::Server server;
943             server.host = serversQuery.value(0).toString();
944             server.port = serversQuery.value(1).toUInt();
945             server.password = serversQuery.value(2).toString();
946             server.useSsl = serversQuery.value(3).toBool();
947             server.sslVersion = serversQuery.value(4).toInt();
948             server.useProxy = serversQuery.value(5).toBool();
949             server.proxyType = serversQuery.value(6).toInt();
950             server.proxyHost = serversQuery.value(7).toString();
951             server.proxyPort = serversQuery.value(8).toUInt();
952             server.proxyUser = serversQuery.value(9).toString();
953             server.proxyPass = serversQuery.value(10).toString();
954             server.sslVerify = serversQuery.value(11).toBool();
955             servers << server;
956         }
957         net.serverList = servers;
958         nets << net;
959     }
960     db.commit();
961     return nets;
962 }
963
964
965 QList<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
966 {
967     QList<NetworkId> connectedNets;
968
969     QSqlDatabase db = logDb();
970     if (!beginReadOnlyTransaction(db)) {
971         qWarning() << "PostgreSqlStorage::connectedNetworks(): cannot start read only transaction!";
972         qWarning() << " -" << qPrintable(db.lastError().text());
973         return connectedNets;
974     }
975
976     QSqlQuery query(db);
977     query.prepare(queryString("select_connected_networks"));
978     query.bindValue(":userid", user.toInt());
979     safeExec(query);
980     watchQuery(query);
981
982     while (query.next()) {
983         connectedNets << query.value(0).toInt();
984     }
985
986     db.commit();
987     return connectedNets;
988 }
989
990
991 void PostgreSqlStorage::setNetworkConnected(UserId user, const NetworkId &networkId, bool isConnected)
992 {
993     QSqlQuery query(logDb());
994     query.prepare(queryString("update_network_connected"));
995     query.bindValue(":userid", user.toInt());
996     query.bindValue(":networkid", networkId.toInt());
997     query.bindValue(":connected", isConnected);
998     safeExec(query);
999     watchQuery(query);
1000 }
1001
1002
1003 QHash<QString, QString> PostgreSqlStorage::persistentChannels(UserId user, const NetworkId &networkId)
1004 {
1005     QHash<QString, QString> persistentChans;
1006
1007     QSqlDatabase db = logDb();
1008     if (!beginReadOnlyTransaction(db)) {
1009         qWarning() << "PostgreSqlStorage::persistentChannels(): cannot start read only transaction!";
1010         qWarning() << " -" << qPrintable(db.lastError().text());
1011         return persistentChans;
1012     }
1013
1014     QSqlQuery query(db);
1015     query.prepare(queryString("select_persistent_channels"));
1016     query.bindValue(":userid", user.toInt());
1017     query.bindValue(":networkid", networkId.toInt());
1018     safeExec(query);
1019     watchQuery(query);
1020
1021     while (query.next()) {
1022         persistentChans[query.value(0).toString()] = query.value(1).toString();
1023     }
1024
1025     db.commit();
1026     return persistentChans;
1027 }
1028
1029
1030 void PostgreSqlStorage::setChannelPersistent(UserId user, const NetworkId &networkId, const QString &channel, bool isJoined)
1031 {
1032     QSqlQuery query(logDb());
1033     query.prepare(queryString("update_buffer_persistent_channel"));
1034     query.bindValue(":userid", user.toInt());
1035     query.bindValue(":networkid", networkId.toInt());
1036     query.bindValue(":buffercname", channel.toLower());
1037     query.bindValue(":joined", isJoined);
1038     safeExec(query);
1039     watchQuery(query);
1040 }
1041
1042
1043 void PostgreSqlStorage::setPersistentChannelKey(UserId user, const NetworkId &networkId, const QString &channel, const QString &key)
1044 {
1045     QSqlQuery query(logDb());
1046     query.prepare(queryString("update_buffer_set_channel_key"));
1047     query.bindValue(":userid", user.toInt());
1048     query.bindValue(":networkid", networkId.toInt());
1049     query.bindValue(":buffercname", channel.toLower());
1050     query.bindValue(":key", key);
1051     safeExec(query);
1052     watchQuery(query);
1053 }
1054
1055
1056 QString PostgreSqlStorage::awayMessage(UserId user, NetworkId networkId)
1057 {
1058     QSqlQuery query(logDb());
1059     query.prepare(queryString("select_network_awaymsg"));
1060     query.bindValue(":userid", user.toInt());
1061     query.bindValue(":networkid", networkId.toInt());
1062     safeExec(query);
1063     watchQuery(query);
1064     QString awayMsg;
1065     if (query.first())
1066         awayMsg = query.value(0).toString();
1067     return awayMsg;
1068 }
1069
1070
1071 void PostgreSqlStorage::setAwayMessage(UserId user, NetworkId networkId, const QString &awayMsg)
1072 {
1073     QSqlQuery query(logDb());
1074     query.prepare(queryString("update_network_set_awaymsg"));
1075     query.bindValue(":userid", user.toInt());
1076     query.bindValue(":networkid", networkId.toInt());
1077     query.bindValue(":awaymsg", awayMsg);
1078     safeExec(query);
1079     watchQuery(query);
1080 }
1081
1082
1083 QString PostgreSqlStorage::userModes(UserId user, NetworkId networkId)
1084 {
1085     QSqlQuery query(logDb());
1086     query.prepare(queryString("select_network_usermode"));
1087     query.bindValue(":userid", user.toInt());
1088     query.bindValue(":networkid", networkId.toInt());
1089     safeExec(query);
1090     watchQuery(query);
1091     QString modes;
1092     if (query.first())
1093         modes = query.value(0).toString();
1094     return modes;
1095 }
1096
1097
1098 void PostgreSqlStorage::setUserModes(UserId user, NetworkId networkId, const QString &userModes)
1099 {
1100     QSqlQuery query(logDb());
1101     query.prepare(queryString("update_network_set_usermode"));
1102     query.bindValue(":userid", user.toInt());
1103     query.bindValue(":networkid", networkId.toInt());
1104     query.bindValue(":usermode", userModes);
1105     safeExec(query);
1106     watchQuery(query);
1107 }
1108
1109
1110 BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create)
1111 {
1112     QSqlDatabase db = logDb();
1113     if (!beginTransaction(db)) {
1114         qWarning() << "PostgreSqlStorage::bufferInfo(): cannot start read only transaction!";
1115         qWarning() << " -" << qPrintable(db.lastError().text());
1116         return BufferInfo();
1117     }
1118
1119     QSqlQuery query(db);
1120     query.prepare(queryString("select_bufferByName"));
1121     query.bindValue(":networkid", networkId.toInt());
1122     query.bindValue(":userid", user.toInt());
1123     query.bindValue(":buffercname", buffer.toLower());
1124     safeExec(query);
1125     watchQuery(query);
1126
1127     if (query.first()) {
1128         BufferInfo bufferInfo = BufferInfo(query.value(0).toInt(), networkId, (BufferInfo::Type)query.value(1).toInt(), 0, buffer);
1129         if (query.next()) {
1130             qCritical() << "PostgreSqlStorage::bufferInfo(): received more then one Buffer!";
1131             qCritical() << "         Query:" << query.lastQuery();
1132             qCritical() << "  bound Values:";
1133             QList<QVariant> list = query.boundValues().values();
1134             for (int i = 0; i < list.size(); ++i)
1135                 qCritical() << i << ":" << list.at(i).toString().toLatin1().data();
1136             Q_ASSERT(false);
1137         }
1138         db.commit();
1139         return bufferInfo;
1140     }
1141
1142     if (!create) {
1143         db.rollback();
1144         return BufferInfo();
1145     }
1146
1147     QSqlQuery createQuery(db);
1148     createQuery.prepare(queryString("insert_buffer"));
1149     createQuery.bindValue(":userid", user.toInt());
1150     createQuery.bindValue(":networkid", networkId.toInt());
1151     createQuery.bindValue(":buffertype", (int)type);
1152     createQuery.bindValue(":buffername", buffer);
1153     createQuery.bindValue(":buffercname", buffer.toLower());
1154     createQuery.bindValue(":joined", type & BufferInfo::ChannelBuffer ? true : false);
1155
1156     safeExec(createQuery);
1157
1158     if (!watchQuery(createQuery)) {
1159         qWarning() << "PostgreSqlStorage::bufferInfo(): unable to create buffer";
1160         db.rollback();
1161         return BufferInfo();
1162     }
1163
1164     createQuery.first();
1165
1166     BufferInfo bufferInfo = BufferInfo(createQuery.value(0).toInt(), networkId, type, 0, buffer);
1167     db.commit();
1168     return bufferInfo;
1169 }
1170
1171
1172 BufferInfo PostgreSqlStorage::getBufferInfo(UserId user, const BufferId &bufferId)
1173 {
1174     QSqlQuery query(logDb());
1175     query.prepare(queryString("select_buffer_by_id"));
1176     query.bindValue(":userid", user.toInt());
1177     query.bindValue(":bufferid", bufferId.toInt());
1178     safeExec(query);
1179     if (!watchQuery(query))
1180         return BufferInfo();
1181
1182     if (!query.first())
1183         return BufferInfo();
1184
1185     BufferInfo bufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), 0, query.value(4).toString());
1186     Q_ASSERT(!query.next());
1187
1188     return bufferInfo;
1189 }
1190
1191
1192 QList<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
1193 {
1194     QList<BufferInfo> bufferlist;
1195
1196     QSqlDatabase db = logDb();
1197     if (!beginReadOnlyTransaction(db)) {
1198         qWarning() << "PostgreSqlStorage::requestBuffers(): cannot start read only transaction!";
1199         qWarning() << " -" << qPrintable(db.lastError().text());
1200         return bufferlist;
1201     }
1202
1203     QSqlQuery query(db);
1204     query.prepare(queryString("select_buffers"));
1205     query.bindValue(":userid", user.toInt());
1206
1207     safeExec(query);
1208     watchQuery(query);
1209     while (query.next()) {
1210         bufferlist << BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), query.value(3).toInt(), query.value(4).toString());
1211     }
1212     db.commit();
1213     return bufferlist;
1214 }
1215
1216
1217 QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
1218 {
1219     QList<BufferId> bufferList;
1220
1221     QSqlDatabase db = logDb();
1222     if (!beginReadOnlyTransaction(db)) {
1223         qWarning() << "PostgreSqlStorage::requestBufferIdsForNetwork(): cannot start read only transaction!";
1224         qWarning() << " -" << qPrintable(db.lastError().text());
1225         return bufferList;
1226     }
1227
1228     QSqlQuery query(db);
1229     query.prepare(queryString("select_buffers_for_network"));
1230     query.bindValue(":networkid", networkId.toInt());
1231     query.bindValue(":userid", user.toInt());
1232
1233     safeExec(query);
1234     watchQuery(query);
1235     while (query.next()) {
1236         bufferList << BufferId(query.value(0).toInt());
1237     }
1238     db.commit();
1239     return bufferList;
1240 }
1241
1242
1243 bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferId)
1244 {
1245     QSqlDatabase db = logDb();
1246     if (!beginTransaction(db)) {
1247         qWarning() << "PostgreSqlStorage::removeBuffer(): cannot start transaction!";
1248         return false;
1249     }
1250
1251     QSqlQuery query(db);
1252     query.prepare(queryString("delete_buffer_for_bufferid"));
1253     query.bindValue(":userid", user.toInt());
1254     query.bindValue(":bufferid", bufferId.toInt());
1255     safeExec(query);
1256     if (!watchQuery(query)) {
1257         db.rollback();
1258         return false;
1259     }
1260
1261     int numRows = query.numRowsAffected();
1262     switch (numRows) {
1263     case 0:
1264         db.commit();
1265         return false;
1266     case 1:
1267         db.commit();
1268         return true;
1269     default:
1270         // there was more then one buffer deleted...
1271         qWarning() << "PostgreSqlStorage::removeBuffer(): Userid" << user << "BufferId" << "caused deletion of" << numRows << "Buffers! Rolling back transaction...";
1272         db.rollback();
1273         return false;
1274     }
1275 }
1276
1277
1278 bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferId, const QString &newName)
1279 {
1280     QSqlDatabase db = logDb();
1281     if (!beginTransaction(db)) {
1282         qWarning() << "PostgreSqlStorage::renameBuffer(): cannot start transaction!";
1283         return false;
1284     }
1285
1286     QSqlQuery query(db);
1287     query.prepare(queryString("update_buffer_name"));
1288     query.bindValue(":buffername", newName);
1289     query.bindValue(":buffercname", newName.toLower());
1290     query.bindValue(":userid", user.toInt());
1291     query.bindValue(":bufferid", bufferId.toInt());
1292     safeExec(query);
1293     if (!watchQuery(query)) {
1294         db.rollback();
1295         return false;
1296     }
1297
1298     int numRows = query.numRowsAffected();
1299     switch (numRows) {
1300     case 0:
1301         db.commit();
1302         return false;
1303     case 1:
1304         db.commit();
1305         return true;
1306     default:
1307         // there was more then one buffer deleted...
1308         qWarning() << "PostgreSqlStorage::renameBuffer(): Userid" << user << "BufferId" << "affected" << numRows << "Buffers! Rolling back transaction...";
1309         db.rollback();
1310         return false;
1311     }
1312 }
1313
1314
1315 bool PostgreSqlStorage::mergeBuffersPermanently(const UserId &user, const BufferId &bufferId1, const BufferId &bufferId2)
1316 {
1317     QSqlDatabase db = logDb();
1318     if (!beginTransaction(db)) {
1319         qWarning() << "PostgreSqlStorage::mergeBuffersPermanently(): cannot start transaction!";
1320         qWarning() << " -" << qPrintable(db.lastError().text());
1321         return false;
1322     }
1323
1324     QSqlQuery checkQuery(db);
1325     checkQuery.prepare("SELECT count(*) FROM buffer "
1326                        "WHERE userid = :userid AND bufferid IN (:buffer1, :buffer2)");
1327     checkQuery.bindValue(":userid", user.toInt());
1328     checkQuery.bindValue(":buffer1", bufferId1.toInt());
1329     checkQuery.bindValue(":buffer2", bufferId2.toInt());
1330     safeExec(checkQuery);
1331     if (!watchQuery(checkQuery)) {
1332         db.rollback();
1333         return false;
1334     }
1335     checkQuery.first();
1336     if (checkQuery.value(0).toInt() != 2) {
1337         db.rollback();
1338         return false;
1339     }
1340
1341     QSqlQuery query(db);
1342     query.prepare(queryString("update_backlog_bufferid"));
1343     query.bindValue(":oldbufferid", bufferId2.toInt());
1344     query.bindValue(":newbufferid", bufferId1.toInt());
1345     safeExec(query);
1346     if (!watchQuery(query)) {
1347         db.rollback();
1348         return false;
1349     }
1350
1351     QSqlQuery delBufferQuery(logDb());
1352     delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
1353     delBufferQuery.bindValue(":userid", user.toInt());
1354     delBufferQuery.bindValue(":bufferid", bufferId2.toInt());
1355     safeExec(delBufferQuery);
1356     if (!watchQuery(delBufferQuery)) {
1357         db.rollback();
1358         return false;
1359     }
1360
1361     db.commit();
1362     return true;
1363 }
1364
1365
1366 void PostgreSqlStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, const MsgId &msgId)
1367 {
1368     QSqlQuery query(logDb());
1369     query.prepare(queryString("update_buffer_lastseen"));
1370
1371     query.bindValue(":userid", user.toInt());
1372     query.bindValue(":bufferid", bufferId.toInt());
1373     query.bindValue(":lastseenmsgid", msgId.toQint64());
1374     safeExec(query);
1375     watchQuery(query);
1376 }
1377
1378
1379 QHash<BufferId, MsgId> PostgreSqlStorage::bufferLastSeenMsgIds(UserId user)
1380 {
1381     QHash<BufferId, MsgId> lastSeenHash;
1382
1383     QSqlDatabase db = logDb();
1384     if (!beginReadOnlyTransaction(db)) {
1385         qWarning() << "PostgreSqlStorage::bufferLastSeenMsgIds(): cannot start read only transaction!";
1386         qWarning() << " -" << qPrintable(db.lastError().text());
1387         return lastSeenHash;
1388     }
1389
1390     QSqlQuery query(db);
1391     query.prepare(queryString("select_buffer_lastseen_messages"));
1392     query.bindValue(":userid", user.toInt());
1393     safeExec(query);
1394     if (!watchQuery(query)) {
1395         db.rollback();
1396         return lastSeenHash;
1397     }
1398
1399     while (query.next()) {
1400         lastSeenHash[query.value(0).toInt()] = query.value(1).toLongLong();
1401     }
1402
1403     db.commit();
1404     return lastSeenHash;
1405 }
1406
1407
1408 void PostgreSqlStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId, const MsgId &msgId)
1409 {
1410     QSqlQuery query(logDb());
1411     query.prepare(queryString("update_buffer_markerlinemsgid"));
1412
1413     query.bindValue(":userid", user.toInt());
1414     query.bindValue(":bufferid", bufferId.toInt());
1415     query.bindValue(":markerlinemsgid", msgId.toQint64());
1416     safeExec(query);
1417     watchQuery(query);
1418 }
1419
1420
1421 QHash<BufferId, MsgId> PostgreSqlStorage::bufferMarkerLineMsgIds(UserId user)
1422 {
1423     QHash<BufferId, MsgId> markerLineHash;
1424
1425     QSqlDatabase db = logDb();
1426     if (!beginReadOnlyTransaction(db)) {
1427         qWarning() << "PostgreSqlStorage::bufferMarkerLineMsgIds(): cannot start read only transaction!";
1428         qWarning() << " -" << qPrintable(db.lastError().text());
1429         return markerLineHash;
1430     }
1431
1432     QSqlQuery query(db);
1433     query.prepare(queryString("select_buffer_markerlinemsgids"));
1434     query.bindValue(":userid", user.toInt());
1435     safeExec(query);
1436     if (!watchQuery(query)) {
1437         db.rollback();
1438         return markerLineHash;
1439     }
1440
1441     while (query.next()) {
1442         markerLineHash[query.value(0).toInt()] = query.value(1).toLongLong();
1443     }
1444
1445     db.commit();
1446     return markerLineHash;
1447 }
1448
1449
1450 void PostgreSqlStorage::setBufferActivity(UserId user, BufferId bufferId, Message::Types bufferActivity)
1451 {
1452     QSqlQuery query(logDb());
1453     query.prepare(queryString("update_buffer_bufferactivity"));
1454
1455     query.bindValue(":userid", user.toInt());
1456     query.bindValue(":bufferid", bufferId.toInt());
1457     query.bindValue(":bufferactivity", (int) bufferActivity);
1458     safeExec(query);
1459     watchQuery(query);
1460 }
1461
1462 QHash<BufferId, Message::Types> PostgreSqlStorage::bufferActivities(UserId user)
1463 {
1464     QHash<BufferId, Message::Types> bufferActivityHash;
1465
1466     QSqlDatabase db = logDb();
1467     if (!beginReadOnlyTransaction(db)) {
1468         qWarning() << "PostgreSqlStorage::bufferActivities(): cannot start read only transaction!";
1469         qWarning() << " -" << qPrintable(db.lastError().text());
1470         return bufferActivityHash;
1471     }
1472
1473     QSqlQuery query(db);
1474     query.prepare(queryString("select_buffer_bufferactivities"));
1475     query.bindValue(":userid", user.toInt());
1476     safeExec(query);
1477     if (!watchQuery(query)) {
1478         db.rollback();
1479         return bufferActivityHash;
1480     }
1481
1482     while (query.next()) {
1483         bufferActivityHash[query.value(0).toInt()] = Message::Types(query.value(1).toInt());
1484     }
1485
1486     db.commit();
1487     return bufferActivityHash;
1488 }
1489
1490 Message::Types PostgreSqlStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMsgId)
1491 {
1492     QSqlQuery query(logDb());
1493     query.prepare(queryString("select_buffer_bufferactivity"));
1494     query.bindValue(":bufferid", bufferId.toInt());
1495     query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
1496     safeExec(query);
1497     watchQuery(query);
1498     Message::Types result = Message::Types(nullptr);
1499     if (query.first())
1500         result = Message::Types(query.value(0).toInt());
1501     return result;
1502 }
1503
1504 QHash<QString, QByteArray> PostgreSqlStorage::bufferCiphers(UserId user, const NetworkId &networkId)
1505 {
1506     QHash<QString, QByteArray> bufferCiphers;
1507
1508     QSqlDatabase db = logDb();
1509     if (!beginReadOnlyTransaction(db)) {
1510         qWarning() << "PostgreSqlStorage::persistentChannels(): cannot start read only transaction!";
1511         qWarning() << " -" << qPrintable(db.lastError().text());
1512         return bufferCiphers;
1513     }
1514
1515     QSqlQuery query(db);
1516     query.prepare(queryString("select_buffer_ciphers"));
1517     query.bindValue(":userid", user.toInt());
1518     query.bindValue(":networkid", networkId.toInt());
1519     safeExec(query);
1520     watchQuery(query);
1521
1522     while (query.next()) {
1523         bufferCiphers[query.value(0).toString()] = QByteArray::fromHex(query.value(1).toString().toUtf8());
1524     }
1525
1526     db.commit();
1527     return bufferCiphers;
1528 }
1529
1530 void PostgreSqlStorage::setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher)
1531 {
1532     QSqlQuery query(logDb());
1533     query.prepare(queryString("update_buffer_cipher"));
1534     query.bindValue(":userid", user.toInt());
1535     query.bindValue(":networkid", networkId.toInt());
1536     query.bindValue(":buffercname", bufferName.toLower());
1537     query.bindValue(":cipher", QString(cipher.toHex()));
1538     safeExec(query);
1539     watchQuery(query);
1540 }
1541
1542
1543 void PostgreSqlStorage::setHighlightCount(UserId user, BufferId bufferId, int highlightcount)
1544 {
1545     QSqlQuery query(logDb());
1546     query.prepare(queryString("update_buffer_highlightcount"));
1547
1548     query.bindValue(":userid", user.toInt());
1549     query.bindValue(":bufferid", bufferId.toInt());
1550     query.bindValue(":highlightcount", highlightcount);
1551     safeExec(query);
1552     watchQuery(query);
1553 }
1554
1555 QHash<BufferId, int> PostgreSqlStorage::highlightCounts(UserId user)
1556 {
1557     QHash<BufferId, int> highlightCountHash;
1558
1559     QSqlDatabase db = logDb();
1560     if (!beginReadOnlyTransaction(db)) {
1561         qWarning() << "PostgreSqlStorage::highlightCounts(): cannot start read only transaction!";
1562         qWarning() << " -" << qPrintable(db.lastError().text());
1563         return highlightCountHash;
1564     }
1565
1566     QSqlQuery query(db);
1567     query.prepare(queryString("select_buffer_highlightcounts"));
1568     query.bindValue(":userid", user.toInt());
1569     safeExec(query);
1570     if (!watchQuery(query)) {
1571         db.rollback();
1572         return highlightCountHash;
1573     }
1574
1575     while (query.next()) {
1576         highlightCountHash[query.value(0).toInt()] = query.value(1).toInt();
1577     }
1578
1579     db.commit();
1580     return highlightCountHash;
1581 }
1582
1583 int PostgreSqlStorage::highlightCount(BufferId bufferId, MsgId lastSeenMsgId)
1584 {
1585     QSqlQuery query(logDb());
1586     query.prepare(queryString("select_buffer_highlightcount"));
1587     query.bindValue(":bufferid", bufferId.toInt());
1588     query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
1589     safeExec(query);
1590     watchQuery(query);
1591     int result = int(0);
1592     if (query.first())
1593         result = query.value(0).toInt();
1594     return result;
1595 }
1596
1597 bool PostgreSqlStorage::logMessage(Message &msg)
1598 {
1599     QSqlDatabase db = logDb();
1600     if (!beginTransaction(db)) {
1601         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
1602         qWarning() << " -" << qPrintable(db.lastError().text());
1603         return false;
1604     }
1605
1606     QVariantList senderParams;
1607     senderParams << msg.sender()
1608                  << msg.realName()
1609                  << msg.avatarUrl();
1610     QSqlQuery getSenderIdQuery = executePreparedQuery("select_senderid", senderParams, db);
1611     qint64 senderId;
1612     if (getSenderIdQuery.first()) {
1613         senderId = getSenderIdQuery.value(0).toLongLong();
1614     }
1615     else {
1616         // it's possible that the sender was already added by another thread
1617         // since the insert might fail we're setting a savepoint
1618         savePoint("sender_sp1", db);
1619         QSqlQuery addSenderQuery = executePreparedQuery("insert_sender", senderParams, db);
1620
1621         if (addSenderQuery.lastError().isValid()) {
1622             rollbackSavePoint("sender_sp1", db);
1623             getSenderIdQuery = executePreparedQuery("select_senderid", senderParams, db);
1624             watchQuery(getSenderIdQuery);
1625             getSenderIdQuery.first();
1626             senderId = getSenderIdQuery.value(0).toLongLong();
1627         }
1628         else {
1629             releaseSavePoint("sender_sp1", db);
1630             addSenderQuery.first();
1631             senderId = addSenderQuery.value(0).toLongLong();
1632         }
1633     }
1634
1635     QVariantList params;
1636     // PostgreSQL handles QDateTime()'s serialized format by default, and QDateTime() serializes
1637     // to a 64-bit time compatible format by default.
1638     params << msg.timestamp()
1639            << msg.bufferInfo().bufferId().toInt()
1640            << msg.type()
1641            << (int)msg.flags()
1642            << senderId
1643            << msg.senderPrefixes()
1644            << msg.contents();
1645     QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
1646
1647     if (!watchQuery(logMessageQuery)) {
1648         db.rollback();
1649         return false;
1650     }
1651
1652     logMessageQuery.first();
1653     MsgId msgId = logMessageQuery.value(0).toLongLong();
1654     db.commit();
1655     if (msgId.isValid()) {
1656         msg.setMsgId(msgId);
1657         return true;
1658     }
1659     else {
1660         return false;
1661     }
1662 }
1663
1664
1665 bool PostgreSqlStorage::logMessages(MessageList &msgs)
1666 {
1667     QSqlDatabase db = logDb();
1668     if (!beginTransaction(db)) {
1669         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
1670         qWarning() << " -" << qPrintable(db.lastError().text());
1671         return false;
1672     }
1673
1674     QList<int> senderIdList;
1675     QHash<SenderData, qint64> senderIds;
1676     QSqlQuery addSenderQuery;
1677     QSqlQuery selectSenderQuery;;
1678     for (int i = 0; i < msgs.count(); i++) {
1679         auto &msg = msgs.at(i);
1680         SenderData sender = { msg.sender(), msg.realName(), msg.avatarUrl() };
1681         if (senderIds.contains(sender)) {
1682             senderIdList << senderIds[sender];
1683             continue;
1684         }
1685
1686         QVariantList senderParams;
1687         senderParams << sender.sender
1688                      << sender.realname
1689                      << sender.avatarurl;
1690
1691         selectSenderQuery = executePreparedQuery("select_senderid", senderParams, db);
1692         if (selectSenderQuery.first()) {
1693             senderIdList << selectSenderQuery.value(0).toLongLong();
1694             senderIds[sender] = selectSenderQuery.value(0).toLongLong();
1695         }
1696         else {
1697             savePoint("sender_sp", db);
1698             addSenderQuery = executePreparedQuery("insert_sender", senderParams, db);
1699             if (addSenderQuery.lastError().isValid()) {
1700                 // seems it was inserted meanwhile... by a different thread
1701                 rollbackSavePoint("sender_sp", db);
1702                 selectSenderQuery = executePreparedQuery("select_senderid", senderParams, db);
1703                 watchQuery(selectSenderQuery);
1704                 selectSenderQuery.first();
1705                 senderIdList << selectSenderQuery.value(0).toLongLong();
1706                 senderIds[sender] = selectSenderQuery.value(0).toLongLong();
1707             }
1708             else {
1709                 releaseSavePoint("sender_sp", db);
1710                 addSenderQuery.first();
1711                 senderIdList << addSenderQuery.value(0).toLongLong();
1712                 senderIds[sender] = addSenderQuery.value(0).toLongLong();
1713             }
1714         }
1715     }
1716
1717     // yes we loop twice over the same list. This avoids alternating queries.
1718     bool error = false;
1719     for (int i = 0; i < msgs.count(); i++) {
1720         Message &msg = msgs[i];
1721         QVariantList params;
1722         // PostgreSQL handles QDateTime()'s serialized format by default, and QDateTime() serializes
1723         // to a 64-bit time compatible format by default.
1724         params << msg.timestamp()
1725                << msg.bufferInfo().bufferId().toInt()
1726                << msg.type()
1727                << (int)msg.flags()
1728                << senderIdList.at(i)
1729                << msg.senderPrefixes()
1730                << msg.contents();
1731         QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
1732         if (!watchQuery(logMessageQuery)) {
1733             db.rollback();
1734             error = true;
1735             break;
1736         }
1737         else {
1738             logMessageQuery.first();
1739             msg.setMsgId(logMessageQuery.value(0).toLongLong());
1740         }
1741     }
1742
1743     if (error) {
1744         // we had a rollback in the db so we need to reset all msgIds
1745         for (int i = 0; i < msgs.count(); i++) {
1746             msgs[i].setMsgId(MsgId());
1747         }
1748         return false;
1749     }
1750
1751     db.commit();
1752     return true;
1753 }
1754
1755
1756 QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
1757 {
1758     QList<Message> messagelist;
1759
1760     QSqlDatabase db = logDb();
1761     if (!beginReadOnlyTransaction(db)) {
1762         qWarning() << "PostgreSqlStorage::requestMsgs(): cannot start read only transaction!";
1763         qWarning() << " -" << qPrintable(db.lastError().text());
1764         return messagelist;
1765     }
1766
1767     BufferInfo bufferInfo = getBufferInfo(user, bufferId);
1768     if (!bufferInfo.isValid()) {
1769         db.rollback();
1770         return messagelist;
1771     }
1772
1773     QString queryName;
1774     QVariantList params;
1775     if (last == -1 && first == -1) {
1776         queryName = "select_messagesNewestK";
1777     }
1778     else if (last == -1) {
1779         queryName = "select_messagesNewerThan";
1780         params << first.toQint64();
1781     }
1782     else {
1783         queryName = "select_messagesRange";
1784         params << first.toQint64();
1785         params << last.toQint64();
1786     }
1787     params << bufferId.toInt();
1788     if (limit != -1)
1789         params << limit;
1790     else
1791         params << QVariant(QVariant::Int);
1792
1793     QSqlQuery query = executePreparedQuery(queryName, params, db);
1794
1795     if (!watchQuery(query)) {
1796         qDebug() << "select_messages failed";
1797         db.rollback();
1798         return messagelist;
1799     }
1800
1801     QDateTime timestamp;
1802     while (query.next()) {
1803         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
1804         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
1805         timestamp = query.value(1).toDateTime();
1806         timestamp.setTimeSpec(Qt::UTC);
1807         Message msg(timestamp,
1808             bufferInfo,
1809             (Message::Type)query.value(2).toInt(),
1810             query.value(8).toString(),
1811             query.value(4).toString(),
1812             query.value(5).toString(),
1813             query.value(6).toString(),
1814             query.value(7).toString(),
1815             (Message::Flags)query.value(3).toInt());
1816         msg.setMsgId(query.value(0).toLongLong());
1817         messagelist << msg;
1818     }
1819
1820     db.commit();
1821     return messagelist;
1822 }
1823
1824
1825 QList<Message> PostgreSqlStorage::requestMsgsFiltered(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
1826 {
1827     QList<Message> messagelist;
1828
1829     QSqlDatabase db = logDb();
1830     if (!beginReadOnlyTransaction(db)) {
1831         qWarning() << "PostgreSqlStorage::requestMsgs(): cannot start read only transaction!";
1832         qWarning() << " -" << qPrintable(db.lastError().text());
1833         return messagelist;
1834     }
1835
1836     BufferInfo bufferInfo = getBufferInfo(user, bufferId);
1837     if (!bufferInfo.isValid()) {
1838         db.rollback();
1839         return messagelist;
1840     }
1841
1842     QSqlQuery query(db);
1843     if (last == -1 && first == -1) {
1844         query.prepare(queryString("select_messagesNewestK_filtered"));
1845     } else if (last == -1) {
1846         query.prepare(queryString("select_messagesNewerThan_filtered"));
1847         query.bindValue(":first", first.toQint64());
1848     } else {
1849         query.prepare(queryString("select_messagesRange_filtered"));
1850         query.bindValue(":last", last.toQint64());
1851         query.bindValue(":first", first.toQint64());
1852     }
1853     query.bindValue(":buffer", bufferId.toInt());
1854     query.bindValue(":limit", limit);
1855     int typeRaw = type;
1856     query.bindValue(":type", typeRaw);
1857     int flagsRaw = flags;
1858     query.bindValue(":flags", flagsRaw);
1859
1860     safeExec(query);
1861     if (!watchQuery(query)) {
1862         qDebug() << "select_messages failed";
1863         db.rollback();
1864         return messagelist;
1865     }
1866
1867     QDateTime timestamp;
1868     while (query.next()) {
1869         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
1870         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
1871         timestamp = query.value(1).toDateTime();
1872         timestamp.setTimeSpec(Qt::UTC);
1873         Message msg(timestamp,
1874                     bufferInfo,
1875                     (Message::Type)query.value(2).toInt(),
1876                     query.value(8).toString(),
1877                     query.value(4).toString(),
1878                     query.value(5).toString(),
1879                     query.value(6).toString(),
1880                     query.value(7).toString(),
1881                     Message::Flags{query.value(3).toInt()});
1882         msg.setMsgId(query.value(0).toLongLong());
1883         messagelist << msg;
1884     }
1885
1886     db.commit();
1887     return messagelist;
1888 }
1889
1890
1891 QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
1892 {
1893     QList<Message> messagelist;
1894
1895     // requestBuffers uses it's own transaction.
1896     QHash<BufferId, BufferInfo> bufferInfoHash;
1897     foreach(BufferInfo bufferInfo, requestBuffers(user)) {
1898         bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
1899     }
1900
1901     QSqlDatabase db = logDb();
1902     if (!beginReadOnlyTransaction(db)) {
1903         qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
1904         qWarning() << " -" << qPrintable(db.lastError().text());
1905         return messagelist;
1906     }
1907
1908     QSqlQuery query(db);
1909     if (last == -1) {
1910         query.prepare(queryString("select_messagesAllNew"));
1911     }
1912     else {
1913         query.prepare(queryString("select_messagesAll"));
1914         query.bindValue(":lastmsg", last.toQint64());
1915     }
1916     query.bindValue(":userid", user.toInt());
1917     query.bindValue(":firstmsg", first.toQint64());
1918     safeExec(query);
1919     if (!watchQuery(query)) {
1920         db.rollback();
1921         return messagelist;
1922     }
1923
1924     QDateTime timestamp;
1925     for (int i = 0; i < limit && query.next(); i++) {
1926         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
1927         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
1928         timestamp = query.value(2).toDateTime();
1929         timestamp.setTimeSpec(Qt::UTC);
1930         Message msg(timestamp,
1931             bufferInfoHash[query.value(1).toInt()],
1932             (Message::Type)query.value(3).toInt(),
1933             query.value(9).toString(),
1934             query.value(5).toString(),
1935             query.value(6).toString(),
1936             query.value(7).toString(),
1937             query.value(8).toString(),
1938             (Message::Flags)query.value(4).toInt());
1939         msg.setMsgId(query.value(0).toLongLong());
1940         messagelist << msg;
1941     }
1942
1943     db.commit();
1944     return messagelist;
1945 }
1946
1947
1948 QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
1949 {
1950     QList<Message> messagelist;
1951
1952     // requestBuffers uses it's own transaction.
1953     QHash<BufferId, BufferInfo> bufferInfoHash;
1954             foreach(BufferInfo bufferInfo, requestBuffers(user)) {
1955             bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
1956         }
1957
1958     QSqlDatabase db = logDb();
1959     if (!beginReadOnlyTransaction(db)) {
1960         qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
1961         qWarning() << " -" << qPrintable(db.lastError().text());
1962         return messagelist;
1963     }
1964
1965     QSqlQuery query(db);
1966     if (last == -1) {
1967         query.prepare(queryString("select_messagesAllNew_filtered"));
1968     }
1969     else {
1970         query.prepare(queryString("select_messagesAll_filtered"));
1971         query.bindValue(":lastmsg", last.toQint64());
1972     }
1973     query.bindValue(":userid", user.toInt());
1974     query.bindValue(":firstmsg", first.toQint64());
1975
1976     int typeRaw = type;
1977     query.bindValue(":type", typeRaw);
1978
1979     int flagsRaw = flags;
1980     query.bindValue(":flags", flagsRaw);
1981
1982     safeExec(query);
1983     if (!watchQuery(query)) {
1984         db.rollback();
1985         return messagelist;
1986     }
1987
1988     QDateTime timestamp;
1989     for (int i = 0; i < limit && query.next(); i++) {
1990         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
1991         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
1992         timestamp = query.value(2).toDateTime();
1993         timestamp.setTimeSpec(Qt::UTC);
1994         Message msg(timestamp,
1995                     bufferInfoHash[query.value(1).toInt()],
1996                     (Message::Type)query.value(3).toInt(),
1997                     query.value(9).toString(),
1998                     query.value(5).toString(),
1999                     query.value(6).toString(),
2000                     query.value(7).toString(),
2001                     query.value(8).toString(),
2002                     Message::Flags{query.value(4).toInt()});
2003         msg.setMsgId(query.value(0).toLongLong());
2004         messagelist << msg;
2005     }
2006
2007     db.commit();
2008     return messagelist;
2009 }
2010
2011 QMap<UserId, QString> PostgreSqlStorage::getAllAuthUserNames()
2012 {
2013     QMap<UserId, QString> authusernames;
2014     QSqlQuery query(logDb());
2015     query.prepare(queryString("select_all_authusernames"));
2016     safeExec(query);
2017     watchQuery(query);
2018
2019     while (query.next()) {
2020         authusernames[query.value(0).toInt()] = query.value(1).toString();
2021     }
2022     return authusernames;
2023 }
2024
2025
2026 // void PostgreSqlStorage::safeExec(QSqlQuery &query) {
2027 //   qDebug() << "PostgreSqlStorage::safeExec";
2028 //   qDebug() << "   executing:\n" << query.executedQuery();
2029 //   qDebug() << "   bound Values:";
2030 //   QList<QVariant> list = query.boundValues().values();
2031 //   for (int i = 0; i < list.size(); ++i)
2032 //     qCritical() << i << ": " << list.at(i).toString().toLatin1().data();
2033
2034 //   query.exec();
2035
2036 //   qDebug() << "Success:" << !query.lastError().isValid();
2037 //   qDebug();
2038
2039 //   if(!query.lastError().isValid())
2040 //     return;
2041
2042 //   qDebug() << "==================== ERROR ====================";
2043 //   watchQuery(query);
2044 //   qDebug() << "===============================================";
2045 //   qDebug();
2046 //   return;
2047 // }
2048
2049
2050 bool PostgreSqlStorage::beginTransaction(QSqlDatabase &db)
2051 {
2052     bool result = db.transaction();
2053     if (!db.isOpen()) {
2054         db = logDb();
2055         result = db.transaction();
2056     }
2057     return result;
2058 }
2059
2060 bool PostgreSqlStorage::beginReadOnlyTransaction(QSqlDatabase &db)
2061 {
2062     QSqlQuery query = db.exec("BEGIN TRANSACTION READ ONLY");
2063     if (!db.isOpen()) {
2064         db = logDb();
2065         query = db.exec("BEGIN TRANSACTION READ ONLY");
2066     }
2067     return !query.lastError().isValid();
2068 }
2069
2070
2071 QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString &paramstring, QSqlDatabase &db)
2072 {
2073     // Query preparing is done lazily. That means that instead of always checking if the query is already prepared
2074     // we just EXECUTE and catch the error
2075     QSqlQuery query;
2076
2077     db.exec("SAVEPOINT quassel_prepare_query");
2078     if (paramstring.isNull()) {
2079         query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
2080     }
2081     else {
2082         query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
2083     }
2084
2085     if (!db.isOpen() || db.lastError().isValid()) {
2086         // If the query failed because the DB connection was down, reopen the connection and start a new transaction.
2087         if (!db.isOpen()) {
2088             db = logDb();
2089             if (!beginTransaction(db)) {
2090                 qWarning() << "PostgreSqlStorage::prepareAndExecuteQuery(): cannot start transaction while recovering from connection loss!";
2091                 qWarning() << " -" << qPrintable(db.lastError().text());
2092                 return query;
2093             }
2094             db.exec("SAVEPOINT quassel_prepare_query");
2095         } else {
2096             db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
2097         }
2098
2099         // and once again: Qt leaves us without error codes so we either parse (language dependent(!)) strings
2100         // or we just guess the error. As we're only interested in unprepared queries, this will be our guess. :)
2101         QSqlQuery checkQuery = db.exec(QString("SELECT count(name) FROM pg_prepared_statements WHERE name = 'quassel_%1' AND from_sql = TRUE").arg(queryname.toLower()));
2102         checkQuery.first();
2103         if (checkQuery.value(0).toInt() == 0) {
2104             db.exec(QString("PREPARE quassel_%1 AS %2").arg(queryname).arg(queryString(queryname)));
2105             if (db.lastError().isValid()) {
2106                 qWarning() << "PostgreSqlStorage::prepareQuery(): unable to prepare query:" << queryname << "AS" << queryString(queryname);
2107                 qWarning() << "  Error:" << db.lastError().text();
2108                 return QSqlQuery(db);
2109             }
2110         }
2111         // we always execute the query again, even if the query was already prepared.
2112         // this ensures, that the error is properly propagated to the calling function
2113         // (otherwise the last call would be the testing select to pg_prepared_statements
2114         // which always gives a proper result and the error would be lost)
2115         if (paramstring.isNull()) {
2116             query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
2117         }
2118         else {
2119             query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
2120         }
2121     }
2122     else {
2123         // only release the SAVEPOINT
2124         db.exec("RELEASE SAVEPOINT quassel_prepare_query");
2125     }
2126     return query;
2127 }
2128
2129
2130 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList &params, QSqlDatabase &db)
2131 {
2132     QSqlDriver *driver = db.driver();
2133
2134     QStringList paramStrings;
2135     QSqlField field;
2136     for (int i = 0; i < params.count(); i++) {
2137         const QVariant &value = params.at(i);
2138         field.setType(value.type());
2139         if (value.isNull())
2140             field.clear();
2141         else
2142             field.setValue(value);
2143
2144         paramStrings << driver->formatValue(field);
2145     }
2146
2147     if (params.isEmpty()) {
2148         return prepareAndExecuteQuery(queryname, db);
2149     }
2150     else {
2151         return prepareAndExecuteQuery(queryname, paramStrings.join(", "), db);
2152     }
2153 }
2154
2155
2156 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant &param, QSqlDatabase &db)
2157 {
2158     QSqlField field;
2159     field.setType(param.type());
2160     if (param.isNull())
2161         field.clear();
2162     else
2163         field.setValue(param);
2164
2165     QString paramString = db.driver()->formatValue(field);
2166     return prepareAndExecuteQuery(queryname, paramString, db);
2167 }
2168
2169
2170 void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlDatabase &db)
2171 {
2172     db.exec(QString("DEALLOCATE quassel_%1").arg(queryname));
2173 }
2174
2175
2176 void PostgreSqlStorage::safeExec(QSqlQuery &query)
2177 {
2178     // If the query fails due to the connection being gone, it seems to cause
2179     // exec() to return false but no lastError to be set
2180     if(!query.exec() && !query.lastError().isValid())
2181     {
2182         QSqlDatabase db = logDb();
2183         QSqlQuery retryQuery(db);
2184         retryQuery.prepare(query.lastQuery());
2185         QMapIterator<QString, QVariant> i(query.boundValues());
2186         while (i.hasNext())
2187         {
2188             i.next();
2189             retryQuery.bindValue(i.key(),i.value());
2190         }
2191         query = retryQuery;
2192         query.exec();
2193     }
2194 }
2195
2196 // ========================================
2197 //  PostgreSqlMigrationWriter
2198 // ========================================
2199 PostgreSqlMigrationWriter::PostgreSqlMigrationWriter()
2200     : PostgreSqlStorage()
2201 {
2202 }
2203
2204
2205 bool PostgreSqlMigrationWriter::prepareQuery(MigrationObject mo)
2206 {
2207     QString query;
2208     switch (mo) {
2209     case QuasselUser:
2210         query = queryString("migrate_write_quasseluser");
2211         break;
2212     case Sender:
2213         query = queryString("migrate_write_sender");
2214         break;
2215     case Identity:
2216         _validIdentities.clear();
2217         query = queryString("migrate_write_identity");
2218         break;
2219     case IdentityNick:
2220         query = queryString("migrate_write_identity_nick");
2221         break;
2222     case Network:
2223         query = queryString("migrate_write_network");
2224         break;
2225     case Buffer:
2226         query = queryString("migrate_write_buffer");
2227         break;
2228     case Backlog:
2229         query = queryString("migrate_write_backlog");
2230         break;
2231     case IrcServer:
2232         query = queryString("migrate_write_ircserver");
2233         break;
2234     case UserSetting:
2235         query = queryString("migrate_write_usersetting");
2236         break;
2237     case CoreState:
2238         query = queryString("migrate_write_corestate");
2239         break;
2240     }
2241     newQuery(query, logDb());
2242     return true;
2243 }
2244
2245
2246 //bool PostgreSqlMigrationWriter::writeUser(const QuasselUserMO &user) {
2247 bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user)
2248 {
2249     bindValue(0, user.id.toInt());
2250     bindValue(1, user.username);
2251     bindValue(2, user.password);
2252     bindValue(3, user.hashversion);
2253     bindValue(4, user.authenticator);
2254     return exec();
2255 }
2256
2257
2258 //bool PostgreSqlMigrationWriter::writeSender(const SenderMO &sender) {
2259 bool PostgreSqlMigrationWriter::writeMo(const SenderMO &sender)
2260 {
2261     bindValue(0, sender.senderId);
2262     bindValue(1, sender.sender);
2263     bindValue(2, sender.realname);
2264     bindValue(3, sender.avatarurl);
2265     return exec();
2266 }
2267
2268
2269 //bool PostgreSqlMigrationWriter::writeIdentity(const IdentityMO &identity) {
2270 bool PostgreSqlMigrationWriter::writeMo(const IdentityMO &identity)
2271 {
2272     _validIdentities << identity.id.toInt();
2273     bindValue(0, identity.id.toInt());
2274     bindValue(1, identity.userid.toInt());
2275     bindValue(2, identity.identityname);
2276     bindValue(3, identity.realname);
2277     bindValue(4, identity.awayNick);
2278     bindValue(5, identity.awayNickEnabled);
2279     bindValue(6, identity.awayReason);
2280     bindValue(7, identity.awayReasonEnabled);
2281     bindValue(8, identity.autoAwayEnabled);
2282     bindValue(9, identity.autoAwayTime);
2283     bindValue(10, identity.autoAwayReason);
2284     bindValue(11, identity.autoAwayReasonEnabled);
2285     bindValue(12, identity.detachAwayEnabled);
2286     bindValue(13, identity.detachAwayReason);
2287     bindValue(14, identity.detachAwayReasonEnabled);
2288     bindValue(15, identity.ident);
2289     bindValue(16, identity.kickReason);
2290     bindValue(17, identity.partReason);
2291     bindValue(18, identity.quitReason);
2292     bindValue(19, identity.sslCert);
2293     bindValue(20, identity.sslKey);
2294     return exec();
2295 }
2296
2297
2298 //bool PostgreSqlMigrationWriter::writeIdentityNick(const IdentityNickMO &identityNick) {
2299 bool PostgreSqlMigrationWriter::writeMo(const IdentityNickMO &identityNick)
2300 {
2301     bindValue(0, identityNick.nickid);
2302     bindValue(1, identityNick.identityId.toInt());
2303     bindValue(2, identityNick.nick);
2304     return exec();
2305 }
2306
2307
2308 //bool PostgreSqlMigrationWriter::writeNetwork(const NetworkMO &network) {
2309 bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network)
2310 {
2311     bindValue(0, network.networkid.toInt());
2312     bindValue(1, network.userid.toInt());
2313     bindValue(2, network.networkname);
2314     if (_validIdentities.contains(network.identityid.toInt()))
2315         bindValue(3, network.identityid.toInt());
2316     else
2317         bindValue(3, QVariant());
2318     bindValue(4, network.encodingcodec);
2319     bindValue(5, network.decodingcodec);
2320     bindValue(6, network.servercodec);
2321     bindValue(7, network.userandomserver);
2322     bindValue(8, network.perform);
2323     bindValue(9, network.useautoidentify);
2324     bindValue(10, network.autoidentifyservice);
2325     bindValue(11, network.autoidentifypassword);
2326     bindValue(12, network.useautoreconnect);
2327     bindValue(13, network.autoreconnectinterval);
2328     bindValue(14, network.autoreconnectretries);
2329     bindValue(15, network.unlimitedconnectretries);
2330     bindValue(16, network.rejoinchannels);
2331     bindValue(17, network.connected);
2332     bindValue(18, network.usermode);
2333     bindValue(19, network.awaymessage);
2334     bindValue(20, network.attachperform);
2335     bindValue(21, network.detachperform);
2336     bindValue(22, network.usesasl);
2337     bindValue(23, network.saslaccount);
2338     bindValue(24, network.saslpassword);
2339     // Custom rate limiting
2340     bindValue(25, network.usecustommessagerate);
2341     bindValue(26, network.messagerateburstsize);
2342     bindValue(27, network.messageratedelay);
2343     bindValue(28, network.unlimitedmessagerate);
2344     return exec();
2345 }
2346
2347
2348 //bool PostgreSqlMigrationWriter::writeBuffer(const BufferMO &buffer) {
2349 bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
2350 {
2351     bindValue(0, buffer.bufferid.toInt());
2352     bindValue(1, buffer.userid.toInt());
2353     bindValue(2, buffer.groupid);
2354     bindValue(3, buffer.networkid.toInt());
2355     bindValue(4, buffer.buffername);
2356     bindValue(5, buffer.buffercname);
2357     bindValue(6, (int)buffer.buffertype);
2358     bindValue(7, buffer.lastmsgid);
2359     bindValue(8, buffer.lastseenmsgid);
2360     bindValue(9, buffer.markerlinemsgid);
2361     bindValue(10, buffer.bufferactivity);
2362     bindValue(11, buffer.highlightcount);
2363     bindValue(12, buffer.key);
2364     bindValue(13, buffer.joined);
2365     bindValue(14, buffer.cipher);
2366     return exec();
2367 }
2368
2369
2370 //bool PostgreSqlMigrationWriter::writeBacklog(const BacklogMO &backlog) {
2371 bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog)
2372 {
2373     bindValue(0, backlog.messageid.toQint64());
2374     bindValue(1, backlog.time);
2375     bindValue(2, backlog.bufferid.toInt());
2376     bindValue(3, backlog.type);
2377     bindValue(4, (int)backlog.flags);
2378     bindValue(5, backlog.senderid);
2379     bindValue(6, backlog.senderprefixes);
2380     bindValue(7, backlog.message);
2381     return exec();
2382 }
2383
2384
2385 //bool PostgreSqlMigrationWriter::writeIrcServer(const IrcServerMO &ircserver) {
2386 bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)
2387 {
2388     bindValue(0, ircserver.serverid);
2389     bindValue(1, ircserver.userid.toInt());
2390     bindValue(2, ircserver.networkid.toInt());
2391     bindValue(3, ircserver.hostname);
2392     bindValue(4, ircserver.port);
2393     bindValue(5, ircserver.password);
2394     bindValue(6, ircserver.ssl);
2395     bindValue(7, ircserver.sslversion);
2396     bindValue(8, ircserver.useproxy);
2397     bindValue(9, ircserver.proxytype);
2398     bindValue(10, ircserver.proxyhost);
2399     bindValue(11, ircserver.proxyport);
2400     bindValue(12, ircserver.proxyuser);
2401     bindValue(13, ircserver.proxypass);
2402     bindValue(14, ircserver.sslverify);
2403     return exec();
2404 }
2405
2406
2407 //bool PostgreSqlMigrationWriter::writeUserSetting(const UserSettingMO &userSetting) {
2408 bool PostgreSqlMigrationWriter::writeMo(const UserSettingMO &userSetting)
2409 {
2410     bindValue(0, userSetting.userid.toInt());
2411     bindValue(1, userSetting.settingname);
2412     bindValue(2, userSetting.settingvalue);
2413     return exec();
2414 }
2415
2416 bool PostgreSqlMigrationWriter::writeMo(const CoreStateMO &coreState)
2417 {
2418     bindValue(0, coreState.key);
2419     bindValue(1, coreState.value);
2420     return exec();
2421 }
2422
2423
2424 bool PostgreSqlMigrationWriter::postProcess()
2425 {
2426     QSqlDatabase db = logDb();
2427     QList<Sequence> sequences;
2428     sequences << Sequence("backlog", "messageid")
2429               << Sequence("buffer", "bufferid")
2430               << Sequence("identity", "identityid")
2431               << Sequence("identity_nick", "nickid")
2432               << Sequence("ircserver", "serverid")
2433               << Sequence("network", "networkid")
2434               << Sequence("quasseluser", "userid")
2435               << Sequence("sender", "senderid");
2436     QList<Sequence>::const_iterator iter;
2437     for (iter = sequences.constBegin(); iter != sequences.constEnd(); ++iter) {
2438         resetQuery();
2439         newQuery(QString("SELECT setval('%1_%2_seq', max(%2)) FROM %1").arg(iter->table, iter->field), db);
2440         if (!exec())
2441             return false;
2442     }
2443
2444     // Update the lastmsgid for all existing buffers.
2445     resetQuery();
2446     newQuery(QString("SELECT populate_lastmsgid()"), db);
2447     if (!exec())
2448         return false;
2449     return true;
2450 }