c911732a40c44fc69c373e7c7f6a55656c97b22d
[quassel.git] / src / core / abstractsqlstorage.h
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 #pragma once
22
23 #include "storage.h"
24
25 #include <memory>
26
27 #include <QList>
28 #include <QSqlDatabase>
29 #include <QSqlQuery>
30 #include <QSqlError>
31
32 class AbstractSqlMigrationReader;
33 class AbstractSqlMigrationWriter;
34
35 class AbstractSqlStorage : public Storage
36 {
37     Q_OBJECT
38
39 public:
40     AbstractSqlStorage(QObject *parent = 0);
41     virtual ~AbstractSqlStorage();
42
43     virtual std::unique_ptr<AbstractSqlMigrationReader> createMigrationReader() { return {}; }
44     virtual std::unique_ptr<AbstractSqlMigrationWriter> createMigrationWriter() { return {}; }
45
46     /**
47      * An SQL query with associated resource filename
48      */
49     struct SqlQueryResource {
50         QString queryString;   ///< SQL query string
51         QString queryFilename; ///< Path to the resource file providing this query
52
53         SqlQueryResource(const QString& queryString, const QString& queryFilename)
54             : queryString(std::move(queryString)),
55               queryFilename(std::move(queryFilename)) {}
56     };
57
58 public slots:
59     virtual State init(const QVariantMap &settings = QVariantMap(),
60                        const QProcessEnvironment &environment = {},
61                        bool loadFromEnvironment = false);
62     virtual bool setup(const QVariantMap &settings = QVariantMap(),
63                        const QProcessEnvironment &environment = {},
64                        bool loadFromEnvironment = false);
65
66 protected:
67     inline virtual void sync() {};
68
69     QSqlDatabase logDb();
70
71     /**
72      * Fetch an SQL query string by name and optional schema version
73      *
74      * Loads the named SQL query from the built-in SQL resource collection, returning it as a
75      * string.  If a version is specified, it'll be loaded from the schema version-specific folder
76      * instead.
77      *
78      * @see schemaVersion()
79      *
80      * @param[in] queryName  File name of the SQL query, minus the .sql extension
81      * @param[in] version
82      * @parblock
83      * SQL schema version; if 0, fetches from current version, otherwise loads from the specified
84      * schema version instead of the current schema files.
85      * @endparblock
86      * @return String with the requested SQL query, ready for parameter substitution
87      */
88     QString queryString(const QString &queryName, int version = 0);
89
90     /**
91      * Gets the collection of SQL setup queries and filenames to create a new database
92      *
93      * @return List of SQL query strings and filenames
94      */
95     QList<SqlQueryResource> setupQueries();
96
97     /**
98      * Gets the collection of SQL upgrade queries and filenames for a given schema version
99      *
100      * @param ver  SQL schema version
101      * @return List of SQL query strings and filenames
102      */
103     QList<SqlQueryResource> upgradeQueries(int ver);
104     bool upgradeDb();
105
106     bool watchQuery(QSqlQuery &query);
107
108     int schemaVersion();
109     virtual int installedSchemaVersion() { return -1; };
110
111     /**
112      * Update the stored schema version number, optionally clearing the record of mid-schema steps
113      *
114      * @param newVersion        New schema version number
115      * @param clearUpgradeStep  If true, clear the record of any in-progress schema upgrades
116      * @return
117      */
118     virtual bool updateSchemaVersion(int newVersion, bool clearUpgradeStep = true) = 0;
119
120     virtual bool setupSchemaVersion(int version) = 0;
121
122     /**
123      * Gets the last successful schema upgrade step, or an empty string if no upgrade is in progress
124      *
125      * @return Filename of last successful schema upgrade query, or empty string if not upgrading
126      */
127     virtual QString schemaVersionUpgradeStep();
128
129     /**
130      * Sets the last successful schema upgrade step
131      *
132      * @param upgradeQuery  The filename of the last successful schema upgrade query
133      * @return True if successfully set, otherwise false
134      */
135     virtual bool setSchemaVersionUpgradeStep(QString upgradeQuery) = 0;
136     virtual void setConnectionProperties(const QVariantMap &properties,
137                                          const QProcessEnvironment &environment,
138                                          bool loadFromEnvironment) = 0;
139     virtual QString driverName() = 0;
140     inline virtual QString hostName() { return QString(); }
141     inline virtual int port() { return -1; }
142     virtual QString databaseName() = 0;
143     inline virtual QString userName() { return QString(); }
144     inline virtual QString password() { return QString(); }
145
146     //! Initialize db specific features on connect
147     /** This is called every time a connection to a specific SQL backend is established
148      *  the default implementation does nothing.
149      *
150      *  When reimplementing this method, don't use logDB() inside this function as
151      *  this would cause as we're just about to initialize that DB connection.
152      */
153     inline virtual bool initDbSession(QSqlDatabase & /* db */) { return true; }
154
155 private slots:
156     void connectionDestroyed();
157
158 private:
159     void addConnectionToPool();
160     void dbConnect(QSqlDatabase &db);
161
162     int _schemaVersion;
163     bool _debug;
164
165     static int _nextConnectionId;
166     QMutex _connectionPoolMutex;
167     // we let a Connection Object manage each actual db connection
168     // those objects reside in the thread the connection belongs to
169     // which allows us thread safe termination of a connection
170     class Connection;
171     QHash<QThread *, Connection *> _connectionPool;
172 };
173
174 struct SenderData {
175     QString sender;
176     QString realname;
177     QString avatarurl;
178
179     friend uint qHash(const SenderData &key);
180     friend bool operator==(const SenderData &a, const SenderData &b);
181 };
182
183 // ========================================
184 //  AbstractSqlStorage::Connection
185 // ========================================
186 class AbstractSqlStorage::Connection : public QObject
187 {
188     Q_OBJECT
189
190 public:
191     Connection(const QString &name, QObject *parent = 0);
192     ~Connection();
193
194     inline QLatin1String name() const { return QLatin1String(_name); }
195
196 private:
197     QByteArray _name;
198 };
199
200
201 // ========================================
202 //  AbstractSqlMigrator
203 // ========================================
204 class AbstractSqlMigrator
205 {
206 public:
207     // migration objects
208     struct QuasselUserMO {
209         UserId id;
210         QString username;
211         QString password;
212         int hashversion;
213         QString authenticator;
214     };
215
216     struct SenderMO {
217         qint64 senderId;
218         QString sender;
219         QString realname;
220         QString avatarurl;
221         SenderMO() : senderId(0) {}
222     };
223
224     struct IdentityMO {
225         IdentityId id;
226         UserId userid;
227         QString identityname;
228         QString realname;
229         QString awayNick;
230         bool awayNickEnabled;
231         QString awayReason;
232         bool awayReasonEnabled;
233         bool autoAwayEnabled;
234         int autoAwayTime;
235         QString autoAwayReason;
236         bool autoAwayReasonEnabled;
237         bool detachAwayEnabled;
238         QString detachAwayReason;
239         bool detachAwayReasonEnabled;
240         QString ident;
241         QString kickReason;
242         QString partReason;
243         QString quitReason;
244         QByteArray sslCert;
245         QByteArray sslKey;
246     };
247
248     struct IdentityNickMO {
249         int nickid;
250         IdentityId identityId;
251         QString nick;
252     };
253
254     struct NetworkMO {
255         UserId userid;
256         QString networkname;
257         QString perform;
258         QString autoidentifyservice;
259         QString autoidentifypassword;
260         QString saslaccount;
261         QString saslpassword;
262         QString servercodec;
263         QString encodingcodec;
264         QString decodingcodec;
265         QString usermode;
266         QString awaymessage;
267         QString attachperform;
268         QString detachperform;
269         NetworkId networkid;
270         IdentityId identityid;
271         int messagerateburstsize;
272         int messageratedelay;
273         int autoreconnectinterval;
274         int autoreconnectretries;
275         bool rejoinchannels;
276         bool userandomserver;
277         bool useautoidentify;
278         bool usesasl;
279         bool useautoreconnect;
280         bool unlimitedconnectretries;
281         bool usecustommessagerate;
282         bool unlimitedmessagerate;
283         bool connected;
284     };
285
286     struct BufferMO {
287         BufferId bufferid;
288         UserId userid;
289         int groupid;
290         NetworkId networkid;
291         QString buffername;
292         QString buffercname;
293         int buffertype;
294         qint64 lastmsgid;
295         qint64 lastseenmsgid;
296         qint64 markerlinemsgid;
297         int bufferactivity;
298         int highlightcount;
299         QString key;
300         bool joined;
301         QString cipher;
302     };
303
304     struct BacklogMO {
305         MsgId messageid;
306         QDateTime time; // has to be in UTC!
307         BufferId bufferid;
308         int type;
309         int flags;
310         qint64 senderid;
311         QString senderprefixes;
312         QString message;
313     };
314
315     struct IrcServerMO {
316         int serverid;
317         UserId userid;
318         NetworkId networkid;
319         QString hostname;
320         int port;
321         QString password;
322         bool ssl;
323         bool sslverify;     /// If true, validate SSL certificates
324         int sslversion;
325         bool useproxy;
326         int proxytype;
327         QString proxyhost;
328         int proxyport;
329         QString proxyuser;
330         QString proxypass;
331     };
332
333     struct UserSettingMO {
334         UserId userid;
335         QString settingname;
336         QByteArray settingvalue;
337     };
338
339     struct CoreStateMO {
340         QString key;
341         QByteArray value;
342     };
343
344     enum MigrationObject {
345         QuasselUser,
346         Sender,
347         Identity,
348         IdentityNick,
349         Network,
350         Buffer,
351         Backlog,
352         IrcServer,
353         UserSetting,
354         CoreState
355     };
356
357     AbstractSqlMigrator();
358     virtual ~AbstractSqlMigrator() {}
359
360     static QString migrationObject(MigrationObject moType);
361
362 protected:
363     void newQuery(const QString &query, QSqlDatabase db);
364     virtual void resetQuery();
365     virtual bool prepareQuery(MigrationObject mo) = 0;
366     bool exec();
367     inline bool next() { return _query->next(); }
368     inline QVariant value(int index) { return _query->value(index); }
369     inline void bindValue(const QString &placeholder, const QVariant &val) { _query->bindValue(placeholder, val); }
370     inline void bindValue(int pos, const QVariant &val) { _query->bindValue(pos, val); }
371
372     inline QSqlError lastError() { return _query ? _query->lastError() : QSqlError(); }
373     void dumpStatus();
374     inline QString executedQuery() { return _query ? _query->executedQuery() : QString(); }
375     inline QVariantList boundValues();
376
377     virtual bool transaction() = 0;
378     virtual void rollback() = 0;
379     virtual bool commit() = 0;
380
381 private:
382     QSqlQuery *_query;
383 };
384
385
386 class AbstractSqlMigrationReader : public AbstractSqlMigrator
387 {
388 public:
389     AbstractSqlMigrationReader();
390
391     virtual bool readMo(QuasselUserMO &user) = 0;
392     virtual bool readMo(IdentityMO &identity) = 0;
393     virtual bool readMo(IdentityNickMO &identityNick) = 0;
394     virtual bool readMo(NetworkMO &network) = 0;
395     virtual bool readMo(BufferMO &buffer) = 0;
396     virtual bool readMo(SenderMO &sender) = 0;
397     virtual bool readMo(BacklogMO &backlog) = 0;
398     virtual bool readMo(IrcServerMO &ircserver) = 0;
399     virtual bool readMo(UserSettingMO &userSetting) = 0;
400     virtual bool readMo(CoreStateMO &coreState) = 0;
401
402     bool migrateTo(AbstractSqlMigrationWriter *writer);
403
404 private:
405     void abortMigration(const QString &errorMsg = QString());
406     bool finalizeMigration();
407
408     template<typename T> bool transferMo(MigrationObject moType, T &mo);
409
410     AbstractSqlMigrationWriter *_writer;
411 };
412
413
414 class AbstractSqlMigrationWriter : public AbstractSqlMigrator
415 {
416 public:
417     virtual bool writeMo(const QuasselUserMO &user) = 0;
418     virtual bool writeMo(const IdentityMO &identity) = 0;
419     virtual bool writeMo(const IdentityNickMO &identityNick) = 0;
420     virtual bool writeMo(const NetworkMO &network) = 0;
421     virtual bool writeMo(const BufferMO &buffer) = 0;
422     virtual bool writeMo(const SenderMO &sender) = 0;
423     virtual bool writeMo(const BacklogMO &backlog) = 0;
424     virtual bool writeMo(const IrcServerMO &ircserver) = 0;
425     virtual bool writeMo(const UserSettingMO &userSetting) = 0;
426     virtual bool writeMo(const CoreStateMO &coreState) = 0;
427
428     inline bool migrateFrom(AbstractSqlMigrationReader *reader) { return reader->migrateTo(this); }
429
430     // called after migration process
431     virtual inline bool postProcess() { return true; }
432     friend class AbstractSqlMigrationReader;
433 };