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