50871b566b1ab9f05a53383ef812698311c896ec
[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     } else if (last == -1) {
1913         query.prepare(queryString("select_messagesNewerThan_filtered"));
1914         query.bindValue(":first", first.toQint64());
1915     } else {
1916         query.prepare(queryString("select_messagesRange_filtered"));
1917         query.bindValue(":last", last.toQint64());
1918         query.bindValue(":first", first.toQint64());
1919     }
1920     query.bindValue(":buffer", bufferId.toInt());
1921     query.bindValue(":limit", limit);
1922     int typeRaw = type;
1923     query.bindValue(":type", typeRaw);
1924     int flagsRaw = flags;
1925     query.bindValue(":flags", flagsRaw);
1926
1927     safeExec(query);
1928     if (!watchQuery(query)) {
1929         qDebug() << "select_messages failed";
1930         db.rollback();
1931         return messagelist;
1932     }
1933
1934     QDateTime timestamp;
1935     while (query.next()) {
1936         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
1937         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
1938         timestamp = query.value(1).toDateTime();
1939         timestamp.setTimeSpec(Qt::UTC);
1940         Message msg(timestamp,
1941                     bufferInfo,
1942                     (Message::Type)query.value(2).toInt(),
1943                     query.value(8).toString(),
1944                     query.value(4).toString(),
1945                     query.value(5).toString(),
1946                     query.value(6).toString(),
1947                     query.value(7).toString(),
1948                     Message::Flags{query.value(3).toInt()});
1949         msg.setMsgId(query.value(0).toLongLong());
1950         messagelist << msg;
1951     }
1952
1953     db.commit();
1954     return messagelist;
1955 }
1956
1957
1958 QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
1959 {
1960     QList<Message> messagelist;
1961
1962     // requestBuffers uses it's own transaction.
1963     QHash<BufferId, BufferInfo> bufferInfoHash;
1964     foreach(BufferInfo bufferInfo, requestBuffers(user)) {
1965         bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
1966     }
1967
1968     QSqlDatabase db = logDb();
1969     if (!beginReadOnlyTransaction(db)) {
1970         qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
1971         qWarning() << " -" << qPrintable(db.lastError().text());
1972         return messagelist;
1973     }
1974
1975     QSqlQuery query(db);
1976     if (last == -1) {
1977         query.prepare(queryString("select_messagesAllNew"));
1978     }
1979     else {
1980         query.prepare(queryString("select_messagesAll"));
1981         query.bindValue(":lastmsg", last.toQint64());
1982     }
1983     query.bindValue(":userid", user.toInt());
1984     query.bindValue(":firstmsg", first.toQint64());
1985     safeExec(query);
1986     if (!watchQuery(query)) {
1987         db.rollback();
1988         return messagelist;
1989     }
1990
1991     QDateTime timestamp;
1992     for (int i = 0; i < limit && query.next(); i++) {
1993         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
1994         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
1995         timestamp = query.value(2).toDateTime();
1996         timestamp.setTimeSpec(Qt::UTC);
1997         Message msg(timestamp,
1998             bufferInfoHash[query.value(1).toInt()],
1999             (Message::Type)query.value(3).toInt(),
2000             query.value(9).toString(),
2001             query.value(5).toString(),
2002             query.value(6).toString(),
2003             query.value(7).toString(),
2004             query.value(8).toString(),
2005             (Message::Flags)query.value(4).toInt());
2006         msg.setMsgId(query.value(0).toLongLong());
2007         messagelist << msg;
2008     }
2009
2010     db.commit();
2011     return messagelist;
2012 }
2013
2014
2015 QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
2016 {
2017     QList<Message> messagelist;
2018
2019     // requestBuffers uses it's own transaction.
2020     QHash<BufferId, BufferInfo> bufferInfoHash;
2021             foreach(BufferInfo bufferInfo, requestBuffers(user)) {
2022             bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
2023         }
2024
2025     QSqlDatabase db = logDb();
2026     if (!beginReadOnlyTransaction(db)) {
2027         qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
2028         qWarning() << " -" << qPrintable(db.lastError().text());
2029         return messagelist;
2030     }
2031
2032     QSqlQuery query(db);
2033     if (last == -1) {
2034         query.prepare(queryString("select_messagesAllNew_filtered"));
2035     }
2036     else {
2037         query.prepare(queryString("select_messagesAll_filtered"));
2038         query.bindValue(":lastmsg", last.toQint64());
2039     }
2040     query.bindValue(":userid", user.toInt());
2041     query.bindValue(":firstmsg", first.toQint64());
2042
2043     int typeRaw = type;
2044     query.bindValue(":type", typeRaw);
2045
2046     int flagsRaw = flags;
2047     query.bindValue(":flags", flagsRaw);
2048
2049     safeExec(query);
2050     if (!watchQuery(query)) {
2051         db.rollback();
2052         return messagelist;
2053     }
2054
2055     QDateTime timestamp;
2056     for (int i = 0; i < limit && query.next(); i++) {
2057         // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
2058         // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
2059         timestamp = query.value(2).toDateTime();
2060         timestamp.setTimeSpec(Qt::UTC);
2061         Message msg(timestamp,
2062                     bufferInfoHash[query.value(1).toInt()],
2063                     (Message::Type)query.value(3).toInt(),
2064                     query.value(9).toString(),
2065                     query.value(5).toString(),
2066                     query.value(6).toString(),
2067                     query.value(7).toString(),
2068                     query.value(8).toString(),
2069                     Message::Flags{query.value(4).toInt()});
2070         msg.setMsgId(query.value(0).toLongLong());
2071         messagelist << msg;
2072     }
2073
2074     db.commit();
2075     return messagelist;
2076 }
2077
2078 QMap<UserId, QString> PostgreSqlStorage::getAllAuthUserNames()
2079 {
2080     QMap<UserId, QString> authusernames;
2081     QSqlQuery query(logDb());
2082     query.prepare(queryString("select_all_authusernames"));
2083     safeExec(query);
2084     watchQuery(query);
2085
2086     while (query.next()) {
2087         authusernames[query.value(0).toInt()] = query.value(1).toString();
2088     }
2089     return authusernames;
2090 }
2091
2092
2093 // void PostgreSqlStorage::safeExec(QSqlQuery &query) {
2094 //   qDebug() << "PostgreSqlStorage::safeExec";
2095 //   qDebug() << "   executing:\n" << query.executedQuery();
2096 //   qDebug() << "   bound Values:";
2097 //   QList<QVariant> list = query.boundValues().values();
2098 //   for (int i = 0; i < list.size(); ++i)
2099 //     qCritical() << i << ": " << list.at(i).toString().toLatin1().data();
2100
2101 //   query.exec();
2102
2103 //   qDebug() << "Success:" << !query.lastError().isValid();
2104 //   qDebug();
2105
2106 //   if(!query.lastError().isValid())
2107 //     return;
2108
2109 //   qDebug() << "==================== ERROR ====================";
2110 //   watchQuery(query);
2111 //   qDebug() << "===============================================";
2112 //   qDebug();
2113 //   return;
2114 // }
2115
2116
2117 bool PostgreSqlStorage::beginTransaction(QSqlDatabase &db)
2118 {
2119     bool result = db.transaction();
2120     if (!db.isOpen()) {
2121         db = logDb();
2122         result = db.transaction();
2123     }
2124     return result;
2125 }
2126
2127 bool PostgreSqlStorage::beginReadOnlyTransaction(QSqlDatabase &db)
2128 {
2129     QSqlQuery query = db.exec("BEGIN TRANSACTION READ ONLY");
2130     if (!db.isOpen()) {
2131         db = logDb();
2132         query = db.exec("BEGIN TRANSACTION READ ONLY");
2133     }
2134     return !query.lastError().isValid();
2135 }
2136
2137
2138 QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString &paramstring, QSqlDatabase &db)
2139 {
2140     // Query preparing is done lazily. That means that instead of always checking if the query is already prepared
2141     // we just EXECUTE and catch the error
2142     QSqlQuery query;
2143
2144     db.exec("SAVEPOINT quassel_prepare_query");
2145     if (paramstring.isNull()) {
2146         query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
2147     }
2148     else {
2149         query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
2150     }
2151
2152     if (!db.isOpen() || db.lastError().isValid()) {
2153         // If the query failed because the DB connection was down, reopen the connection and start a new transaction.
2154         if (!db.isOpen()) {
2155             db = logDb();
2156             if (!beginTransaction(db)) {
2157                 qWarning() << "PostgreSqlStorage::prepareAndExecuteQuery(): cannot start transaction while recovering from connection loss!";
2158                 qWarning() << " -" << qPrintable(db.lastError().text());
2159                 return query;
2160             }
2161             db.exec("SAVEPOINT quassel_prepare_query");
2162         } else {
2163             db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
2164         }
2165
2166         // and once again: Qt leaves us without error codes so we either parse (language dependent(!)) strings
2167         // or we just guess the error. As we're only interested in unprepared queries, this will be our guess. :)
2168         QSqlQuery checkQuery = db.exec(QString("SELECT count(name) FROM pg_prepared_statements WHERE name = 'quassel_%1' AND from_sql = TRUE").arg(queryname.toLower()));
2169         checkQuery.first();
2170         if (checkQuery.value(0).toInt() == 0) {
2171             db.exec(QString("PREPARE quassel_%1 AS %2").arg(queryname).arg(queryString(queryname)));
2172             if (db.lastError().isValid()) {
2173                 qWarning() << "PostgreSqlStorage::prepareQuery(): unable to prepare query:" << queryname << "AS" << queryString(queryname);
2174                 qWarning() << "  Error:" << db.lastError().text();
2175                 return QSqlQuery(db);
2176             }
2177         }
2178         // we always execute the query again, even if the query was already prepared.
2179         // this ensures, that the error is properly propagated to the calling function
2180         // (otherwise the last call would be the testing select to pg_prepared_statements
2181         // which always gives a proper result and the error would be lost)
2182         if (paramstring.isNull()) {
2183             query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
2184         }
2185         else {
2186             query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
2187         }
2188     }
2189     else {
2190         // only release the SAVEPOINT
2191         db.exec("RELEASE SAVEPOINT quassel_prepare_query");
2192     }
2193     return query;
2194 }
2195
2196
2197 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList &params, QSqlDatabase &db)
2198 {
2199     QSqlDriver *driver = db.driver();
2200
2201     QStringList paramStrings;
2202     QSqlField field;
2203     for (int i = 0; i < params.count(); i++) {
2204         const QVariant &value = params.at(i);
2205         field.setType(value.type());
2206         if (value.isNull())
2207             field.clear();
2208         else
2209             field.setValue(value);
2210
2211         paramStrings << driver->formatValue(field);
2212     }
2213
2214     if (params.isEmpty()) {
2215         return prepareAndExecuteQuery(queryname, db);
2216     }
2217     else {
2218         return prepareAndExecuteQuery(queryname, paramStrings.join(", "), db);
2219     }
2220 }
2221
2222
2223 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant &param, QSqlDatabase &db)
2224 {
2225     QSqlField field;
2226     field.setType(param.type());
2227     if (param.isNull())
2228         field.clear();
2229     else
2230         field.setValue(param);
2231
2232     QString paramString = db.driver()->formatValue(field);
2233     return prepareAndExecuteQuery(queryname, paramString, db);
2234 }
2235
2236
2237 void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlDatabase &db)
2238 {
2239     db.exec(QString("DEALLOCATE quassel_%1").arg(queryname));
2240 }
2241
2242
2243 void PostgreSqlStorage::safeExec(QSqlQuery &query)
2244 {
2245     // If the query fails due to the connection being gone, it seems to cause
2246     // exec() to return false but no lastError to be set
2247     if(!query.exec() && !query.lastError().isValid())
2248     {
2249         QSqlDatabase db = logDb();
2250         QSqlQuery retryQuery(db);
2251         retryQuery.prepare(query.lastQuery());
2252         QMapIterator<QString, QVariant> i(query.boundValues());
2253         while (i.hasNext())
2254         {
2255             i.next();
2256             retryQuery.bindValue(i.key(),i.value());
2257         }
2258         query = retryQuery;
2259         query.exec();
2260     }
2261 }
2262
2263 // ========================================
2264 //  PostgreSqlMigrationWriter
2265 // ========================================
2266 PostgreSqlMigrationWriter::PostgreSqlMigrationWriter()
2267     : PostgreSqlStorage()
2268 {
2269 }
2270
2271
2272 bool PostgreSqlMigrationWriter::prepareQuery(MigrationObject mo)
2273 {
2274     QString query;
2275     switch (mo) {
2276     case QuasselUser:
2277         query = queryString("migrate_write_quasseluser");
2278         break;
2279     case Sender:
2280         query = queryString("migrate_write_sender");
2281         break;
2282     case Identity:
2283         _validIdentities.clear();
2284         query = queryString("migrate_write_identity");
2285         break;
2286     case IdentityNick:
2287         query = queryString("migrate_write_identity_nick");
2288         break;
2289     case Network:
2290         query = queryString("migrate_write_network");
2291         break;
2292     case Buffer:
2293         query = queryString("migrate_write_buffer");
2294         break;
2295     case Backlog:
2296         query = queryString("migrate_write_backlog");
2297         break;
2298     case IrcServer:
2299         query = queryString("migrate_write_ircserver");
2300         break;
2301     case UserSetting:
2302         query = queryString("migrate_write_usersetting");
2303         break;
2304     case CoreState:
2305         query = queryString("migrate_write_corestate");
2306         break;
2307     }
2308     newQuery(query, logDb());
2309     return true;
2310 }
2311
2312
2313 //bool PostgreSqlMigrationWriter::writeUser(const QuasselUserMO &user) {
2314 bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user)
2315 {
2316     bindValue(0, user.id.toInt());
2317     bindValue(1, user.username);
2318     bindValue(2, user.password);
2319     bindValue(3, user.hashversion);
2320     bindValue(4, user.authenticator);
2321     return exec();
2322 }
2323
2324
2325 //bool PostgreSqlMigrationWriter::writeSender(const SenderMO &sender) {
2326 bool PostgreSqlMigrationWriter::writeMo(const SenderMO &sender)
2327 {
2328     bindValue(0, sender.senderId);
2329     bindValue(1, sender.sender);
2330     bindValue(2, sender.realname);
2331     bindValue(3, sender.avatarurl);
2332     return exec();
2333 }
2334
2335
2336 //bool PostgreSqlMigrationWriter::writeIdentity(const IdentityMO &identity) {
2337 bool PostgreSqlMigrationWriter::writeMo(const IdentityMO &identity)
2338 {
2339     _validIdentities << identity.id.toInt();
2340     bindValue(0, identity.id.toInt());
2341     bindValue(1, identity.userid.toInt());
2342     bindValue(2, identity.identityname);
2343     bindValue(3, identity.realname);
2344     bindValue(4, identity.awayNick);
2345     bindValue(5, identity.awayNickEnabled);
2346     bindValue(6, identity.awayReason);
2347     bindValue(7, identity.awayReasonEnabled);
2348     bindValue(8, identity.autoAwayEnabled);
2349     bindValue(9, identity.autoAwayTime);
2350     bindValue(10, identity.autoAwayReason);
2351     bindValue(11, identity.autoAwayReasonEnabled);
2352     bindValue(12, identity.detachAwayEnabled);
2353     bindValue(13, identity.detachAwayReason);
2354     bindValue(14, identity.detachAwayReasonEnabled);
2355     bindValue(15, identity.ident);
2356     bindValue(16, identity.kickReason);
2357     bindValue(17, identity.partReason);
2358     bindValue(18, identity.quitReason);
2359     bindValue(19, identity.sslCert);
2360     bindValue(20, identity.sslKey);
2361     return exec();
2362 }
2363
2364
2365 //bool PostgreSqlMigrationWriter::writeIdentityNick(const IdentityNickMO &identityNick) {
2366 bool PostgreSqlMigrationWriter::writeMo(const IdentityNickMO &identityNick)
2367 {
2368     bindValue(0, identityNick.nickid);
2369     bindValue(1, identityNick.identityId.toInt());
2370     bindValue(2, identityNick.nick);
2371     return exec();
2372 }
2373
2374
2375 //bool PostgreSqlMigrationWriter::writeNetwork(const NetworkMO &network) {
2376 bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network)
2377 {
2378     bindValue(0, network.networkid.toInt());
2379     bindValue(1, network.userid.toInt());
2380     bindValue(2, network.networkname);
2381     if (_validIdentities.contains(network.identityid.toInt()))
2382         bindValue(3, network.identityid.toInt());
2383     else
2384         bindValue(3, QVariant());
2385     bindValue(4, network.encodingcodec);
2386     bindValue(5, network.decodingcodec);
2387     bindValue(6, network.servercodec);
2388     bindValue(7, network.userandomserver);
2389     bindValue(8, network.perform);
2390     bindValue(9, network.useautoidentify);
2391     bindValue(10, network.autoidentifyservice);
2392     bindValue(11, network.autoidentifypassword);
2393     bindValue(12, network.useautoreconnect);
2394     bindValue(13, network.autoreconnectinterval);
2395     bindValue(14, network.autoreconnectretries);
2396     bindValue(15, network.unlimitedconnectretries);
2397     bindValue(16, network.rejoinchannels);
2398     bindValue(17, network.connected);
2399     bindValue(18, network.usermode);
2400     bindValue(19, network.awaymessage);
2401     bindValue(20, network.attachperform);
2402     bindValue(21, network.detachperform);
2403     bindValue(22, network.usesasl);
2404     bindValue(23, network.saslaccount);
2405     bindValue(24, network.saslpassword);
2406     // Custom rate limiting
2407     bindValue(25, network.usecustommessagerate);
2408     bindValue(26, network.messagerateburstsize);
2409     bindValue(27, network.messageratedelay);
2410     bindValue(28, network.unlimitedmessagerate);
2411     return exec();
2412 }
2413
2414
2415 //bool PostgreSqlMigrationWriter::writeBuffer(const BufferMO &buffer) {
2416 bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
2417 {
2418     bindValue(0, buffer.bufferid.toInt());
2419     bindValue(1, buffer.userid.toInt());
2420     bindValue(2, buffer.groupid);
2421     bindValue(3, buffer.networkid.toInt());
2422     bindValue(4, buffer.buffername);
2423     bindValue(5, buffer.buffercname);
2424     bindValue(6, (int)buffer.buffertype);
2425     bindValue(7, buffer.lastmsgid);
2426     bindValue(8, buffer.lastseenmsgid);
2427     bindValue(9, buffer.markerlinemsgid);
2428     bindValue(10, buffer.bufferactivity);
2429     bindValue(11, buffer.highlightcount);
2430     bindValue(12, buffer.key);
2431     bindValue(13, buffer.joined);
2432     bindValue(14, buffer.cipher);
2433     return exec();
2434 }
2435
2436
2437 //bool PostgreSqlMigrationWriter::writeBacklog(const BacklogMO &backlog) {
2438 bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog)
2439 {
2440     bindValue(0, backlog.messageid.toQint64());
2441     bindValue(1, backlog.time);
2442     bindValue(2, backlog.bufferid.toInt());
2443     bindValue(3, backlog.type);
2444     bindValue(4, (int)backlog.flags);
2445     bindValue(5, backlog.senderid);
2446     bindValue(6, backlog.senderprefixes);
2447     bindValue(7, backlog.message);
2448     return exec();
2449 }
2450
2451
2452 //bool PostgreSqlMigrationWriter::writeIrcServer(const IrcServerMO &ircserver) {
2453 bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)
2454 {
2455     bindValue(0, ircserver.serverid);
2456     bindValue(1, ircserver.userid.toInt());
2457     bindValue(2, ircserver.networkid.toInt());
2458     bindValue(3, ircserver.hostname);
2459     bindValue(4, ircserver.port);
2460     bindValue(5, ircserver.password);
2461     bindValue(6, ircserver.ssl);
2462     bindValue(7, ircserver.sslversion);
2463     bindValue(8, ircserver.useproxy);
2464     bindValue(9, ircserver.proxytype);
2465     bindValue(10, ircserver.proxyhost);
2466     bindValue(11, ircserver.proxyport);
2467     bindValue(12, ircserver.proxyuser);
2468     bindValue(13, ircserver.proxypass);
2469     bindValue(14, ircserver.sslverify);
2470     return exec();
2471 }
2472
2473
2474 //bool PostgreSqlMigrationWriter::writeUserSetting(const UserSettingMO &userSetting) {
2475 bool PostgreSqlMigrationWriter::writeMo(const UserSettingMO &userSetting)
2476 {
2477     bindValue(0, userSetting.userid.toInt());
2478     bindValue(1, userSetting.settingname);
2479     bindValue(2, userSetting.settingvalue);
2480     return exec();
2481 }
2482
2483 bool PostgreSqlMigrationWriter::writeMo(const CoreStateMO &coreState)
2484 {
2485     bindValue(0, coreState.key);
2486     bindValue(1, coreState.value);
2487     return exec();
2488 }
2489
2490
2491 bool PostgreSqlMigrationWriter::postProcess()
2492 {
2493     QSqlDatabase db = logDb();
2494     QList<Sequence> sequences;
2495     sequences << Sequence("backlog", "messageid")
2496               << Sequence("buffer", "bufferid")
2497               << Sequence("identity", "identityid")
2498               << Sequence("identity_nick", "nickid")
2499               << Sequence("ircserver", "serverid")
2500               << Sequence("network", "networkid")
2501               << Sequence("quasseluser", "userid")
2502               << Sequence("sender", "senderid");
2503     QList<Sequence>::const_iterator iter;
2504     for (iter = sequences.constBegin(); iter != sequences.constEnd(); ++iter) {
2505         resetQuery();
2506         newQuery(QString("SELECT setval('%1_%2_seq', max(%2)) FROM %1").arg(iter->table, iter->field), db);
2507         if (!exec())
2508             return false;
2509     }
2510
2511     // Update the lastmsgid for all existing buffers.
2512     resetQuery();
2513     newQuery(QString("SELECT populate_lastmsgid()"), db);
2514     if (!exec())
2515         return false;
2516     return true;
2517 }