From aa0ef24ecdd2007d90ec4086a267a2ae930ae01e Mon Sep 17 00:00:00 2001 From: Ian Walters Date: Thu, 18 Jun 2009 10:29:55 +1000 Subject: Use QContiguousCache for cached queries By using QContiguousCache, and upper limit on cache size is assured. Also caching is set to occur in the background when possible. This should reduce any impact on animation performance. --- src/declarative/extra/qmlsqlquery.cpp | 142 +++++++++++++++++++++++++++++----- src/declarative/extra/qmlsqlquery.h | 3 + 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/declarative/extra/qmlsqlquery.cpp b/src/declarative/extra/qmlsqlquery.cpp index 0e2e91b..82ac50062 100644 --- a/src/declarative/extra/qmlsqlquery.cpp +++ b/src/declarative/extra/qmlsqlquery.cpp @@ -51,6 +51,9 @@ #include #include +#include +#include + QT_BEGIN_NAMESPACE QML_DEFINE_TYPE(QmlSqlBind, SqlBind) QML_DEFINE_TYPE(QmlSqlQuery, SqlQuery) @@ -192,12 +195,21 @@ class QmlSqlQueryPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QmlSqlQuery) public: - QmlSqlQueryPrivate(QmlSqlQuery *owner) : isSel(false), query(NULL), requireCache(false), count(-1), binds(owner) {} + QmlSqlQueryPrivate(QmlSqlQuery *owner) + : isSel(false), query(NULL), requireCache(false), count(-1), + cacheNear(100), cacheRetain(100), cacheSize(1000), + cacheInterval(250), scheduledRow(-1), cacheTimer(-1), + binds(owner) {} void prepareQuery() const; void bindQuery() const; - void cacheQuery() const; void grabRoles() const; + void clearCache(); + void initCache() const; + void hitCache(int row) const; + + void enactShift(int row) const; + QString queryText; bool isSel; QVariant connectionVariant; @@ -206,7 +218,14 @@ public: mutable int count; mutable QList roles; mutable QStringList roleNames; - mutable QVector< QVector< QVariant > > cache; + + typedef QContiguousCache Column; + mutable QVector< Column * > cache; + + int cacheNear, cacheRetain, cacheSize, cacheInterval; + + mutable int scheduledRow; + mutable int cacheTimer; class QmlSqlBindList : public QmlList { @@ -408,12 +427,13 @@ QHash QmlSqlQuery::data(int row, const QList &roles) const if (!d->requireCache) d->query->seek(row); + else + d->hitCache(row); for (int i = 0; i < roles.count(); ++i) { int column = roles[i]; - Q_ASSERT(column >= 0 && column < d->cache.size()); if (d->requireCache) - result.insert(column, d->cache[column].at(row)); + result.insert(column, d->cache[column]->at(row)); else result.insert(column, d->query->value(column)); } @@ -511,7 +531,7 @@ void QmlSqlQuery::resetBinds() if (!d->query) return; int oldcount = d->count; - d->cache.resize(0); + d->clearCache(); d->roles.clear(); d->roleNames.clear(); d->bindQuery(); @@ -520,7 +540,7 @@ void QmlSqlQuery::resetBinds() if (!d->query->exec()) qWarning() << "failed to execute query" << d->query->lastQuery() << d->query->boundValues() << d->query->lastError().text(); } - d->cacheQuery(); // may finish query + d->initCache(); // may finish query emitChanges(oldcount); } } @@ -540,12 +560,12 @@ void QmlSqlQuery::exec() if (d->isSel) { int oldcount = d->count; - d->cache.resize(0); + d->clearCache(); d->roles.clear(); d->roleNames.clear(); if (!d->query->exec()) qWarning() << "failed to execute query" << d->query->lastQuery() << d->query->boundValues() << d->query->lastError().text(); - d->cacheQuery(); // may finish query + d->initCache(); // may finish query emitChanges(oldcount); } else { if (!d->query->exec()) @@ -565,7 +585,7 @@ void QmlSqlQuery::resetQuery() Q_ASSERT(d->query != 0); delete d->query; d->query = 0; - d->cache.resize(0); + d->clearCache(); d->roles.clear(); d->roleNames.clear(); int oldcount = d->count; @@ -591,6 +611,22 @@ void QmlSqlQuery::emitChanges(int oldcount) } /* + \internal + + Handles delayed caching of the query. +*/ +void QmlSqlQuery::timerEvent(QTimerEvent *event) +{ + Q_D(QmlSqlQuery); + if (event->timerId() == d->cacheTimer) { + killTimer(d->cacheTimer); + d->cacheTimer = -1; + d->enactShift(d->scheduledRow); + d->scheduledRow = -1; + } +} + +/* Prepares the query. If the query starts with SELECT it is assumed to be a SELECT statement and the query is also executed. */ @@ -625,7 +661,7 @@ void QmlSqlQueryPrivate::prepareQuery() const if (isSel) { if (!query->exec()) qWarning() << "failed to execute query" << query->lastQuery() << query->boundValues() << query->lastError().text(); - cacheQuery(); + initCache(); } } @@ -645,26 +681,39 @@ void QmlSqlQueryPrivate::bindQuery() const } /* - If the query is connected to a database with simple locking or - that cannot ask for the count of a result set, caches the required - data of the query and finishes the query to release locks. + Clears cached query results +*/ +void QmlSqlQueryPrivate::clearCache() +{ + for (int i = 0; i < cache.size(); ++i) + delete cache[i]; + cache.resize(0); +} + +/* + If caching is required, initializes the cache with + column definitions and initial rows. - Otherwise just caches the count of the query. + Otherwise caches the count for the query. */ -void QmlSqlQueryPrivate::cacheQuery() const +void QmlSqlQueryPrivate::initCache() const { if (requireCache) { int row = 0; - while (query->next()) { + if (query->next()) { if (roleNames.isEmpty()) { grabRoles(); cache.resize(roleNames.count()); + for (int i = 0; i < cache.size(); ++i) + cache[i] = new Column(cacheSize); } Q_ASSERT(cache.size() > 0); - for (int i = 0; i < cache.size(); ++i) { - cache[i].append(query->value(i)); - } - row++; + do { + for (int i = 0; i < cache.size(); ++i) + cache[i]->insert(row, query->value(i)); + row++; + } while (!cache[0]->isFull() && query->next()); + while(query->next()) row++; } count = row; query->finish(); @@ -674,6 +723,57 @@ void QmlSqlQueryPrivate::cacheQuery() const } /* + Schedules future background cache loading based of + the given row. + + Should only be called if caching is active. +*/ +void QmlSqlQueryPrivate::hitCache(int row) const +{ + Q_Q(const QmlSqlQuery); + + Q_ASSERT(cache.size() > 0 && requireCache); + if (cache[0]->containsIndex(row)) { + int fi = cache[0]->firstIndex(); + int li = cache[0]->lastIndex(); + if (fi > 0 && row < fi + cacheNear) + scheduledRow = qMax(0, row + cacheRetain - cacheSize); + else if (li < count-1 && row > li - cacheNear) + scheduledRow = qMax(0, row - cacheRetain); + + if (scheduledRow != -1 && cacheTimer == -1) + cacheTimer = ((QmlSqlQuery *)q)->startTimer(cacheInterval); + } else { + if (cacheTimer != -1) { + ((QmlSqlQuery *)q)->killTimer(cacheTimer); + cacheTimer = -1; + } + scheduledRow = -1; + qDebug() << "cache miss for row:" << row << "count:" << count; + enactShift(row); + } +} + +/* + Load items from sql centered around row. +*/ +void QmlSqlQueryPrivate::enactShift(int targetRow) const +{ + Q_ASSERT(query && cache.size()); + query->exec(); + int row = targetRow < cache[0]->firstIndex() ? targetRow : qMax(targetRow, cache[0]->lastIndex()+1); + + if (query->seek(row)) { + do { + for (int i = 0; i < cache.size(); ++i) { + cache[i]->insert(row, query->value(i)); + } + row++; + } while(row < targetRow + cacheSize && query->next()); + } +} + +/* Gets the column names for the roles of the SqlQuery element. The query must be active and on a valid row. diff --git a/src/declarative/extra/qmlsqlquery.h b/src/declarative/extra/qmlsqlquery.h index dca3596..7aa0709 100644 --- a/src/declarative/extra/qmlsqlquery.h +++ b/src/declarative/extra/qmlsqlquery.h @@ -123,6 +123,9 @@ public: public slots: void exec(); +protected: + void timerEvent(QTimerEvent *); + private slots: void resetBinds(); void resetQuery(); -- cgit v0.12