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