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