aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h
blob: a465679f9f8888fd753ff3361a678c64e9a08072 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
#ifndef ABSTRACTDB_H
#define ABSTRACTDB_H

#include "returncode.h"
#include "sqlquery.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() const;
        QString getPath() const;
        quint8 getVersion() const;
        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;
        void loadExtensions();

    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;

        int loadedExtensionCount = 0;

    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();
        void reloadExtensions();
};

/**
 * @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