diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h')
| -rw-r--r-- | SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h new file mode 100644 index 0000000..89edf03 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h @@ -0,0 +1,485 @@ +#ifndef ABSTRACTDB_H +#define ABSTRACTDB_H + +#include "returncode.h" +#include "sqlquery.h" +#include "dialect.h" +#include "db/db.h" +#include "common/bihash.h" +#include "services/functionmanager.h" +#include "common/readwritelocker.h" +#include "coreSQLiteStudio_global.h" +#include <QObject> +#include <QVariant> +#include <QList> +#include <QHash> +#include <QSet> +#include <QReadWriteLock> +#include <QRunnable> +#include <QStringList> + +class AsyncQueryRunner; + +/** + * @brief Base database logic implementation. + * + * This class implements common base logic for all database implementations. It's still abstract class + * and needs further implementation to be usable. + */ +class API_EXPORT AbstractDb : public Db +{ + Q_OBJECT + + public: + /** + * @brief Initializes database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See below for details. + * + * Connection options are handled individually by the derived database implementation class. + * It can be password for encrypted databases, read-only access flag, etc. + */ + AbstractDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + + virtual ~AbstractDb(); + + bool isOpen(); + QString getName(); + QString getPath(); + quint8 getVersion(); + Dialect getDialect(); + QString getEncoding(); + QHash<QString,QVariant>& getConnectionOptions(); + void setName(const QString& value); + void setPath(const QString& value); + void setConnectionOptions(const QHash<QString,QVariant>& value); + SqlQueryPtr exec(const QString& query, const QList<QVariant> &args, Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString& query, const QHash<QString, QVariant>& args, Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString &query, Db::Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString &query, const QVariant &arg); + SqlQueryPtr exec(const QString &query, std::initializer_list<QVariant> argList); + SqlQueryPtr exec(const QString &query, std::initializer_list<std::pair<QString,QVariant>> argMap); + void asyncExec(const QString& query, const QList<QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + void asyncExec(const QString& query, const QHash<QString, QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, const QList<QVariant>& args, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, const QHash<QString, QVariant>& args, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, Flags flags = Flag::NONE); + bool begin(); + bool commit(); + bool rollback(); + void interrupt(); + void asyncInterrupt(); + bool isReadable(); + bool isWritable(); + AttachGuard guardedAttach(Db* otherDb, bool silent = false); + QString attach(Db* otherDb, bool silent = false); + void detach(Db* otherDb); + void detachAll(); + const QHash<Db*,QString>& getAttachedDatabases(); + QSet<QString> getAllAttaches(); + QString getUniqueNewObjectName(const QString& attachedDbName = QString()); + QString getErrorText(); + int getErrorCode(); + bool initAfterCreated(); + void setTimeout(int secs); + int getTimeout() const; + bool isValid() const; + + protected: + struct FunctionUserData + { + QString name; + int argCount = 0; + Db* db = nullptr; + }; + + virtual QString getAttachSql(Db* otherDb, const QString& generatedAttachName); + + /** + * @brief Generates unique database name for ATTACH. + * @param lock Defines if the lock on dbOperLock mutex. + * @return Unique database name. + * + * Database name here is the name to be used for ATTACH statement. + * For example it will never be "main" or "temp", as those names are already used. + * It also will never be any name that is currently used for any ATTACH'ed database. + * It respects both manual ATTACH'es (called by user), as well as by attach() calls. + * + * Operations on database are normally locked during name generation, because it involves + * queries to the database about what are currently existing objects. + * The lock can be ommited if the calling method already locked dbOperLock. + */ + QString generateUniqueDbName(bool lock = true); + + /** + * @brief Detaches given database object from this database. + * @param otherDb Other registered database. + * + * This is called from detach() and detachAll(). + */ + void detachInternal(Db* otherDb); + + /** + * @brief Clears attached databases list. + * + * Called by closeQuiet(). Only clears maps and lists regarding attached databases. + * It doesn't call detach(), because closing the database will already detach all databases. + */ + void clearAttaches(); + + /** + * @brief Generated unique ID for asynchronous query execution. + * @return Unique ID. + */ + static quint32 generateAsyncId(); + + /** + * @brief Executes query asynchronously. + * @param runner Prepared object for asynchronous execution. + * @return Asynchronous execution unique ID. + * + * This is called by asyncExec(). Runs prepared runner object (which has all information about the query) + * on separate thread. + */ + quint32 asyncExec(AsyncQueryRunner* runner); + + /** + * @brief Opens the database and calls initial setup. + * @return true on success, false on failure. + * + * Calls openInternal() and if it succeeded, calls initialDbSetup(). + * It's called from openQuiet(). + */ + bool openAndSetup(); + + /** + * @brief Checks if the database connection is open. + * @return true if the connection is open, or false otherwise. + * + * This is called from isOpen(). Implementation should test and return information if the database + * connection is open. A lock on connectionStateLock is already set by the isOpen() method. + */ + virtual bool isOpenInternal() = 0; + + /** + * @brief Interrupts execution of any queries. + * + * Implementation of this method should interrupt any query executions that are currently in progress. + * Typical implementation for SQLite databases will call sqlite_interupt() / sqlite3_interupt(). + */ + virtual void interruptExecution() = 0; + + /** + * @brief Returns error message. + * @return Error string. + * + * This can be either error from last query execution, but also from connection opening problems, etc. + */ + virtual QString getErrorTextInternal() = 0; + + /** + * @brief Returns error code. + * @return Error code. + * + * This can be either error from last query execution, but also from connection opening problems, etc. + */ + virtual int getErrorCodeInternal() = 0; + + /** + * @brief Opens database connection. + * @return true on success, false on failure. + * + * Opens database. Called by open() and openAndSetup(). + */ + virtual bool openInternal() = 0; + + /** + * @brief Closes database connection. + * + * Closes database. Called by open() and openQuiet(). + */ + virtual bool closeInternal() = 0; + + virtual void initAfterOpen(); + + void checkForDroppedObject(const QString& query); + bool registerCollation(const QString& name); + bool deregisterCollation(const QString& name); + bool isCollationRegistered(const QString& name); + + /** + * @brief Registers a collation sequence implementation in the database. + * @param name Name of the collation. + * @return true on success, false on failure. + * + * This should be low-level implementation depended on SQLite driver. + * The general implementation of registerCollation() in this class just keeps track on collations + * registered. + */ + virtual bool registerCollationInternal(const QString& name) = 0; + + /** + * @brief Deregisters previously registered collation from this database. + * @param name Collation name. + * @return true on success, false on failure. + * + * This should be low-level implementation depended on SQLite driver. + * The general implementation of registerCollation() in this class just keeps track on collations + * registered. + */ + virtual bool deregisterCollationInternal(const QString& name) = 0; + + static QHash<QString,QVariant> getAggregateContext(void* memPtr); + static void setAggregateContext(void* memPtr, const QHash<QString,QVariant>& aggregateContext); + static void releaseAggregateContext(void* memPtr); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param dataPtr SQL function user data (defined when registering function). Must be of FunctionUserData* type, or descendant. + * @param argList List of arguments passed to the function. + * @param[out] ok true (default) to indicate successful execution, or false to report an error. + * @return Result returned from the plugin handling function implementation. + * + * This method is aware of the implementation language and the code defined for it, + * so it delegates the execution to the proper plugin handling that language. + * + * This method is called for scalar functions. + */ + static QVariant evaluateScalar(void* dataPtr, const QList<QVariant>& argList, bool& ok); + static void evaluateAggregateStep(void* dataPtr, QHash<QString, QVariant>& aggregateContext, QList<QVariant> argList); + static QVariant evaluateAggregateFinal(void* dataPtr, QHash<QString, QVariant>& aggregateContext, bool& ok); + + /** + * @brief Database name. + * + * It must be unique across all Db instances. Use generateUniqueDbName() to get the unique name + * for new database. It's used as a key for DbManager. + * + * Databases are also presented to the user with this name on UI. + */ + QString name; + + /** + * @brief Path to the database file. + */ + QString path; + + /** + * @brief Connection options. + * + * There are no standard options. Custom DbPlugin implementations may support some options. + */ + QHash<QString,QVariant> connOptions; + + /** + * @brief SQLite version of this database. + * + * This is only a major version number (2 or 3). + */ + quint8 version = 0; + + /** + * @brief Map of databases attached to this database. + * + * It's mapping from ATTACH name to the database object. It contains only attaches + * that were made with attach() calls. + */ + BiHash<QString,Db*> attachedDbMap; + + /** + * @brief Counter of attaching requrests for each database. + * + * When calling attach() on other Db, it gets its own entry in this mapping. + * If the mapping already exists, its value is incremented. + * Then, when calling detach(), counter is decremented and when it reaches 0, + * the database is actualy detached. + */ + QHash<Db*,int> attachCounter; + + /** + * @brief Result handler functions for asynchronous executions. + * + * For each asyncExec() with function pointer in argument there's an entry in this map + * pointing to the function. Keys are asynchronous IDs. + */ + QHash<int,QueryResultsHandler> resultHandlers; + + /** + * @brief Database operation lock. + * + * This lock is set whenever any operation on the actual database is performed (i.e. call to + * exec(), interrupt(), open(), close(), generateUniqueDbName(true), attach(), detach(), and others... + * generally anything that does operations on database that must be synchronous). + * + * In case of exec() it can be locked for READ or WRITE (depending on query type), + * because there can be multiple SELECTs and there's nothing wrong with it, + * while for other methods is always lock for WRITE. + */ + QReadWriteLock dbOperLock; + + private: + /** + * @brief Represents single function that is registered in the database. + * + * Registered custom SQL functions are diversed by SQLite by their name, arguments count and their type, + * so this structure has exactly those parameters. + */ + struct RegisteredFunction + { + /** + * @brief Function name. + */ + QString name; + + /** + * @brief Arguments count (-1 for undefined count). + */ + int argCount; + + /** + * @brief Function type. + */ + FunctionManager::ScriptFunction::Type type; + }; + + friend int qHash(const AbstractDb::RegisteredFunction& fn); + friend bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2); + + /** + * @brief Applies execution flags and executes query. + * @param query Query to be executed. + * @param args Query parameters. + * @param flags Query execution flags. + * @return Execution results - either successful or failed. + * + * This is called from both exec() and execNoLock() and is a final step before calling execInternal() + * (the plugin-provided execution). This is where \p flags are interpreted and applied. + */ + SqlQueryPtr execHashArg(const QString& query, const QHash<QString, QVariant>& args, Flags flags); + + /** + * @overload + */ + SqlQueryPtr execListArg(const QString& query, const QList<QVariant>& args, Flags flags); + + /** + * @brief Generates unique database name. + * @return Unique database name. + * + * This is a lock-less variant of generateUniqueDbName(). It is called from that method. + * See generateUniqueDbName() for details. + */ + QString generateUniqueDbNameNoLock(); + + /** + * @brief Provides required locking mode for given query. + * @param query Query to be executed. + * @return Locking mode: READ or WRITE. + * + * Given the query this method analyzes what is the query and provides information if the query + * will do some changes on the database, or not. Then it returns proper locking mode that should + * be used for this query execution. + * + * Query execution methods from this class check if lock mode of the query to be executed isn't + * in conflict with the lock being currently applied on the dbOperLock (if any is applied at the moment). + * + * This method works on a very simple rule. It assumes that queries: SELECT, ANALYZE, EXPLAIN, + * and PRAGMA - are read-only, while all other queries are read-write. + * In case of PRAGMA this is not entirely true, but it's not like using PRAGMA for changing + * some setting would cause database state inconsistency. At least not from perspective of SQLiteStudio. + * + * In case of WITH statement it filters out the "WITH clause" and then checks for SELECT keyword. + */ + ReadWriteLocker::Mode getLockingMode(const QString& query, Db::Flags flags); + + /** + * @brief Handles asynchronous query results with results handler function. + * @param asyncId Asynchronous ID. + * @param results Results from execution. + * @return true if the results were handled, or false if they were not. + * + * This method checks if there is a handler function for given asynchronous ID (in resultHandlers) + * and if there is, then evaluates it and returns true. Otherwise does nothing and returns false. + */ + bool handleResultInternally(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Registers single custom SQL function. + * @param function Function to register. + * + * If function got registered successfully, it's added to registeredFunctions. + * If there was a function with the same name, argument count and type already registered, + * it will be overwritten (both in SQLite and in registeredFunctions). + */ + void registerFunction(const RegisteredFunction& function); + + /** + * @brief Connection state lock. + * + * It's locked whenever the connection state is changed or tested. + * For open() and close() it's a WRITE lock, for isOpen() it's READ lock. + */ + QReadWriteLock connectionStateLock; + + /** + * @brief Sequence container for generating unique asynchronous IDs. + */ + static quint32 asyncId; + + /** + * @brief Current timeout (in seconds) for waiting for the database to be released from the lock. + * + * See Db::setTimeout() for details. + */ + int timeout = 60; + + /** + * @brief List of all functions currently registered in this database. + */ + QSet<RegisteredFunction> registeredFunctions; + + /** + * @brief List of all collations currently registered in this database. + */ + QStringList registeredCollations; + + private slots: + /** + * @brief Handles asynchronous execution results. + * @param runner Container with input and output data of the query. + * + * This is called from the other thread when it finished asynchronous execution. + * It checks if there is any handler function to evaluate it with results + * and if there's not, emits asyncExecFinished() signal. + */ + void asyncQueryFinished(AsyncQueryRunner* runner); + + public slots: + bool open(); + bool close(); + bool openQuiet(); + bool closeQuiet(); + bool openForProbing(); + void registerAllFunctions(); + void registerAllCollations(); +}; + +/** + * @brief Standard function required by QHash. + * @param fn Function to calculate hash for. + * @return Hash value calculated from all members of DbBase::RegisteredFunction. + */ +int qHash(const AbstractDb::RegisteredFunction& fn); + +/** + * @brief Simple comparator operator, compares all members. + * @param other Other function to compare. + * @return true if \p other is equal, false otherwise. + * + * This function had to be declared/defined outside of the DbBase::RegisteredFunction, because QSet/QHash requires this. + */ +bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2); + +#endif // ABSTRACTDB_H |
