aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h
blob: f7865904cd73e67b835fc1c3529d86cd240eea7a (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
#ifndef SQLQUERY_H
#define SQLQUERY_H

#include "coreSQLiteStudio_global.h"
#include "db/db.h"
#include "db/sqlresultsrow.h"
#include <QList>
#include <QSharedPointer>

/** @file */

/**
 * @brief Row ID of the row in any table.
 *
 * It's a advanced ROWID container that can hold either simple integer ROWID,
 * or a set of column values reflecting one or more PRIMARY KEY columns.
 *
 * This way it RowId can be applied to both regular tables, as well as "WITHOUT ROWID" tables.
 *
 * Each entry in the RowId has a key and a value (after all it's a QHash). Keys are names
 * of the column and values are values in that columns. For regular tables the RowId
 * will contain exactly one entry: <tt>ROWID -> some_integer</tt>
 */
typedef QHash<QString,QVariant> RowId;

/**
 * @brief SQL query to execute and get results from
 *
 * This object is created by and returned from Db::exec() (and familiar methods)
 * or Db::prepare() calls.
 * It uses incremental reading for accessing data, so it only reads as much data
 * as you ask it to. It can tell you how many rows and how many columns are available
 * in the results. It also provides information about errors that occured during query execution.
 *
 * Typical workflow looks like this:
 * @code
 * SqlQueryPtr results = db->exec("SELECT * FROM table");
 * SqlResultsRowPtr row;
 * while (row = results->next())
 * {
 *     qDebug() << row->valueList();
 * }
 * @endcode
 *
 * To re-use compiled query, use it like this:
 * @code
 * SqlQueryPtr query = db->prepare("SELECT * FROM table WHERE id BETWEEN ? AND ?");
 * SqlResultsRowPtr row;
 *
 * query->setArgs({5, 10});
 * query->execute();
 * while (row = query->next())
 * {
 *     qDebug() << row->valueList();
 * }
 *
 * query->setArgs({1, 3});
 * query->execute();
 * while (row = query->next())
 * {
 *     qDebug() << row->valueList();
 * }
 * @endcode
 */
class API_EXPORT SqlQuery
{
    public:
        /**
         * @brief Produces empty, erronous result.
         * @param errorText Error message returned with #getErrorText() of the returned object.
         * @param errorCode Error code returned with #getErrorText() of the returned object.
         * @return SqlQuery object shared pointer with no results, but with error details populated.
         */
        static SqlQueryPtr error(const QString& errorText, int errorCode);

        /**
         * @brief Releases result resources.
         */
        virtual ~SqlQuery();

        /**
         * @brief Executes or re-executes prepared query.
         * @return true on success, false on failure.
         *
         * You can ignore result of this method and check for error later with isError().
         */
        virtual bool execute();

        /**
         * @brief Reads next row of results
         * @return Next results row, or null pointer if no more rows are available.
         */
        SqlResultsRowPtr next();

        /**
         * @brief Tells if there is next row available.
         * @return true if there's next row, of false if there's not.
         *
         * If you just want to iterate through rows, you don't need to call this method.
         * The next() method will return null pointer if there is no next row available,
         * so you can tell when to stop iterating. Furthermore, you should avoid
         * calling this method just for iterating through rows, because next() method
         * already does that internally (in most implementations).
         *
         * In other cases this method might be useful. For example when you read single cell:
         * @code
         * SqlQueryPtr results = db->("SELECT value FROM table WHERE rowid = ?", {rowId});
         * if (results->isError() || !results->hasNext())
         *    return "some default value";
         *
         * return results->getSingleCell().toString();
         * @endcode
         */
        bool hasNext();

        /**
         * @brief Gets error test of the most recent error.
         * @return Error text.
         */
        virtual QString getErrorText() = 0;

        /**
         * @brief Gets error code of the most recent error.
         * @return Error code as returned from DbPlugin.
         */
        virtual int getErrorCode() = 0;

        /**
         * @brief Gets list of column names in the results.
         * @return List of column names.
         */
        virtual QStringList getColumnNames() = 0;

        /**
         * @brief Gets number of columns in the results.
         * @return Columns count.
         */
        virtual int columnCount() = 0;

        /**
         * @brief Gets number of rows that were affected by the query.
         * @return Number of rows affected.
         *
         * For UPDATE this is number of rows updated.
         * For DELETE this is number of rows deleted.
         * FOR INSERT this is number of rows inserted (starting with SQLite 3.7.11 you can insert multiple rows with single INSERT statement).
         */
        virtual qint64 rowsAffected();

        /**
         * @brief Reads all rows immediately and returns them.
         * @return All data rows as a list.
         *
         * Don't use this method against huge data results.
         */
        virtual QList<SqlResultsRowPtr> getAll();

        /**
         * @brief Loads all data immediately into memory.
         *
         * This method makes sense only if you plan to use getAll() later on.
         * If you won't use getAll(), then calling this method is just a waste of memory.
         *
         * It is useful if you execute query asynchronously and you will be using all results.
         * In that case the asynchronous execution takes care of loading data from the database and the final code
         * just operates on in-memory data.
         */
        virtual void preload();

        /**
         * @brief Reads first column of first row and returns its value.
         * @return Value read.
         *
         * This method is useful when dealing with for example PRAGMA statement results,
         * or for SELECT queries with expected single row and single column.
         */
        virtual QVariant getSingleCell();

        /**
         * @brief Tells if there was an error while query execution.
         * @return true if there was an error, false otherwise.
         */
        virtual bool isError();

        /**
         * @brief Tells if the query execution was interrupted.
         * @return true if query was interrupted, or false otherwise.
         *
         * Interruption of execution is interpreted as an execution error,
         * so if this method returns true, then isError() will return true as well.
         */
        virtual bool isInterrupted();

        /**
         * @brief Retrieves ROWID of the INSERT'ed row.
         * @return ROWID as 64-bit signed integer or set of multiple columns. If empty, then there was no row inserted.
         * @see RowId
         * @see getRegularInsertRowId()
         */
        virtual RowId getInsertRowId();

        /**
         * @brief Retrieves ROWID of the INSERT'ed row.
         * @return ROWID as 64-bit signed integer.
         *
         * This is different from getInsertRowId(), because it assumes that the insert was made to a regular table,
         * while getInsertRowId() supports also inserts to WITHOUT ROWID tables.
         *
         * If you know that the insert was made to a regular table, you can use this method to simply get the ROWID.
         */
        virtual qint64 getRegularInsertRowId();

        /**
         * @brief columnAsList
         * @tparam T Data type to use for the result list.
         * @param name name of the column to get values from.
         * @return List of all values from given column.
         */
        template <class T>
        QList<T> columnAsList(const QString& name)
        {
            QList<T> list;
            SqlResultsRowPtr row;
            while (hasNext())
            {
                row = next();
                list << row->value(name).value<T>();
            }
            return list;
        }

        /**
         * @brief columnAsList
         * @tparam T Data type to use for the result list.
         * @param index Index of the column to get values from (must be between 0 and columnCount()-1).
         * @return List of all values from given column.
         */
        template <class T>
        QList<T> columnAsList(int index)
        {
            QList<T> list;
            if (index < 0 || index >= columnCount())
                return list;

            SqlResultsRowPtr row;
            while (hasNext())
            {
                row = next();
                list << row->value(index).value<T>();
            }
            return list;
        }

        QString getQuery() const;
        void setFlags(Db::Flags flags);
        void clearArgs();
        void setArgs(const QList<QVariant>& args);
        void setArgs(const QHash<QString,QVariant>& args);

    protected:
        /**
         * @brief Reads next row of results
         * @return Next results row, or null pointer if no more rows are available.
         *
         * This is pretty much the same as next(), except next() handles preloaded data,
         * while this method should work natively on the derived implementation of results object.
         */
        virtual SqlResultsRowPtr nextInternal() = 0;

        /**
         * @brief Tells if there is next row available.
         * @return true if there's next row, of false if there's not.
         *
         * This is pretty much the same as hasNext(), except hasNext() handles preloaded data,
         * while this method should work natively on the derived implementation of results object.
         */
        virtual bool hasNextInternal() = 0;

        virtual bool execInternal(const QList<QVariant>& args) = 0;
        virtual bool execInternal(const QHash<QString, QVariant>& args) = 0;

        /**
         * @brief Row ID of the most recently inserted row.
         */
        RowId insertRowId;

        /**
         * @brief Flag indicating if the data was preloaded with preload().
         */
        bool preloaded = false;

        /**
         * @brief Index of the next row to be returned.
         *
         * If the data was preloaded (see preload()), then iterating with next() whould use this index to find out
         * which preloaded row should be returned next.
         */
        int preloadedRowIdx = -1;

        /**
         * @brief Data preloaded with preload().
         */
        QList<SqlResultsRowPtr> preloadedData;

        qint64 affected = 0;

        QString query;
        QVariant queryArgs;
        Db::Flags flags;
};

class API_EXPORT RowIdConditionBuilder
{
    public:
        void setRowId(const RowId& rowId);
        const QHash<QString,QVariant>& getQueryArgs() const;
        QString build();

    protected:
        QStringList conditions;
        QHash<QString,QVariant> queryArgs;
};

/**
 * @brief Shared pointer to query object.
 * Results are usually passed as shared pointer, so it's used as needed and deleted when no longer required.
 */
typedef QSharedPointer<SqlQuery> SqlQueryPtr;

#endif // SQLQUERY_H