aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h
blob: 90c56f6c8cccc91d7282bcc3c24c0671daad4686 (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
#ifndef CHAINEXECUTOR_H
#define CHAINEXECUTOR_H

#include "db/db.h"
#include <QObject>

// TODO add parameters support for ChainExecutor.
// it requires clever api, cause there can be multiple queries and each can use differend parameters,
// while we cannot apply same parameter set for each query, cause they are bind by an available list/hash.

/**
 * @brief Simple query executor, which executes queries one by one.
 *
 * A query executor which lets you execute query (or many queries)
 * using asynchronous execution and it gets back to the called only when
 * all queries succeeded, or it failed at some query.
 *
 * This is very useful if there is a sequence of queries to be executed
 * and you're interested only in the result of the last query.
 *
 * It also lets to configure if queries should be executed within transaction,
 * or not.
 */
class API_EXPORT ChainExecutor : public QObject
{
        Q_OBJECT

    public:
        typedef QPair<int,QString> ExecutionError;

        /**
         * @brief Creates executor.
         * @param parent Parent object for QObject.
         */
        explicit ChainExecutor(QObject *parent = 0);

        /**
         * @brief Tells if transactional execution is enabled.
         * @return Transactional execution status.
         */
        bool getTransaction() const;

        /**
         * @brief Enabled or disables transactional execution.
         * @param value True to enable, false to disable.
         *
         * Transactional execution is enabled by default. It means that all defined SQL queries
         * will be executed in single SQL transaction.
         */
        void setTransaction(bool value);

        /**
         * @brief Provides list of SQL queries configured for this executor.
         * @return List of queries.
         */
        QStringList getQueries() const;

        /**
         * @brief Defines list of queries to be executed.
         * @param value List of query strings.
         *
         * This is the main mathod you're interested in when using ChainExecutor.
         * This is how you define what SQL queries will be executed.
         *
         * Calling this method will clear any parameters defined previously with setParam().
         */
        void setQueries(const QStringList& value);

        /**
         * @brief Provides currently configured database.
         * @return Database that the queries are executed on in this executor.
         */
        Db* getDb() const;

        /**
         * @brief Defines database for executing queries.
         * @param value The database object.
         *
         * It is necessary to define the database before executing queries,
         * otherwise the start() will emit failure() signal and do nothing else.
         */
        void setDb(Db* value);

        /**
         * @brief Provides list of configured query mandatory flags.
         * @return List of flags.
         *
         * See setMandatoryQueries() for details on mandatory flags.
         */
        QList<bool> getMandatoryQueries() const;

        /**
         * @brief Defines list of mandatory flags for queries.
         * @param value List of flags - a boolean per each defined query.
         *
         * Setting mandatory flags lets you define which queries (defined with setSqls())
         * are mandatory for the successful execution and which are not.
         * Queries are mandatory by default (when flags are not defined),
         * which means that every defined query execution must be successfull,
         * otherwise executor breaks the execution chain and reports error.
         *
         * By defining mandatory flags to false for some queries, you're telling
         * to ChainExecutor, that it's okay if those queries fail and it should
         * move along.
         *
         * For example:
         * @code
         * ChainExecutor executor;
         * executor.setSqls({
         *     "DELETE FROM table1 WHERE value = 5",
         *     "DELETE FROM possibly_not_existing_table WHERE column > 3",
         *     "INSERT INTO table1 VALUES (4, 6)"
         * });
         * executor.setMandatoryQueries({true, false, true});
         * @endcode
         * We defined second query to be optional, therefore if the table
         * "possibly_not_existing_table" doesn't exist, that's fine.
         * It will be ignored and the third query will be executed.
         * If flags were not defined, then execution of second query would fail,
         * executor would stop there, report error (with failure() signal)
         * and the third query would not be executed.
         *
         * It also affects transactions. If executor was defined to execute
         * in a transaction (with setTransaction()), then failed query
         * that was not mandatory will also not rollback the transaction.
         *
         * In other words, queries marked as not mandatory are silently ignored
         * when failed.
         */
        void setMandatoryQueries(const QList<bool>& value);

        /**
         * @brief Provides list of execution error messages.
         * @return List of messages.
         *
         * Execution error messages usually have zero or one message,
         * but if you defined some queries to be not mandatory,
         * then each failed optional query will be silently ignored,
         * but its error message will be stored and returned by this method.
         * In that case, the result of this method can provide more than
         * one message.
         */
        QStringList getErrorsMessages() const;

        /**
         * @brief Provides list of execution errors.
         * @return List of errors.
         *
         * These are the same errors as returned by getErrorsMessages(), except this list contains
         * both error code (as returned from SQLite) and error message.
         */
        const QList<ExecutionError>& getErrors() const;

        /**
         * @brief Tells if the executor is configured for asynchronous execution.
         * @return Asynchronous flag value.
         */
        bool getAsync() const;

        /**
         * @brief Defines asynchronous execution mode.
         * @param value true to enable asynchronous execution, false to disable it.
         *
         * Asynchronous execution causes start() to return immediately.
         *
         * When asynchronous mode is enabled, results of execution
         * have to be handled by connecting to failed() and success() signals.
         *
         * If the asynchronous mode is disabled, result can be queried
         * by getSuccessfulExecution() call.
         */
        void setAsync(bool value);

        /**
         * @brief Tells if the most recent execution was successful.
         * @return true if execution was successful, or false if it failed.
         *
         * Successful execution means that all mandatory queries
         * (see setMandatoryQueries()) executed successfully.
         * Optional (not mandatory) queries do not affect result of this method.
         *
         * If this method returns true, it also means that success() signal
         * was emitted.
         * If this method returns false, it also means that failure() signal
         * was emitted.
         */
        bool getSuccessfulExecution() const;

        /**
         * @brief Defines named parameter to bind in queries.
         * @param paramName Parameter name (must include the preceding ':').
         * @param value Value for the parameter.
         *
         * Any parameter defined with this method will be applied to each query
         * executed by the executor. If some query doesn't include parameter
         * placeholder with defined name, then the parameter will simply
         * not be applied to that query.
         */
        void setParam(const QString& paramName, const QVariant& value);

        bool getDisableForeignKeys() const;
        void setDisableForeignKeys(bool value);

        bool getDisableObjectDropsDetection() const;
        void setDisableObjectDropsDetection(bool value);

    private:
        /**
         * @brief Executes query defines as the current one.
         *
         * Checks is there is a current query defined (pointed by currentSqlIndex).
         * If there is, then executes it. If not, goes to executionSuccessful().
         *
         * This is called for each next query in asynchronous mode.
         */
        void executeCurrentSql();

        /**
         * @brief Handles failed execution.
         * @param errorCode Error code.
         * @param errorText Error message.
         *
         * Rolls back transaction (in case of transactional execution) and emits failure().
         */
        void executionFailure(int errorCode, const QString& errorText);

        /**
         * @brief Handles successful execution.
         *
         * Commits transaction (in case of transactional execution) and emits success().
         */
        void executionSuccessful(SqlQueryPtr results);

        /**
         * @brief Executes all queries synchronously.
         *
         * If the asynchronous mode is disabled, then this method executes all queries.
         */
        void executeSync();

        /**
         * @brief Handles single query execution results.
         * @param results Results from the query.
         * @return true if the execution was successful, or false otherwise.
         *
         * If there was an error while execution, then executionFailure() is also called.
         */
        bool handleResults(SqlQueryPtr results);

        Db::Flags getExecFlags() const;

        void restoreFk();

        /**
         * @brief Database for execution.
         */
        Db* db = nullptr;

        /**
         * @brief Transactional execution mode.
         */
        bool transaction = true;

        /**
         * @brief Asynchronous execution mode.
         */
        bool async = true;

        /**
         * @brief Queries to be executed.
         */
        QStringList sqls;

        /**
         * @brief List of flags for mandatory queries.
         *
         * See setMandatoryQueries() for details.
         */
        QList<bool> mandatoryQueries;

        /**
         * @brief Index pointing to the current query in sqls list.
         *
         * When executing query in asynchronous mode, this index points to the next
         * query that should be executed.
         */
        int currentSqlIndex = -1;

        /**
         * @brief Asynchronous ID of current query execution.
         *
         * The ID is provided by Db::asyncExec().
         */
        quint32 asyncId = -1;

        /**
         * @brief Execution interrupted flag.
         *
         * Once the interrup() was called, this flag is set to true,
         * so the executor knows that it should not execute any further queries.
         */
        bool interrupted = false;

        /**
         * @brief Errors raised during queries execution.
         *
         * In case of major failure, the error message is appended to this list,
         * but when mandatory flags allow some failures, than this list may
         * contain more error messages.
         */
        QList<ExecutionError> executionErrors;

        /**
         * @brief Successful execution indicator.
         *
         * This is set after execution is finished.
         */
        bool successfulExecution = false;

        /**
         * @brief Parameters to bind to queries.
         *
         * This is filled with setParam() calls and used later to bind
         * parameters to executed queries.
         */
        QHash<QString,QVariant> queryParams;

        bool disableForeignKeys = false;
        bool disableObjectDropsDetection = false;

        SqlQueryPtr lastExecutionResults;

    public slots:
        /**
         * @brief Interrupts query execution.
         */
        void interrupt();

        /**
         * @brief Starts execution of all defined queries, one by one.
         */
        void exec();

    private slots:
        /**
         * @brief Handles asynchronous execution results from Db::asyncExec().
         * @param asyncId Asynchronous ID of the execution for the results.
         * @param results Results returned from execution.
         *
         * Checks if given asynchronous ID matches the internally stored asyncId
         * and if yes, then handles results and executes next query in the queue.
         */
        void handleAsyncResults(quint32 asyncId, SqlQueryPtr results);

    signals:
        /**
         * @brief Emitted when all mandatory queries were successfully executed.
         * @param results Execution results from last query execution.
         *
         * See setMandatoryQueries() for details on mandatory queries.
         */
        void success(SqlQueryPtr results);

        /**
         * @brief Emitted when chain execution is finished, regardless of result.
         * @param results Execution results from last query execution (may be null).
         *
         * In slot for this signal always check getSuccessfulExecution(),
         * because execution could failed, despite results providing no error.
         * It may happen for example if execution was interrupted,
         * or executor could not pass through execution preparation phase
         * (in which case results will be null).
         */
        void finished(SqlQueryPtr results);

        /**
         * @brief Emitted when major error occurred while executing a query.
         * @param errorCode Error code.
         * @param errorText Error message.
         *
         * It's emitted only when mandatory query has failed execution.
         * See setMandatoryQueries() for details on mandatory queries.
         */
        void failure(int errorCode, const QString& errorText);
};

#endif // CHAINEXECUTOR_H