core: Track upgrade step within schema version
[quassel.git] / src / core / abstractsqlstorage.h
index 6fefb78..88ecfb5 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-07 by the Quassel IRC Team                         *
+ *   Copyright (C) 2005-2019 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   You should have received a copy of the GNU General Public License     *
  *   along with this program; if not, write to the                         *
  *   Free Software Foundation, Inc.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef ABSTRACTSQLSTORAGE_H
-#define ABSTRACTSQLSTORAGE_H
+#pragma once
 
-#include "storage.h"
+#include <memory>
 
+#include <QList>
 #include <QSqlDatabase>
+#include <QSqlError>
+#include <QSqlQuery>
+
+#include "storage.h"
+
+class AbstractSqlMigrationReader;
+class AbstractSqlMigrationWriter;
+
+class AbstractSqlStorage : public Storage
+{
+    Q_OBJECT
+
+public:
+    AbstractSqlStorage(QObject* parent = nullptr);
+    ~AbstractSqlStorage() override;
+
+    virtual std::unique_ptr<AbstractSqlMigrationReader> createMigrationReader() { return {}; }
+    virtual std::unique_ptr<AbstractSqlMigrationWriter> createMigrationWriter() { return {}; }
+
+    /**
+     * An SQL query with associated resource filename
+     */
+    struct SqlQueryResource {
+        QString queryString;   ///< SQL query string
+        QString queryFilename; ///< Path to the resource file providing this query
+
+        SqlQueryResource(const QString& queryString, const QString& queryFilename)
+            : queryString(std::move(queryString)),
+              queryFilename(std::move(queryFilename)) {}
+    };
+
+public slots:
+    State init(const QVariantMap& settings = QVariantMap(),
+               const QProcessEnvironment& environment = {},
+               bool loadFromEnvironment = false) override;
+    bool setup(const QVariantMap& settings = QVariantMap(),
+               const QProcessEnvironment& environment = {},
+               bool loadFromEnvironment = false) override;
+
+protected:
+    inline void sync() override{};
+
+    QSqlDatabase logDb();
+
+    /**
+     * Fetch an SQL query string by name and optional schema version
+     *
+     * Loads the named SQL query from the built-in SQL resource collection, returning it as a
+     * string.  If a version is specified, it'll be loaded from the schema version-specific folder
+     * instead.
+     *
+     * @see schemaVersion()
+     *
+     * @param[in] queryName  File name of the SQL query, minus the .sql extension
+     * @param[in] version
+     * @parblock
+     * SQL schema version; if 0, fetches from current version, otherwise loads from the specified
+     * schema version instead of the current schema files.
+     * @endparblock
+     * @return String with the requested SQL query, ready for parameter substitution
+     */
+    QString queryString(const QString& queryName, int version = 0);
+
+    QStringList setupQueries();
+
+    /**
+     * Gets the collection of SQL upgrade queries and filenames for a given schema version
+     *
+     * @param ver  SQL schema version
+     * @return List of SQL query strings and filenames
+     */
+    QList<SqlQueryResource> upgradeQueries(int ver);
+    bool upgradeDb();
+
+    bool watchQuery(QSqlQuery& query);
+
+    int schemaVersion();
+    virtual int installedSchemaVersion() { return -1; };
+
+    /**
+     * Update the stored schema version number, optionally clearing the record of mid-schema steps
+     *
+     * @param newVersion        New schema version number
+     * @param clearUpgradeStep  If true, clear the record of any in-progress schema upgrades
+     * @return
+     */
+    virtual bool updateSchemaVersion(int newVersion, bool clearUpgradeStep = true) = 0;
+
+    virtual bool setupSchemaVersion(int version) = 0;
+
+    /**
+     * Gets the last successful schema upgrade step, or an empty string if no upgrade is in progress
+     *
+     * @return Filename of last successful schema upgrade query, or empty string if not upgrading
+     */
+    virtual QString schemaVersionUpgradeStep();
+
+    /**
+     * Sets the last successful schema upgrade step
+     *
+     * @param upgradeQuery  The filename of the last successful schema upgrade query
+     * @return True if successfully set, otherwise false
+     */
+    virtual bool setSchemaVersionUpgradeStep(QString upgradeQuery) = 0;
+
+    virtual void setConnectionProperties(const QVariantMap& properties, const QProcessEnvironment& environment, bool loadFromEnvironment) = 0;
+    virtual QString driverName() = 0;
+    inline virtual QString hostName() { return QString(); }
+    inline virtual int port() { return -1; }
+    virtual QString databaseName() = 0;
+    inline virtual QString userName() { return QString(); }
+    inline virtual QString password() { return QString(); }
+
+    //! Initialize db specific features on connect
+    /** This is called every time a connection to a specific SQL backend is established
+     *  the default implementation does nothing.
+     *
+     *  When reimplementing this method, don't use logDB() inside this function as
+     *  this would cause as we're just about to initialize that DB connection.
+     */
+    inline virtual bool initDbSession(QSqlDatabase& /* db */) { return true; }
+
+private slots:
+    void connectionDestroyed();
+
+private:
+    void addConnectionToPool();
+    void dbConnect(QSqlDatabase& db);
+
+    int _schemaVersion{0};
+    bool _debug;
+
+    static int _nextConnectionId;
+    QMutex _connectionPoolMutex;
+    // we let a Connection Object manage each actual db connection
+    // those objects reside in the thread the connection belongs to
+    // which allows us thread safe termination of a connection
+    class Connection;
+    QHash<QThread*, Connection*> _connectionPool;
+};
 
-class QSqlQuery;
+struct SenderData
+{
+    QString sender;
+    QString realname;
+    QString avatarurl;
 
-class AbstractSqlStorage : public Storage {
-  Q_OBJECT
+    friend uint qHash(const SenderData& key);
+    friend bool operator==(const SenderData& a, const SenderData& b);
+};
+
+// ========================================
+//  AbstractSqlStorage::Connection
+// ========================================
+class AbstractSqlStorage::Connection : public QObject
+{
+    Q_OBJECT
+
+public:
+    Connection(const QString& name, QObject* parent = nullptr);
+    ~Connection() override;
+
+    inline QLatin1String name() const { return QLatin1String(_name); }
+
+private:
+    QByteArray _name;
+};
 
+// ========================================
+//  AbstractSqlMigrator
+// ========================================
+class AbstractSqlMigrator
+{
 public:
-  AbstractSqlStorage(QObject *parent = 0);
-  virtual ~AbstractSqlStorage();
+    // migration objects
+    struct QuasselUserMO
+    {
+        UserId id;
+        QString username;
+        QString password;
+        int hashversion;
+        QString authenticator;
+    };
+
+    struct SenderMO
+    {
+        qint64 senderId{0};
+        QString sender;
+        QString realname;
+        QString avatarurl;
+    };
+
+    struct IdentityMO
+    {
+        IdentityId id;
+        UserId userid;
+        QString identityname;
+        QString realname;
+        QString awayNick;
+        bool awayNickEnabled;
+        QString awayReason;
+        bool awayReasonEnabled;
+        bool autoAwayEnabled;
+        int autoAwayTime;
+        QString autoAwayReason;
+        bool autoAwayReasonEnabled;
+        bool detachAwayEnabled;
+        QString detachAwayReason;
+        bool detachAwayReasonEnabled;
+        QString ident;
+        QString kickReason;
+        QString partReason;
+        QString quitReason;
+        QByteArray sslCert;
+        QByteArray sslKey;
+    };
+
+    struct IdentityNickMO
+    {
+        int nickid;
+        IdentityId identityId;
+        QString nick;
+    };
+
+    struct NetworkMO
+    {
+        UserId userid;
+        QString networkname;
+        QString perform;
+        QString autoidentifyservice;
+        QString autoidentifypassword;
+        QString saslaccount;
+        QString saslpassword;
+        QString servercodec;
+        QString encodingcodec;
+        QString decodingcodec;
+        QString usermode;
+        QString awaymessage;
+        QString attachperform;
+        QString detachperform;
+        NetworkId networkid;
+        IdentityId identityid;
+        int messagerateburstsize;
+        int messageratedelay;
+        int autoreconnectinterval;
+        int autoreconnectretries;
+        bool rejoinchannels;
+        bool userandomserver;
+        bool useautoidentify;
+        bool usesasl;
+        bool useautoreconnect;
+        bool unlimitedconnectretries;
+        bool usecustommessagerate;
+        bool unlimitedmessagerate;
+        bool connected;
+    };
+
+    struct BufferMO
+    {
+        BufferId bufferid;
+        UserId userid;
+        int groupid;
+        NetworkId networkid;
+        QString buffername;
+        QString buffercname;
+        int buffertype;
+        qint64 lastmsgid;
+        qint64 lastseenmsgid;
+        qint64 markerlinemsgid;
+        int bufferactivity;
+        int highlightcount;
+        QString key;
+        bool joined;
+        QString cipher;
+    };
+
+    struct BacklogMO
+    {
+        MsgId messageid;
+        QDateTime time;  // has to be in UTC!
+        BufferId bufferid;
+        int type;
+        int flags;
+        qint64 senderid;
+        QString senderprefixes;
+        QString message;
+    };
+
+    struct IrcServerMO
+    {
+        int serverid;
+        UserId userid;
+        NetworkId networkid;
+        QString hostname;
+        int port;
+        QString password;
+        bool ssl;
+        bool sslverify;  /// If true, validate SSL certificates
+        int sslversion;
+        bool useproxy;
+        int proxytype;
+        QString proxyhost;
+        int proxyport;
+        QString proxyuser;
+        QString proxypass;
+    };
+
+    struct UserSettingMO
+    {
+        UserId userid;
+        QString settingname;
+        QByteArray settingvalue;
+    };
+
+    struct CoreStateMO
+    {
+        QString key;
+        QByteArray value;
+    };
+
+    enum MigrationObject
+    {
+        QuasselUser,
+        Sender,
+        Identity,
+        IdentityNick,
+        Network,
+        Buffer,
+        Backlog,
+        IrcServer,
+        UserSetting,
+        CoreState
+    };
+
+    virtual ~AbstractSqlMigrator() = default;
+
+    static QString migrationObject(MigrationObject moType);
 
-  //! Returns the name of the storage backend engine
-  /** \return A virtual equivalent of displayName() */
-  virtual QString engineName() { return ""; }
-  
 protected:
-  bool init(const QVariantMap &settings = QVariantMap());
-  virtual void sync();
-  
-  QSqlDatabase logDb();
-  
-  QString queryString(const QString &queryName, int version);
-  QString queryString(const QString &queryName);
-
-  QSqlQuery *cachedQuery(const QString &queryName, int version);
-  QSqlQuery *cachedQuery(const QString &queryName);
-
-  QStringList setupQueries();
-  bool setup(const QVariantMap &settings = QVariantMap());
-
-  QStringList upgradeQueries(int ver);
-  bool upgradeDb();
-
-  bool watchQuery(QSqlQuery *query);
-  
-  int schemaVersion();
-  virtual int installedSchemaVersion() { return -1; };
-
-  virtual QString driverName() = 0;
-  inline virtual QString hostName() { return QString(); }
-  virtual QString databaseName() = 0;
-  inline virtual QString userName() { return QString(); }
-  inline virtual QString password() { return QString(); }
+    void newQuery(const QString& query, QSqlDatabase db);
+    virtual void resetQuery();
+    virtual bool prepareQuery(MigrationObject mo) = 0;
+    bool exec();
+    inline bool next() { return _query->next(); }
+    inline QVariant value(int index) { return _query->value(index); }
+    inline void bindValue(const QString& placeholder, const QVariant& val) { _query->bindValue(placeholder, val); }
+    inline void bindValue(int pos, const QVariant& val) { _query->bindValue(pos, val); }
+
+    inline QSqlError lastError() { return _query ? _query->lastError() : QSqlError(); }
+    void dumpStatus();
+    inline QString executedQuery() { return _query ? _query->executedQuery() : QString(); }
+    inline QVariantList boundValues();
+
+    virtual bool transaction() = 0;
+    virtual void rollback() = 0;
+    virtual bool commit() = 0;
 
 private:
-  bool openDb();
+    QSqlQuery* _query{nullptr};
+};
 
-  int _schemaVersion;
+class AbstractSqlMigrationReader : public AbstractSqlMigrator
+{
+public:
+    AbstractSqlMigrationReader();
 
-  QHash<QPair<QString, int>, QSqlQuery *> _queryCache;
+    virtual bool readMo(QuasselUserMO& user) = 0;
+    virtual bool readMo(IdentityMO& identity) = 0;
+    virtual bool readMo(IdentityNickMO& identityNick) = 0;
+    virtual bool readMo(NetworkMO& network) = 0;
+    virtual bool readMo(BufferMO& buffer) = 0;
+    virtual bool readMo(SenderMO& sender) = 0;
+    virtual bool readMo(BacklogMO& backlog) = 0;
+    virtual bool readMo(IrcServerMO& ircserver) = 0;
+    virtual bool readMo(UserSettingMO& userSetting) = 0;
+    virtual bool readMo(CoreStateMO& coreState) = 0;
 
+    bool migrateTo(AbstractSqlMigrationWriter* writer);
+
+private:
+    void abortMigration(const QString& errorMsg = QString());
+    bool finalizeMigration();
+
+    template<typename T>
+    bool transferMo(MigrationObject moType, T& mo);
+
+    AbstractSqlMigrationWriter* _writer{nullptr};
 };
 
+class AbstractSqlMigrationWriter : public AbstractSqlMigrator
+{
+public:
+    virtual bool writeMo(const QuasselUserMO& user) = 0;
+    virtual bool writeMo(const IdentityMO& identity) = 0;
+    virtual bool writeMo(const IdentityNickMO& identityNick) = 0;
+    virtual bool writeMo(const NetworkMO& network) = 0;
+    virtual bool writeMo(const BufferMO& buffer) = 0;
+    virtual bool writeMo(const SenderMO& sender) = 0;
+    virtual bool writeMo(const BacklogMO& backlog) = 0;
+    virtual bool writeMo(const IrcServerMO& ircserver) = 0;
+    virtual bool writeMo(const UserSettingMO& userSetting) = 0;
+    virtual bool writeMo(const CoreStateMO& coreState) = 0;
+
+    inline bool migrateFrom(AbstractSqlMigrationReader* reader) { return reader->migrateTo(this); }
 
-#endif
+    // called after migration process
+    virtual inline bool postProcess() { return true; }
+    friend class AbstractSqlMigrationReader;
+};