├── Database ├── AsyncQuery.cpp ├── AsyncQuery.h ├── AsyncQueryResult.cpp ├── AsyncQueryResult.h ├── AsynqQueryModel.cpp ├── AsynqQueryModel.h ├── ConnectionManager.cpp └── ConnectionManager.h ├── LICENSE ├── QtAsyncSql.pro ├── README.md ├── data └── Northwind.sl3 ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h └── mainwindow.ui /Database/AsyncQuery.cpp: -------------------------------------------------------------------------------- 1 | #include "AsyncQuery.h" 2 | #include "ConnectionManager.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | namespace Database { 12 | 13 | class SqlTaskPrivate : public QRunnable 14 | { 15 | public: 16 | SqlTaskPrivate(AsyncQuery *instance, AsyncQuery::QueuedQuery query, 17 | ulong delayMs = 0); 18 | 19 | void run() override; 20 | 21 | private: 22 | AsyncQuery* _instance; 23 | AsyncQuery::QueuedQuery _query; 24 | ulong _delayMs; 25 | 26 | }; 27 | 28 | SqlTaskPrivate::SqlTaskPrivate(AsyncQuery *instance, AsyncQuery::QueuedQuery query, 29 | ulong delayMs) 30 | : _instance(instance) 31 | , _query(query) 32 | , _delayMs(delayMs) 33 | { 34 | } 35 | 36 | void SqlTaskPrivate::run() 37 | { 38 | QElapsedTimer timer; 39 | timer.start(); 40 | 41 | Q_ASSERT(_instance); 42 | 43 | ConnectionManager* conmgr = ConnectionManager::instance(); 44 | if (!conmgr->connectionExists()) { 45 | bool ret = conmgr->open(); 46 | Q_ASSERT(ret); 47 | } 48 | 49 | QSqlDatabase db = conmgr->threadConnection(); 50 | 51 | //delay query 52 | if (_delayMs > 0) { 53 | QThread::currentThread()->msleep(_delayMs); 54 | } 55 | 56 | AsyncQueryResult result; 57 | QSqlQuery query = QSqlQuery(db); 58 | bool succ = true; 59 | if (_query.isPrepared) { 60 | succ = query.prepare(_query.query); 61 | //bind values 62 | QMapIterator i(_query.boundValues); 63 | while (i.hasNext()) { 64 | i.next(); 65 | query.bindValue(i.key(), i.value()); 66 | } 67 | } 68 | if (succ) { 69 | if (_query.isPrepared) { 70 | query.exec(); 71 | } 72 | else { 73 | query.exec(_query.query); 74 | } 75 | } 76 | 77 | result._record = query.record(); 78 | result._error = query.lastError(); 79 | int cols = result._record.count(); 80 | 81 | while (query.next()) { 82 | QVector currow(cols); 83 | 84 | for (int ii = 0; ii < cols; ii++) { 85 | if (query.isNull(ii)) { 86 | currow[ii] = QVariant(); 87 | } 88 | else { 89 | currow[ii] = query.value(ii); 90 | } 91 | } 92 | result._data.append(currow); 93 | } 94 | 95 | //send result 96 | _instance->taskCallback(result); 97 | } 98 | 99 | /****************************************************************************************/ 100 | /* AsyncQuery */ 101 | /****************************************************************************************/ 102 | 103 | 104 | AsyncQuery::AsyncQuery(QObject* parent /* = nullptr */) 105 | : QObject(parent), logger("Database.AsyncQuery") 106 | , _deleteOnDone(false) 107 | , _delayMs(0) 108 | , _mode(Mode_Parallel) 109 | , _taskCnt(0) 110 | { 111 | } 112 | 113 | AsyncQuery::~AsyncQuery() 114 | { 115 | } 116 | 117 | void AsyncQuery::setMode(AsyncQuery::Mode mode) 118 | { 119 | QMutexLocker locker(&_mutex); 120 | _mode = mode; 121 | } 122 | 123 | AsyncQuery::Mode AsyncQuery::mode() 124 | { 125 | QMutexLocker locker(&_mutex); 126 | return _mode; 127 | } 128 | 129 | bool AsyncQuery::isRunning() const 130 | { 131 | QMutexLocker lock(&_mutex); 132 | return (_taskCnt > 0); 133 | } 134 | 135 | AsyncQueryResult AsyncQuery::result() const 136 | { 137 | QMutexLocker lock(&_mutex); 138 | return _result; 139 | } 140 | 141 | void AsyncQuery::prepare(const QString &query) 142 | { 143 | _curQuery.query = query; 144 | } 145 | 146 | void AsyncQuery::bindValue(const QString &placeholder, const QVariant &val) 147 | { 148 | _curQuery.boundValues[placeholder] = val; 149 | } 150 | 151 | void AsyncQuery::startExec() 152 | { 153 | _curQuery.isPrepared = true; 154 | startExecIntern(); 155 | } 156 | 157 | void AsyncQuery::startExec(const QString &query) 158 | { 159 | _curQuery.isPrepared = false; 160 | _curQuery.query = query; 161 | startExecIntern(); 162 | 163 | } 164 | 165 | bool AsyncQuery::waitDone(ulong msTimout) 166 | { 167 | QMutexLocker lock(&_mutex); 168 | if (_taskCnt > 0) 169 | return _waitcondition.wait(&_mutex, msTimout); 170 | else 171 | return true; 172 | } 173 | 174 | void AsyncQuery::startExecOnce(const QString &query, QObject *receiver, const char *member) 175 | { 176 | AsyncQuery *q = new AsyncQuery(); 177 | q->_deleteOnDone = true; 178 | connect(q, SIGNAL(execDone(Database::AsyncQueryResult)), 179 | receiver, member); 180 | q->startExec(query); 181 | } 182 | 183 | void AsyncQuery::setDelayMs(ulong ms) 184 | { 185 | QMutexLocker locker(&_mutex); 186 | _delayMs = ms; 187 | } 188 | 189 | void AsyncQuery::startExecIntern() 190 | { 191 | QMutexLocker lock(&_mutex); 192 | if (_mode == Mode_Parallel) { 193 | QThreadPool* pool = QThreadPool::globalInstance(); 194 | SqlTaskPrivate* task = new SqlTaskPrivate(this, _curQuery, _delayMs); 195 | incTaskCount(); 196 | pool->start(task); 197 | } else { 198 | if (_taskCnt == 0) { 199 | QThreadPool* pool = QThreadPool::globalInstance(); 200 | SqlTaskPrivate* task = new SqlTaskPrivate(this, _curQuery, _delayMs); 201 | incTaskCount(); 202 | pool->start(task); 203 | } else { 204 | if (_mode == Mode_Fifo) { 205 | _ququ.enqueue(_curQuery); 206 | } else { 207 | _ququ.clear(); 208 | _ququ.enqueue(_curQuery); 209 | } 210 | } 211 | } 212 | } 213 | 214 | void AsyncQuery::incTaskCount() 215 | { 216 | if (_taskCnt == 0) { 217 | emit busyChanged(true); 218 | } 219 | _taskCnt++; 220 | } 221 | 222 | void AsyncQuery::decTaskCount() 223 | { 224 | if (_taskCnt == 1) { 225 | emit busyChanged(false); 226 | } 227 | _taskCnt--; 228 | 229 | } 230 | 231 | void AsyncQuery::taskCallback(const AsyncQueryResult& result) 232 | { 233 | _mutex.lock(); 234 | Q_ASSERT(_taskCnt > 0); 235 | _result = result; 236 | if (_mode != Mode_Parallel && !_ququ.isEmpty()) { 237 | //start next query if queue not empty 238 | QueuedQuery query = _ququ.dequeue(); 239 | QThreadPool* pool = QThreadPool::globalInstance(); 240 | SqlTaskPrivate* task = new SqlTaskPrivate(this, query, _delayMs); 241 | pool->start(task); 242 | } else { 243 | decTaskCount(); 244 | } 245 | 246 | _waitcondition.wakeAll(); 247 | _mutex.unlock(); 248 | 249 | emit execDone(result); 250 | 251 | if (_deleteOnDone) { 252 | // note delete later should be thread save 253 | deleteLater(); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /Database/AsyncQuery.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AsyncQueryResult.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace Database { 14 | 15 | // class forward decl's 16 | class SqlTaskPrivate; 17 | 18 | /** 19 | * @brief Class to run a asynchron sql query. 20 | * 21 | * @details This class provides functionalities to execute sql queries in a 22 | * asynchrounous way. The interface is similar to the Qt's synchronous QSqlQuery 23 | * http://doc.qt.io/qt-5/qsqlquery.html 24 | * 25 | * Create a AsyncQuery, connect a handler to the 26 | * execDone(const Database::AsyncQueryResult &result) signal and start the query 27 | * with startExec(const QString &query). The query is started in a proper thread and 28 | * the connected slot is called when finished. Queries are internally maintained in 29 | * a QThreadPool. By using the QThreadPool the execution of queries is optimized to 30 | * the available cores on the cpu and threads are not blindly generated. 31 | * 32 | * QSqlDatabase's can be only be used from within the thread that created it. This 33 | * class provides a solution to run queries also from different threads 34 | * (http://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module). 35 | * 36 | */ 37 | class AsyncQuery : public QObject 38 | { 39 | friend class SqlTaskPrivate; 40 | Q_OBJECT 41 | 42 | public: 43 | /** 44 | * @brief The Mode defines how subsequent queries, triggered with 45 | * startExec() or startExec(const QString &query) are handled. 46 | */ 47 | 48 | typedef enum Mode { 49 | /** All queries for this object are started immediately and run in parallel. 50 | * The order in which subsequent queries are executed and finished can not 51 | * be guaranteed. Each query is started as soon as possible. 52 | */ 53 | Mode_Parallel, 54 | /** Subsquent queries for this object are started in a Fifo fashion. 55 | * A Subsequent query waits until the last query is finished. 56 | * This guarantees the order of query sequences. 57 | */ 58 | Mode_Fifo, 59 | /** Same as Mode_Fifo, but if a previous startExec call is not executed 60 | * yet it is skipped and overwritten by the current query. E.g. if a 61 | * graphical slider is bound to a sql query heavy database access can be 62 | * ommited by using this mode. 63 | */ 64 | Mode_SkipPrevious, 65 | } Mode; 66 | 67 | explicit AsyncQuery(QObject* parent = nullptr); 68 | virtual ~AsyncQuery(); 69 | 70 | /** 71 | * @brief Set the mode how subsequent queries are executed. 72 | */ 73 | void setMode(AsyncQuery::Mode mode); 74 | AsyncQuery::Mode mode(); 75 | 76 | /** 77 | * @brief Are there any queries running. 78 | */ 79 | bool isRunning() const; 80 | 81 | /** 82 | * @brief Retrieve the result of the last query 83 | */ 84 | AsyncQueryResult result() const; 85 | 86 | /** 87 | * @brief Prepare a AsyncQuery 88 | */ 89 | void prepare(const QString &query); 90 | 91 | /** 92 | * @brief BindValue for prepared query. 93 | */ 94 | void bindValue(const QString &placeholder, const QVariant &val); 95 | 96 | /** 97 | * @brief Start a prepared query execution set with prepare(const QString &query); 98 | */ 99 | void startExec(); //start 100 | 101 | /** 102 | * @brief Start the execution of the query. 103 | */ 104 | void startExec(const QString & query); 105 | 106 | /** 107 | * @brief Wait for query is finished 108 | * @details This function blocks the calling thread until query is finsihed. Using 109 | * this function provides same functionallity as Qt's synchron QSqlQuery. 110 | */ 111 | bool waitDone(ulong msTimout = ULONG_MAX); 112 | 113 | /** 114 | * @brief Convinience function to start a AsyncQuery once with given slot as result 115 | * handler. 116 | * @details Sample Usage: 117 | * \code{.cpp} 118 | * Database::AsyncQuery::startExecOnce( 119 | * "SELECT name FROM sqlite_master WHERE type='table'", 120 | * this, SLOT(myExecDoneHandler(const Database::AsyncQueryResult &))); 121 | * \endcode 122 | */ 123 | static void startExecOnce(const QString& query, QObject* receiver,const char* member); 124 | 125 | /** 126 | * @brief Convinience function to start a AsyncQuery once with given lambda function 127 | * as result handler. 128 | * @details Sample Usage: 129 | * \code{.cpp} 130 | * Database::AsyncQuery::startExecOnce( 131 | * "SELECT name FROM sqlite_master WHERE type='table'", 132 | * [=](const Database::AsyncQueryResult& res) { 133 | * //do handling here 134 | * }); 135 | * \endcode 136 | */ 137 | template 138 | static inline void startExecOnce(const QString& query, Func1 slot) 139 | { 140 | AsyncQuery * q = new AsyncQuery(); 141 | q->_deleteOnDone =true; 142 | connect(q, &AsyncQuery::execDone, slot); 143 | q->startExec(query); 144 | } 145 | 146 | /** 147 | * @brief Set delay to execute query. Mainly used for testing. 148 | * @details The executing query thread sleeps ms before query is executed. 149 | */ 150 | void setDelayMs(ulong ms); 151 | 152 | signals: 153 | /** 154 | * @brief Is emited when asynchronous query is done. 155 | */ 156 | void execDone(const Database::AsyncQueryResult& result); 157 | /** 158 | * @brief Is emited if asynchronous query running status changes. 159 | */ 160 | void busyChanged(bool busy); 161 | 162 | private: 163 | typedef struct QueuedQuery { 164 | bool isPrepared; 165 | QString query; 166 | QMap boundValues; 167 | } QueuedQuery; 168 | 169 | void startExecIntern(); 170 | /* use only in locked area */ 171 | void incTaskCount(); 172 | void decTaskCount(); 173 | 174 | // asynchronous callbacks 175 | // attention lives in the context of QRunable 176 | void taskCallback(const AsyncQueryResult& result); 177 | 178 | 179 | private: 180 | QLoggingCategory logger; 181 | 182 | QWaitCondition _waitcondition; 183 | mutable QMutex _mutex; 184 | bool _deleteOnDone; 185 | ulong _delayMs; 186 | Mode _mode; 187 | int _taskCnt; 188 | 189 | AsyncQueryResult _result; 190 | QQueue _ququ; 191 | QueuedQuery _curQuery; 192 | 193 | }; 194 | 195 | } 196 | -------------------------------------------------------------------------------- /Database/AsyncQueryResult.cpp: -------------------------------------------------------------------------------- 1 | #include "AsyncQueryResult.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace Database { 7 | 8 | AsyncQueryResult::AsyncQueryResult() 9 | { 10 | qRegisterMetaType(); 11 | } 12 | 13 | AsyncQueryResult::~AsyncQueryResult() 14 | { 15 | } 16 | 17 | AsyncQueryResult::AsyncQueryResult(const AsyncQueryResult& other) 18 | { 19 | _data = other._data; 20 | _record = other._record; 21 | _error = other._error; 22 | } 23 | 24 | AsyncQueryResult& AsyncQueryResult::operator=(const AsyncQueryResult& other) 25 | { 26 | _data = other._data; 27 | _record = other._record; 28 | _error = other._error; 29 | return *this; 30 | } 31 | 32 | QSqlError AsyncQueryResult::error() const 33 | { 34 | return _error; 35 | } 36 | 37 | QSqlRecord AsyncQueryResult::headRecord() const 38 | { 39 | return _record; 40 | } 41 | 42 | int AsyncQueryResult::count() const 43 | { 44 | return _data.size(); 45 | } 46 | 47 | QSqlRecord AsyncQueryResult::record(int row) const 48 | { 49 | QSqlRecord rec = _record; 50 | if (row >= 0 && row < _data.size()) { 51 | for (int i = 0; i < _record.count(); i++) { 52 | rec.setValue(i, _data[row][i]); 53 | } 54 | } 55 | return rec; 56 | } 57 | 58 | QVariant AsyncQueryResult::value(int row, int col) const 59 | { 60 | if (row >= 0 && row < _data.size()) { 61 | if (col >= 0 && col < _record.count()) 62 | return _data[row][col]; 63 | } 64 | return QVariant(); 65 | } 66 | 67 | QVariant AsyncQueryResult::value(int row, const QString &col) const 68 | { 69 | int colid = _record.indexOf(col); 70 | return value(row, colid); 71 | } 72 | 73 | bool AsyncQueryResult::isValid() const 74 | { 75 | return !_error.isValid(); 76 | } 77 | } // namespace 78 | -------------------------------------------------------------------------------- /Database/AsyncQueryResult.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace Database { 11 | 12 | // class forward decls's 13 | class SqlTaskPrivate; 14 | 15 | /** 16 | * @brief Represent a AsyncQuery result. 17 | * @details The query result is retreived via the getter functions. If an sql error 18 | * occured AsyncQueryResult is not isValid() and the error can retrieved with 19 | * error(). 20 | * 21 | */ 22 | class AsyncQueryResult 23 | { 24 | friend class SqlTaskPrivate; 25 | 26 | public: 27 | AsyncQueryResult(); 28 | virtual ~AsyncQueryResult(); 29 | AsyncQueryResult(const AsyncQueryResult&); 30 | AsyncQueryResult& operator=(const AsyncQueryResult& other); 31 | 32 | /** 33 | * @brief Returns \c true if no error occured in the query. 34 | */ 35 | bool isValid() const; 36 | 37 | /** 38 | * @brief Retrieve the sql error of the query. 39 | */ 40 | QSqlError error() const; 41 | 42 | /** 43 | * @brief Returns the head record to retrieve column names of the table. 44 | */ 45 | QSqlRecord headRecord() const; 46 | 47 | /** 48 | * @brief Returns the number of rows in the result. 49 | */ 50 | int count() const; 51 | 52 | /** 53 | * @brief Returns the QSqlRecord of given row. 54 | */ 55 | QSqlRecord record(int row) const; 56 | 57 | /** 58 | * @brief Returns the value of given row and columns. 59 | * @details If row or col is invalid a empty QVariatn is returned. 60 | */ 61 | QVariant value(int row, int col) const; 62 | 63 | /** 64 | * @brief Returns the value of given row and column name. 65 | * @details If row or col is invalid a empty QVariatn is returned. 66 | */ 67 | QVariant value(int row, const QString &col) const; 68 | 69 | /** 70 | * @brief Returns internal raw data structure of result. 71 | */ 72 | QVector> data() const { return _data; } 73 | 74 | private: 75 | QVector> _data; 76 | QSqlRecord _record; 77 | QSqlError _error; 78 | }; 79 | 80 | } // namespace 81 | 82 | Q_DECLARE_METATYPE(Database::AsyncQueryResult) 83 | -------------------------------------------------------------------------------- /Database/AsynqQueryModel.cpp: -------------------------------------------------------------------------------- 1 | #include "AsynqQueryModel.h" 2 | 3 | #include "AsyncQuery.h" 4 | 5 | 6 | namespace Database { 7 | 8 | 9 | AsyncQueryModel::AsyncQueryModel(QObject* parent) 10 | : QAbstractTableModel(parent) 11 | , logger("Database.AsyncQuerModel") 12 | { 13 | _aQuery = new AsyncQuery(this); 14 | connect (_aQuery, SIGNAL(execDone(Database::AsyncQueryResult)), 15 | this, SLOT(onExecDone(Database::AsyncQueryResult))); 16 | } 17 | 18 | AsyncQueryModel::~AsyncQueryModel() 19 | { 20 | 21 | } 22 | 23 | AsyncQuery *AsyncQueryModel::asyncQuery() const 24 | { 25 | return _aQuery; 26 | } 27 | 28 | void AsyncQueryModel::startExec(const QString &query) 29 | { 30 | _aQuery->startExec(query); 31 | } 32 | 33 | int AsyncQueryModel::rowCount(const QModelIndex &parent) const 34 | { 35 | Q_UNUSED(parent); 36 | return _res.count(); 37 | 38 | } 39 | 40 | int AsyncQueryModel::columnCount(const QModelIndex &parent) const 41 | { 42 | Q_UNUSED(parent); 43 | return _res.headRecord().count(); 44 | } 45 | 46 | QVariant AsyncQueryModel::data(const QModelIndex &index, int role) const 47 | { 48 | if (role == Qt::DisplayRole) 49 | { 50 | return _res.value(index.row(), index.column()); 51 | } 52 | return QVariant(); 53 | 54 | } 55 | 56 | QVariant AsyncQueryModel::headerData(int section, 57 | Qt::Orientation orientation, int role) const 58 | { 59 | if (role == Qt::DisplayRole) { 60 | if (orientation == Qt::Horizontal) { 61 | return _res.headRecord().fieldName(section); 62 | } 63 | } 64 | return QVariant(); 65 | } 66 | 67 | void AsyncQueryModel::onExecDone(const Database::AsyncQueryResult &result) 68 | { 69 | if (!result.isValid()) { 70 | qCDebug(logger) << "SqlError" << result.error().text(); 71 | } 72 | 73 | beginResetModel(); 74 | _res = result; 75 | endResetModel(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Database/AsynqQueryModel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "AsyncQueryResult.h" 7 | 8 | namespace Database { 9 | 10 | class AsyncQuery; 11 | 12 | /** 13 | * @brief The AsyncQueryModel class implementents a QtAbstractTableModel for asynchronous 14 | * queries. 15 | * @details The model can used with a QTableView to show the query results. 16 | */ 17 | class AsyncQueryModel : public QAbstractTableModel 18 | { 19 | Q_OBJECT 20 | public: 21 | explicit AsyncQueryModel(QObject* parent = 0); 22 | virtual ~AsyncQueryModel(); 23 | 24 | /** 25 | * @brief The internal AsynQuery object. Any startExec() function of this 26 | * object will update the model content. 27 | */ 28 | AsyncQuery *asyncQuery() const; 29 | 30 | /** 31 | * @brief Convinience function to start the an query. The model will be updated 32 | * when finsihed. 33 | */ 34 | void startExec(const QString &query); 35 | 36 | /** @name QAbstractItemModel interface */ 37 | ///@{ 38 | int rowCount(const QModelIndex &parent) const; 39 | int columnCount(const QModelIndex &parent) const; 40 | QVariant data(const QModelIndex &index, int role) const; 41 | QVariant headerData(int section, Qt::Orientation orientation, int role) const; 42 | ///@} 43 | 44 | protected slots: 45 | void onExecDone(const Database::AsyncQueryResult &result); 46 | 47 | private: 48 | QLoggingCategory logger; 49 | AsyncQueryResult _res; 50 | AsyncQuery *_aQuery; 51 | }; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Database/ConnectionManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionManager.h" 2 | #include 3 | 4 | 5 | namespace Database { 6 | 7 | ConnectionManager *ConnectionManager::_instance = nullptr; 8 | QMutex ConnectionManager::_instanceMutex; 9 | 10 | ConnectionManager::ConnectionManager(QObject* parent /*= nullptr */) 11 | : QObject(parent), logger("Database.ConnectionManager") 12 | { 13 | _port = -1; 14 | _precisionPolicy = QSql::LowPrecisionDouble; 15 | _type = "QMYSQL"; 16 | } 17 | 18 | ConnectionManager::~ConnectionManager() 19 | { 20 | closeAll(); 21 | } 22 | 23 | ConnectionManager *ConnectionManager::createInstance() 24 | { 25 | return instance(); 26 | } 27 | 28 | ConnectionManager *ConnectionManager::instance() 29 | { 30 | QMutexLocker locker(&_instanceMutex); 31 | if (_instance == nullptr) { 32 | _instance = new ConnectionManager(); 33 | } 34 | return _instance; 35 | } 36 | 37 | void ConnectionManager::destroyInstance() 38 | { 39 | QMutexLocker locker(&_instanceMutex); 40 | if (_instance != nullptr) { 41 | delete _instance; _instance = nullptr; 42 | } 43 | } 44 | 45 | void ConnectionManager::setType(QString type) 46 | { 47 | QMutexLocker locker(&_mutex); 48 | _type = type; 49 | } 50 | 51 | QString ConnectionManager::type() 52 | { 53 | QMutexLocker locker(&_mutex); 54 | return _type; 55 | } 56 | 57 | void ConnectionManager::setHostName(const QString & host) 58 | { 59 | QMutexLocker locker(&_mutex); 60 | _hostName = host; 61 | } 62 | 63 | QString ConnectionManager::hostName() const 64 | { 65 | QMutexLocker locker(&_mutex); 66 | return _hostName; 67 | } 68 | 69 | void ConnectionManager::setPort(int port) 70 | { 71 | QMutexLocker locker(&_mutex); 72 | _port = port; 73 | } 74 | 75 | int ConnectionManager::port() const 76 | { 77 | QMutexLocker locker(&_mutex); 78 | return _port; 79 | } 80 | 81 | void ConnectionManager::setDatabaseName(const QString& name) 82 | { 83 | QMutexLocker locker(&_mutex); 84 | _databaseName = name; 85 | } 86 | 87 | QString ConnectionManager::databaseName() const 88 | { 89 | QMutexLocker locker(&_mutex); 90 | return _databaseName; 91 | } 92 | 93 | 94 | void ConnectionManager::setUserName(const QString & name) 95 | { 96 | QMutexLocker locker(&_mutex); 97 | _userName = name; 98 | } 99 | 100 | QString ConnectionManager::userName() const 101 | { 102 | QMutexLocker locker(&_mutex); 103 | return _userName; 104 | } 105 | 106 | void ConnectionManager::setNumericalPrecisionPolicy( 107 | QSql::NumericalPrecisionPolicy precisionPolicy) 108 | { 109 | QMutexLocker locker(&_mutex); 110 | _precisionPolicy = precisionPolicy; 111 | } 112 | 113 | QSql::NumericalPrecisionPolicy 114 | ConnectionManager::numericalPrecisionPolicy() const 115 | { 116 | QMutexLocker locker(&_mutex); 117 | return _precisionPolicy; 118 | } 119 | 120 | void ConnectionManager::setPassword(const QString & password) 121 | { 122 | QMutexLocker locker(&_mutex); 123 | _password = password; 124 | } 125 | 126 | QString ConnectionManager::password() const 127 | { 128 | QMutexLocker locker(&_mutex); 129 | return _password; 130 | } 131 | 132 | int ConnectionManager::connectionCount() const 133 | { 134 | QMutexLocker locker(&_mutex); 135 | return _conns.count(); 136 | } 137 | 138 | bool ConnectionManager::connectionExists(QThread* t /*= QThread::currentThread()*/) const 139 | { 140 | QMutexLocker locker(&_mutex); 141 | return _conns.contains(t); 142 | } 143 | 144 | bool ConnectionManager::open() 145 | { 146 | QMutexLocker locker(&_mutex); 147 | 148 | QThread* curThread = QThread::currentThread(); 149 | 150 | if (_conns.contains(curThread)) { 151 | qCWarning(logger) << "ConnectionManager::open: " 152 | "there is a open connection"; 153 | return true; 154 | } 155 | 156 | QString conname = QString("CNM0x%1") .arg((qlonglong)curThread, 0, 16); 157 | QSqlDatabase dbconn = QSqlDatabase::addDatabase(_type, conname); 158 | dbconn.setHostName(_hostName); 159 | dbconn.setDatabaseName(_databaseName); 160 | dbconn.setUserName(_userName); 161 | dbconn.setPassword(_password); 162 | dbconn.setPort(_port); 163 | 164 | bool ok = dbconn.open(); 165 | 166 | if (ok != true) { 167 | qCCritical(logger) << "ConnectionManager::open: con= " << conname 168 | << ": Connection error=" << dbconn.lastError().text(); 169 | return false; 170 | } 171 | 172 | _conns.insert(curThread, dbconn); 173 | 174 | return true; 175 | } 176 | 177 | QSqlDatabase ConnectionManager::threadConnection() const 178 | { 179 | QMutexLocker locker(&_mutex); 180 | QThread* curThread = QThread::currentThread(); 181 | QSqlDatabase ret = _conns.value(curThread, QSqlDatabase()); 182 | return ret; 183 | } 184 | 185 | void ConnectionManager::dump() 186 | { 187 | qCInfo(logger) << "Database connections:" << _conns; 188 | } 189 | 190 | void ConnectionManager::closeAll() 191 | { 192 | QMutexLocker locker(&_mutex); 193 | /// @attention es koennte sein, dass das nicht geht, weil falscher thread 194 | 195 | while (_conns.count()) { 196 | QThread* t = _conns.firstKey(); 197 | QSqlDatabase db = _conns.take(t); 198 | db.close(); 199 | } 200 | } 201 | 202 | void ConnectionManager::closeOne(QThread* t) 203 | { 204 | QMutexLocker locker(&_mutex); 205 | /// @attention es koennte sein, dass das nicht geht, wenn falscher thread 206 | 207 | if (!_conns.contains(t)) { 208 | qCWarning(logger) << "closeOne no Connection open for thread " << t; 209 | return; 210 | } 211 | 212 | QSqlDatabase db = _conns.take(t); 213 | db.close(); 214 | } 215 | 216 | } // namespace 217 | -------------------------------------------------------------------------------- /Database/ConnectionManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | 14 | namespace Database { 15 | 16 | /** 17 | * @brief Maintains the database connection for asynchrone queries. 18 | * 19 | * @details Set up the ConnectionManager (database type, name, etc...) at programm 20 | * startup with createInstance() and setting the appropriate values to the instance. The 21 | * ConnectionManager instance provides the same interface as QSqlDatabase for initializing 22 | * the connection. A asynchrone query AsyncQuery inernally uses the configured instance 23 | * and opens a connection for each thread. 24 | * 25 | * Before application shutdown the instance have to be destroyed with destroyInstance(). 26 | * 27 | * @note All functions are thread save and reentrant 28 | */ 29 | class ConnectionManager : public QObject 30 | { 31 | Q_OBJECT 32 | 33 | /** Number if open connections. */ 34 | Q_PROPERTY(int connectionCount READ connectionCount NOTIFY connectionCountChanged) 35 | 36 | public: 37 | /** 38 | * @brief Call createInstance for initialization. 39 | * @return The ConnectionManager instance. 40 | */ 41 | static ConnectionManager *createInstance(); 42 | 43 | /** 44 | * @brief Get the instance. 45 | * @details If the instance is not created it will be created. 46 | * @return The ConnectionManager instance. 47 | */ 48 | static ConnectionManager *instance(); 49 | 50 | /** 51 | * @brief Delete the ConnectionManager instance. 52 | */ 53 | static void destroyInstance(); 54 | 55 | /** 56 | * @name Wrapper methods around QSqlDatabase 57 | * @note Each connection which was already opened (e.g. by the AsyncQuery) are not 58 | * reopened on change. 59 | */ 60 | ///@{ 61 | 62 | /** 63 | * @brief Set the database driver. Valid values are e.g. "QMYSQL", "QSQLITE", ... 64 | * @see http://doc.qt.io/qt-5/sql-driver.html 65 | */ 66 | void setType(QString type); 67 | QString type(); 68 | 69 | void setHostName(const QString & host); 70 | QString hostName() const; 71 | 72 | void setPort(int port); 73 | int port() const; 74 | 75 | void setDatabaseName(const QString& name); 76 | QString databaseName() const; 77 | 78 | void setUserName(const QString & name); 79 | QString userName() const; 80 | 81 | void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy); 82 | QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const; 83 | 84 | void setPassword(const QString & password); 85 | QString password() const; 86 | ///@} 87 | 88 | ///@{ 89 | /** 90 | * @name Connection maintainance. Basically for AsyncQuery internal usage. 91 | */ 92 | 93 | /** 94 | * @brief Number of open connections. 95 | */ 96 | int connectionCount() const; 97 | 98 | /** 99 | * @brief Returns \c true, if a database connection for thread t exists. 100 | * @param t [optional] thread Object. 101 | */ 102 | bool connectionExists(QThread* t = QThread::currentThread()) const; 103 | 104 | /** 105 | * @brief Opens a database connection for current thread. 106 | * @returns \c true on success 107 | */ 108 | bool open(); 109 | 110 | /** 111 | * @brief Check if a connection exists for current thread. 112 | * @note If no connection exists QSqlDatabase::isValid() is \c false 113 | */ 114 | QSqlDatabase threadConnection() const; 115 | 116 | /** @brief Dump all connections to tracelog */ 117 | void dump(); 118 | 119 | /** 120 | * @brief Close all open connections. 121 | */ 122 | void closeAll(); 123 | 124 | /** 125 | * @brief Close connection for thread t. 126 | * @note If connection does not exists nothing happens. 127 | */ 128 | void closeOne(QThread* t); 129 | ///@} 130 | 131 | signals: 132 | /** 133 | * @brief Is emitted if the number of connections is changed. 134 | */ 135 | void connectionCountChanged(int); 136 | 137 | private: 138 | ConnectionManager(QObject* parent = nullptr); 139 | virtual ~ConnectionManager(); 140 | 141 | //the static instance 142 | static ConnectionManager *_instance; 143 | static QMutex _instanceMutex; 144 | 145 | mutable QMutex _mutex; 146 | QMap _conns; 147 | 148 | QString _hostName; 149 | int _port; 150 | QString _userName; 151 | QString _databaseName; 152 | QSql::NumericalPrecisionPolicy _precisionPolicy; 153 | QString _password; 154 | QString _type; 155 | 156 | QLoggingCategory logger; 157 | }; 158 | 159 | } // namespace 160 | 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Asoss GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QtAsyncSql.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-02-16T07:23:31 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui sql 8 | 9 | CONFIG += c++11 10 | 11 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 12 | 13 | TARGET = QtThreadedAsyncSql 14 | TEMPLATE = app 15 | 16 | 17 | SOURCES += main.cpp\ 18 | mainwindow.cpp \ 19 | Database/AsyncQuery.cpp \ 20 | Database/AsyncQueryResult.cpp \ 21 | Database/ConnectionManager.cpp \ 22 | Database/AsynqQueryModel.cpp 23 | 24 | HEADERS += mainwindow.h \ 25 | Database/AsyncQuery.h \ 26 | Database/AsyncQueryResult.h \ 27 | Database/ConnectionManager.h \ 28 | Database/AsynqQueryModel.h 29 | 30 | FORMS += mainwindow.ui 31 | 32 | copydata.commands = $(COPY_DIR) $$PWD/data $$OUT_PWD 33 | first.depends = $(first) copydata 34 | export(first.depends) 35 | export(copydata.commands) 36 | QMAKE_EXTRA_TARGETS += first copydata 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtAsyncSql 2 | QT based classes to support asynchronous and threaded SQL queries. 3 | 4 | ##Introduction 5 | 6 | ###Overview 7 | Qt provides QSqlQuery class for synchronous database access. Often asynchronous and threaded database access is desired. QtAsyncSql provides a implementation for asynchronous database access using the Qt Api. 8 | 9 | ### Features 10 | * Asynchronous SQL queries with Qt's SIGNAL and SLOT mechanism. 11 | * Database access from distinct threads. 12 | (Closes the gap of Qt's Database Api: *"A connection can only be used from within the thread that created it."* See http://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module). 13 | * Fast parallel query execution. 14 | AsyncQueries internally are distributed via QRunnable tasks in a QThreadPool which is designed to optimally leverage the available number of cores on your hardware. There is no massive thread generation if a lot of queries are started. 15 | * Different execution modes *Parallel*, *Fifo* and *SkipPrevious*. 16 | 17 | ### Make 18 | To build the demo application simply run `qmake && make` or open `QtAsyncSql.pro` file in qtCreator. 19 | For the implementation **Qt5.5** and **c++11** compiler was used (Note that the source should be adaptable without much effort to **Qt4** and lower c++ standards.). 20 | 21 | 22 | ###Example Usage 23 | All relevant classes (can be found in the folder Database) are embedded in the namespace `Database`. In the main function the ConnectionManager need to be created, set up and destroyed: 24 | ```cpp 25 | int main(int argc, char *argv[]) 26 | { 27 | Database::ConnectionManager *mgr = Database::ConnectionManager::createInstance(); 28 | mgr->setType("QSQLITE"); //set the driver 29 | mgr->setDatabaseName( "/data/Northwind.sl3"); //set database name 30 | mgr->setHostname(...) //set host 31 | 32 | //... do the main loop 33 | 34 | Database::ConnectionManager::destroyInstance(); 35 | } 36 | ``` 37 | Next in any thread an AsyncQuery can be created, configured and started: 38 | ```cpp 39 | Database::AsyncQuery query = new Database::AsyncQuery(); 40 | connect (query, SIGNAL(execDone(Database::AsyncQueryResult)), 41 | this, SLOT(onExecDone(Database::AsyncQueryResult))); 42 | query->startExec("SELECT * FROM Companies"); 43 | //...execution continous immediatly 44 | ``` 45 | 46 | When database access is finished the SIGNAL is triggered and the SLOT prints the result table: 47 | 48 | ```cpp 49 | void MainWindow::onExecDone(const Database::AsyncQueryResult &result) 50 | { 51 | if (!result.isValid()) { 52 | qDebug() << "SqlError" << result.error().text(); 53 | } else { 54 | int columns = result.headRecord().count(); 55 | for (int row = 0; row < result.count(); row++) { 56 | for (int col = 0; col < columns; col++) { 57 | qDebug() << result.value(row, col).toString(); 58 | } 59 | } 60 | } 61 | } 62 | ``` 63 | ### Demo Application 64 | The QtAsyncSql Demo application (build the application) demonstrates all provided features. 65 | 66 | ##Details 67 | This section describes the implemented interface. For further details it is refered to the comments in the header files. 68 | 69 | ###ConnectionManager Class 70 | Maintains the database connection for asynchrone queries. Internally several connections are opened to access the database from different threads. 71 | 72 | ###AsyncQuery Class 73 | Asynchronous queries are started via: 74 | ```cpp 75 | void startExec(const QString &query); 76 | ``` 77 | There is also support for prepared statements with value binding. (Note that dabase preparation is currently done each time startExec() is called -> need to be fixed.) 78 | ```cpp 79 | void prepare(const QString &query); 80 | void bindValue(const QString &placeholder, const QVariant &val); 81 | void startExec(); 82 | ``` 83 | Following signals are provided: 84 | ```cpp 85 | void execDone(const Database::AsyncQueryResult& result); // query has finished execution 86 | void busyChanged(bool busy); // busy indicator 87 | ``` 88 | 89 | #### Modes 90 | The different modes define how subsequent `startExec(...)` calls are handled. 91 | 92 | * **Mode_Parallel** (default) 93 | All queries started by the AsyncQuery object are started immediately and run in parallel. The order in which subsequent queries are executed and finished can not be guaranteed. Each query is started as soon as possible. 94 | * **Mode_Fifo** 95 | Subsquent queries for the AsyncQuery object are started in a Fifo fashion. A Subsequent query waits until the last query is finished. This guarantees the order of query sequences. 96 | * **Mode_SkipPrevious** 97 | Same as **Mode_Fifo**, but if a previous `startExec(...)` call is not executed yet it is skipped and overwritten by the currrent query. E.g. if a graphical slider is bound to a sql query heavy database access can be ommited by using this mode (see the demo application). 98 | 99 | ####Convenience Functions 100 | If a query should be executed just once AsynQuery provides 2 static convenience functions (`static void startExecOnce 101 | (...)`) where no explicit object needs to be created. 102 | ```cpp 103 | static void startExecOnce(const QString& query, QObject* receiver,const char* member); 104 | template 105 | static inline void startExecOnce(const QString& query, Func1 slot) 106 | ``` 107 | Which can be used as follows: 108 | 109 | ```cpp 110 | //signal slot style 111 | Database::AsyncQuery::startExecOnce("SELECT name FROM sqlite_master WHERE type='table'", 112 | this, SLOT(myExecDoneHandler(const Database::AsyncQueryResult &))); 113 | //and lambda style 114 | Database::AsyncQuery::startExecOnce("SELECT name FROM sqlite_master WHERE type='table'", 115 | [=](const Database::AsyncQueryResult& res) { 116 | //do handling directly here in lambda 117 | }); 118 | ``` 119 | 120 | ####Others 121 | Block the calling thread until all started queries are executed (allow synchronous execution): 122 | ``` 123 | bool waitDone(ulong msTimout = ULONG_MAX); 124 | ``` 125 | Delay each query execution for `ms` milliseconds before it is started. This does not block the calling thread, but only the internall query thread (is mainly used for testing, see the demo): 126 | ```cpp 127 | void setDelayMs(ulong ms); 128 | ``` 129 | 130 | 131 | ###AsyncQueryResult Class 132 | The query result is retreived via the getter functions. If an sql error occured AsyncQueryResult is not valid and the error can be retrieved. 133 | 134 | ###AsyncQueryModel Class 135 | The AsyncQueryModel class implementents a QtAbstractTableModel for asynchronous queries which can be used with a QTableView to show the query results. 136 | 137 | Create the model and bind it to a view: 138 | ```cpp 139 | Database::AsyncQueryModel *queryModel = new Database::AsyncQueryModel(); 140 | QTableView *view = new QTableView(); 141 | view->setModel(queryModel); 142 | view->show(); 143 | ``` 144 | Start query directly: 145 | ```cpp 146 | queryModel->startExec("SELECT * FROM Companies"); //updates the bound views 147 | ``` 148 | Or via the model's AsyncQuery object: 149 | ```cpp 150 | Database::AsyncQuery *query = queryModel->asyncQuery(); 151 | query->prepare("SELECT * FROM Products WHERE UnitPrice < :price"); 152 | query->bindValue(":price", value); 153 | query->startExec(); //updates the bound views 154 | ``` -------------------------------------------------------------------------------- /data/Northwind.sl3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/featureNull/QtAsyncSql/7faa2853acb5f430002ec6302fadba6c796e76ea/data/Northwind.sl3 -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | #include "Database/ConnectionManager.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication a(argc, argv); 9 | 10 | Database::ConnectionManager *mgr = Database::ConnectionManager::createInstance(); 11 | mgr->setType("QSQLITE"); 12 | mgr->setDatabaseName(QApplication::applicationDirPath() + "/data/Northwind.sl3"); 13 | 14 | 15 | MainWindow w; 16 | w.show(); 17 | 18 | int ret = a.exec(); 19 | 20 | Database::ConnectionManager::destroyInstance(); 21 | 22 | return ret; 23 | } 24 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include 5 | 6 | #include "Database/ConnectionManager.h" 7 | #include "Database/AsyncQuery.h" 8 | #include "Database/AsynqQueryModel.h" 9 | #include "QComboBox" 10 | 11 | 12 | #include "QSqlQuery" 13 | #include "QStringListModel" 14 | 15 | MainWindow::MainWindow(QWidget *parent) 16 | : QMainWindow(parent) 17 | , ui(new Ui::MainWindow) 18 | , _tableModel(nullptr) 19 | { 20 | ui->setupUi(this); 21 | 22 | //Setup Tables tab 23 | connect (ui->cbTables, SIGNAL(currentIndexChanged(QString)), 24 | this, SLOT(onComboBoxChanged(QString))); 25 | 26 | //fill combobox with table Strings use lambda expression 27 | Database::AsyncQuery::startExecOnce( 28 | "SELECT name FROM sqlite_master WHERE type='table'", 29 | [=](const Database::AsyncQueryResult& res) { 30 | for (int i = 0; i < res.count(); i++) { 31 | ui->cbTables->addItem(res.value(i, "name").toString()); 32 | } 33 | if (res.count() > 0) { 34 | ui->cbTables->setCurrentIndex(0); 35 | } 36 | }); 37 | 38 | _tableModel = new Database::AsyncQueryModel(this); 39 | ui->tvTables->setModel(_tableModel); 40 | 41 | connect (_tableModel->asyncQuery(), SIGNAL(busyChanged(bool)), 42 | this, SLOT(onBusyChanged(bool))); 43 | 44 | //setup SqlStatement tab 45 | ui->teQuery->setText(QString() 46 | +"SELECT o.OrderID, c.CompanyName, e.FirstName, e.LastName\n" 47 | +"FROM Orders o\n" 48 | +" JOIN Employees e ON (e.EmployeeID = o.EmployeeID)\n" 49 | +" JOIN Customers c ON (c.CustomerID = o.CustomerID)\n" 50 | +"WHERE o.ShippedDate > o.RequiredDate AND o.OrderDate > '1-Jan-1998'\n" 51 | +"ORDER BY c.CompanyName;\n" 52 | ); 53 | 54 | 55 | _queryModel = new Database::AsyncQueryModel(this); 56 | ui->tvQuery->setModel(_queryModel); 57 | 58 | connect (_queryModel->asyncQuery(), SIGNAL(busyChanged(bool)), 59 | this, SLOT(onBusyChanged(bool))); 60 | connect (ui->btnQuery, &QPushButton::clicked, 61 | [=] { 62 | _queryModel->startExec(ui->teQuery->toPlainText()); 63 | }); 64 | 65 | 66 | //setup mode tab 67 | connect (ui->btnExec4Queries, SIGNAL(clicked(bool)), 68 | this, SLOT(onExec4Queries(bool))); 69 | connect (ui->btnClearResults, SIGNAL(clicked(bool)), 70 | this, SLOT(onClearClicked(bool))); 71 | 72 | 73 | _aQuery = new Database::AsyncQuery(this); 74 | connect (_aQuery, SIGNAL(execDone(Database::AsyncQueryResult)), 75 | this, SLOT(onExec4QueriesDone(Database::AsyncQueryResult))); 76 | connect (_aQuery, SIGNAL(busyChanged(bool)), 77 | this, SLOT(onBusyChanged(bool))); 78 | 79 | 80 | _sliderModel = new Database::AsyncQueryModel(this); 81 | ui->tvSlider->setModel(_sliderModel); 82 | 83 | connect (_sliderModel->asyncQuery(), SIGNAL(busyChanged(bool)), 84 | this, SLOT(onBusyChanged(bool))); 85 | 86 | connect (ui->slUnitPrice, SIGNAL(valueChanged(int)), 87 | this, SLOT(onSliderChanged(int))); 88 | 89 | } 90 | 91 | MainWindow::~MainWindow() 92 | { 93 | delete ui; 94 | } 95 | 96 | void MainWindow::onBusyChanged(bool busy) 97 | { 98 | if (busy) { 99 | ui->pbLoad->setMaximum(0); 100 | ui->pbLoad->setValue(0); 101 | } else { 102 | ui->pbLoad->setMaximum(1); 103 | ui->pbLoad->setValue(1); 104 | } 105 | } 106 | 107 | void MainWindow::onComboBoxChanged(const QString &index) 108 | { 109 | _tableModel->startExec("SELECT * FROM '" + index + "'"); 110 | } 111 | 112 | void MainWindow::onClearClicked(bool clicked) 113 | { 114 | Q_UNUSED(clicked); 115 | ui->lblCategroies->setText("---"); 116 | ui->lblCustomers->setText("---"); 117 | ui->lblEmployees->setText("---"); 118 | ui->lblOrders->setText("---"); 119 | } 120 | 121 | void MainWindow::onExec4Queries(bool clicked) 122 | { 123 | Q_UNUSED(clicked) 124 | onClearClicked(true); 125 | if (ui->rbParallel->isChecked()) { 126 | _aQuery->setMode(Database::AsyncQuery::Mode_Parallel); 127 | } else if (ui->rbFifo->isChecked()) { 128 | _aQuery->setMode(Database::AsyncQuery::Mode_Fifo); 129 | } else if (ui->rbSkipPrevious->isChecked()) { 130 | _aQuery->setMode(Database::AsyncQuery::Mode_SkipPrevious); 131 | } 132 | 133 | if (ui->cbDelay->isChecked()) { 134 | _aQuery->setDelayMs(500); 135 | } else { 136 | _aQuery->setDelayMs(0); 137 | } 138 | 139 | _aQuery->startExec("SELECT COUNT(*) AS NumCategories FROM Categories"); 140 | _aQuery->startExec("SELECT COUNT(*) AS NumCustomers FROM Customers"); 141 | _aQuery->startExec("SELECT COUNT(*) AS NumEmployees FROM Employees"); 142 | _aQuery->startExec("SELECT COUNT(*) AS NumOrders FROM Orders"); 143 | } 144 | 145 | void MainWindow::onExec4QueriesDone(const Database::AsyncQueryResult &result) 146 | { 147 | if (result.headRecord().fieldName(0) == "NumCategories") { 148 | ui->lblCategroies->setText(result.value(0,0).toString()); 149 | } else if (result.headRecord().fieldName(0) == "NumCustomers") { 150 | ui->lblCustomers->setText(result.value(0,0).toString()); 151 | } else if (result.headRecord().fieldName(0) == "NumEmployees") { 152 | ui->lblEmployees->setText(result.value(0,0).toString()); 153 | } else if (result.headRecord().fieldName(0) == "NumOrders") { 154 | ui->lblOrders->setText(result.value(0,0).toString()); 155 | } 156 | } 157 | 158 | void MainWindow::onSliderChanged(int value) 159 | { 160 | ui->lblSlider->setText(QString::number(value)); 161 | Database::AsyncQuery *aQuery = _sliderModel->asyncQuery(); 162 | 163 | if (ui->rbParallel->isChecked()) { 164 | aQuery->setMode(Database::AsyncQuery::Mode_Parallel); 165 | } else if (ui->rbFifo->isChecked()) { 166 | aQuery->setMode(Database::AsyncQuery::Mode_Fifo); 167 | } else if (ui->rbSkipPrevious->isChecked()) { 168 | aQuery->setMode(Database::AsyncQuery::Mode_SkipPrevious); 169 | } 170 | 171 | if (ui->cbDelay->isChecked()) { 172 | aQuery->setDelayMs(500); 173 | } else { 174 | aQuery->setDelayMs(0); 175 | } 176 | 177 | aQuery->prepare("SELECT * FROM Products WHERE UnitPrice < :price"); 178 | aQuery->bindValue(":price", value); 179 | aQuery->startExec(); 180 | } 181 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Ui { 9 | class MainWindow; 10 | } 11 | 12 | namespace Database { 13 | class AsyncQueryModel; 14 | } 15 | 16 | class MainWindow : public QMainWindow 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit MainWindow(QWidget *parent = 0); 22 | ~MainWindow(); 23 | 24 | public slots: 25 | void onBusyChanged(bool busy); 26 | 27 | void onComboBoxChanged (const QString &index); 28 | void onExec4Queries(bool clicked); 29 | void onClearClicked(bool clicked); 30 | void onExec4QueriesDone(const Database::AsyncQueryResult& result); 31 | void onSliderChanged(int value); 32 | 33 | private: 34 | Ui::MainWindow *ui; 35 | 36 | QString str; 37 | 38 | Database::AsyncQueryModel *_tableModel; 39 | Database::AsyncQueryModel *_queryModel; 40 | Database::AsyncQuery *_aQuery; 41 | Database::AsyncQueryModel *_sliderModel; 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 790 10 | 527 11 | 12 | 13 | 14 | QtAsyncSql Demo 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | Tables 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 200 37 | 0 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::Horizontal 46 | 47 | 48 | 49 | 40 50 | 20 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | SqlStatements 67 | 68 | 69 | 70 | 71 | 72 | 73 | 0 74 | 0 75 | 76 | 77 | 78 | QTextEdit::NoWrap 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | setQuery 88 | 89 | 90 | 91 | 92 | 93 | 94 | Qt::Vertical 95 | 96 | 97 | 98 | 20 99 | 40 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 0 111 | 0 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Modes 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Parallel 129 | 130 | 131 | true 132 | 133 | 134 | 135 | 136 | 137 | 138 | Fifo 139 | 140 | 141 | 142 | 143 | 144 | 145 | SkipPrevious 146 | 147 | 148 | 149 | 150 | 151 | 152 | Enable exec delay (500ms) 153 | 154 | 155 | true 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Exec4AsyncQueries 167 | 168 | 169 | 170 | 171 | 172 | 173 | Select COUNT(*) from following 4 tables 174 | 175 | 176 | 177 | 178 | 179 | 180 | Qt::Horizontal 181 | 182 | 183 | 184 | 40 185 | 20 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | Categories 200 | 201 | 202 | 203 | 204 | 205 | 206 | --- 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | Customers 218 | 219 | 220 | 221 | 222 | 223 | 224 | --- 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | Employees 236 | 237 | 238 | 239 | 240 | 241 | 242 | --- 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | Orders 254 | 255 | 256 | 257 | 258 | 259 | 260 | --- 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | ClearResults 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | SELECT * FROM Products WHERE UnitPrice < 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 20 289 | 0 290 | 291 | 292 | 293 | --- 294 | 295 | 296 | 297 | 298 | 299 | 300 | 50 301 | 302 | 303 | true 304 | 305 | 306 | Qt::Horizontal 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | true 323 | 324 | 325 | 1 326 | 327 | 328 | 1 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 0 338 | 0 339 | 790 340 | 25 341 | 342 | 343 | 344 | 345 | 346 | TopToolBarArea 347 | 348 | 349 | false 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | --------------------------------------------------------------------------------