diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h')
| -rw-r--r-- | SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h new file mode 100644 index 0000000..a7610fa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h @@ -0,0 +1,323 @@ +#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 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 SELECT statements this is number of returned rows. + * 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; + + int affected = 0; + + QString query; + QVariant queryArgs; + Db::Flags flags; +}; + +class API_EXPORT RowIdConditionBuilder +{ + public: + void setRowId(const RowId& rowId); + const QHash<QString,QVariant>& getQueryArgs(); + 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 |
