summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2016-02-20 20:49:17 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2016-02-20 20:49:17 +0100
commited42bdd74d70c7bcb9e1fb8f071ccb92b1515406 (patch)
treefc068b2f678d3d38d5ad398e6c85474306a0aa02
parentbc06643cd0c16140f6013be35b64732c1676e794 (diff)
downloadsink-ed42bdd74d70c7bcb9e1fb8f071ccb92b1515406.tar.gz
sink-ed42bdd74d70c7bcb9e1fb8f071ccb92b1515406.zip
Fetch more data on demand
We skip values we've already seen and only retrieve the new ones. This currently only properly works in a non-live query and we don't give the model any feedback when we can't fetch more data anymore. However, it generally works and we get the desired effect.
-rw-r--r--common/CMakeLists.txt1
-rw-r--r--common/modelresult.cpp4
-rw-r--r--common/queryrunner.cpp13
-rw-r--r--common/resultset.cpp134
-rw-r--r--common/resultset.h101
-rw-r--r--common/store.cpp3
-rw-r--r--common/typeindex.cpp4
-rw-r--r--tests/querytest.cpp8
8 files changed, 171 insertions, 97 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index fe72605..0d61d90 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -58,6 +58,7 @@ set(command_SRCS
58 typeindex.cpp 58 typeindex.cpp
59 resourcefacade.cpp 59 resourcefacade.cpp
60 resourceconfig.cpp 60 resourceconfig.cpp
61 resultset.cpp
61 domain/applicationdomaintype.cpp 62 domain/applicationdomaintype.cpp
62 domain/event.cpp 63 domain/event.cpp
63 domain/mail.cpp 64 domain/mail.cpp
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index 5b5a473..ceefa76 100644
--- a/common/modelresult.cpp
+++ b/common/modelresult.cpp
@@ -155,7 +155,8 @@ bool ModelResult<T, Ptr>::hasChildren(const QModelIndex &parent) const
155template<class T, class Ptr> 155template<class T, class Ptr>
156bool ModelResult<T, Ptr>::canFetchMore(const QModelIndex &parent) const 156bool ModelResult<T, Ptr>::canFetchMore(const QModelIndex &parent) const
157{ 157{
158 return !mEntityChildrenFetched.contains(parent.internalId()); 158 const auto id = parent.internalId();
159 return !mEntityChildrenFetched.contains(id) || mEntityChildrenFetchComplete.contains(id);
159} 160}
160 161
161template<class T, class Ptr> 162template<class T, class Ptr>
@@ -218,6 +219,7 @@ template<class T, class Ptr>
218void ModelResult<T, Ptr>::fetchEntities(const QModelIndex &parent) 219void ModelResult<T, Ptr>::fetchEntities(const QModelIndex &parent)
219{ 220{
220 const auto id = getIdentifier(parent); 221 const auto id = getIdentifier(parent);
222 mEntityChildrenFetchComplete.remove(id);
221 mEntityChildrenFetched.insert(id); 223 mEntityChildrenFetched.insert(id);
222 Trace() << "Loading child entities of parent " << id; 224 Trace() << "Loading child entities of parent " << id;
223 if (loadEntities) { 225 if (loadEntities) {
diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp
index 2f627bf..5ac1344 100644
--- a/common/queryrunner.cpp
+++ b/common/queryrunner.cpp
@@ -79,9 +79,12 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
79 mBatchSize(query.limit) 79 mBatchSize(query.limit)
80{ 80{
81 Trace() << "Starting query"; 81 Trace() << "Starting query";
82 if (query.limit && query.sortProperty.isEmpty()) {
83 Warning() << "A limited query without sorting is typically a bad idea.";
84 }
82 //We delegate loading of initial data to the result provider, os it can decide for itself what it needs to load. 85 //We delegate loading of initial data to the result provider, os it can decide for itself what it needs to load.
83 mResultProvider->setFetcher([=](const typename DomainType::Ptr &parent) { 86 mResultProvider->setFetcher([=](const typename DomainType::Ptr &parent) {
84 Trace() << "Running fetcher"; 87 Trace() << "Running fetcher. Offset: " << mOffset << " Batchsize: " << mBatchSize;
85 auto resultProvider = mResultProvider; 88 auto resultProvider = mResultProvider;
86 async::run<qint64>([=]() -> qint64 { 89 async::run<qint64>([=]() -> qint64 {
87 QueryWorker<DomainType> worker(query, instanceIdentifier, factory, bufferType, mResultTransformation); 90 QueryWorker<DomainType> worker(query, instanceIdentifier, factory, bufferType, mResultTransformation);
@@ -89,6 +92,7 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
89 return newRevision; 92 return newRevision;
90 }) 93 })
91 .template then<void, qint64>([query, this](qint64 newRevision) { 94 .template then<void, qint64>([query, this](qint64 newRevision) {
95 mOffset += mBatchSize;
92 //Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise. 96 //Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise.
93 if (query.liveQuery) { 97 if (query.liveQuery) {
94 mResourceAccess->sendRevisionReplayedCommand(newRevision); 98 mResourceAccess->sendRevisionReplayedCommand(newRevision);
@@ -325,7 +329,9 @@ ResultSet QueryWorker<DomainType>::filterAndSortSet(ResultSet &resultSet, const
325 }; 329 };
326 330
327 auto skip = [iterator]() { 331 auto skip = [iterator]() {
328 iterator->next(); 332 if (iterator->hasNext()) {
333 iterator->next();
334 }
329 }; 335 };
330 return ResultSet(generator, skip); 336 return ResultSet(generator, skip);
331 } else { 337 } else {
@@ -339,8 +345,7 @@ ResultSet QueryWorker<DomainType>::filterAndSortSet(ResultSet &resultSet, const
339 if ((operation != Sink::Operation_Removal) && filter(domainObject)) { 345 if ((operation != Sink::Operation_Removal) && filter(domainObject)) {
340 //In the initial set every entity is new 346 //In the initial set every entity is new
341 callback(domainObject, Sink::Operation_Creation); 347 callback(domainObject, Sink::Operation_Creation);
342 } 348 } } else {
343 } else {
344 //Always remove removals, they probably don't match due to non-available properties 349 //Always remove removals, they probably don't match due to non-available properties
345 if ((operation == Sink::Operation_Removal) || filter(domainObject)) { 350 if ((operation == Sink::Operation_Removal) || filter(domainObject)) {
346 //TODO only replay if this is in the currently visible set (or just always replay, worst case we have a couple to many results) 351 //TODO only replay if this is in the currently visible set (or just always replay, worst case we have a couple to many results)
diff --git a/common/resultset.cpp b/common/resultset.cpp
new file mode 100644
index 0000000..6e1479a
--- /dev/null
+++ b/common/resultset.cpp
@@ -0,0 +1,134 @@
1/*
2 * Copyright (C) 2016 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#include "resultset.h"
20
21#include "common/log.h"
22
23ResultSet::ResultSet()
24 : mIt(nullptr)
25{
26
27}
28
29ResultSet::ResultSet(const ValueGenerator &generator, const SkipValue &skip)
30 : mIt(nullptr),
31 mValueGenerator(generator),
32 mSkip(skip)
33{
34
35}
36
37ResultSet::ResultSet(const IdGenerator &generator)
38 : mIt(nullptr),
39 mGenerator(generator),
40 mSkip([this]() {
41 next();
42 })
43{
44
45}
46
47ResultSet::ResultSet(const QVector<QByteArray> &resultSet)
48 : mResultSet(resultSet),
49 mIt(mResultSet.constBegin()),
50 mSkip([this]() {
51 if (mIt != mResultSet.constEnd()) {
52 mIt++;
53 }
54 }),
55 mFirst(true)
56{
57
58}
59
60ResultSet::ResultSet(const ResultSet &other)
61 : mResultSet(other.mResultSet),
62 mIt(nullptr),
63 mFirst(true)
64{
65 if (other.mValueGenerator) {
66 mValueGenerator = other.mValueGenerator;
67 mSkip = other.mSkip;
68 } else if (other.mGenerator) {
69 mGenerator = other.mGenerator;
70 mSkip = [this]() {
71 next();
72 };
73 } else {
74 mResultSet = other.mResultSet;
75 mIt = mResultSet.constBegin();
76 mSkip = [this]() {
77 if (mIt != mResultSet.constEnd()) {
78 mIt++;
79 }
80 };
81 }
82}
83
84bool ResultSet::next()
85{
86 if (mIt) {
87 if (mIt != mResultSet.constEnd() && !mFirst) {
88 mIt++;
89 }
90 mFirst = false;
91 return mIt != mResultSet.constEnd();
92 } else if (mGenerator) {
93 Q_ASSERT(mGenerator);
94 mCurrentValue = mGenerator();
95 if (!mCurrentValue.isNull()) {
96 return true;
97 }
98 } else {
99 next([](const Sink::ApplicationDomain::ApplicationDomainType::Ptr &value, Sink::Operation){ return false; });
100 }
101 return false;
102}
103
104bool ResultSet::next(std::function<bool(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &value, Sink::Operation)> callback)
105{
106 Q_ASSERT(mValueGenerator);
107 return mValueGenerator(callback);
108}
109
110void ResultSet::skip(int number)
111{
112 Q_ASSERT(mSkip);
113 for (int i = 0; i < number; i++) {
114 mSkip();
115 }
116}
117
118QByteArray ResultSet::id()
119{
120 if (mIt) {
121 if (mIt == mResultSet.constEnd()) {
122 return QByteArray();
123 }
124 Q_ASSERT(mIt != mResultSet.constEnd());
125 return *mIt;
126 } else {
127 return mCurrentValue;
128 }
129}
130
131bool ResultSet::isEmpty()
132{
133 return mResultSet.isEmpty();
134}
diff --git a/common/resultset.h b/common/resultset.h
index 8a0720d..e513460 100644
--- a/common/resultset.h
+++ b/common/resultset.h
@@ -34,100 +34,20 @@ class ResultSet {
34 typedef std::function<QByteArray()> IdGenerator; 34 typedef std::function<QByteArray()> IdGenerator;
35 typedef std::function<void()> SkipValue; 35 typedef std::function<void()> SkipValue;
36 36
37 ResultSet() 37 ResultSet();
38 : mIt(nullptr) 38 ResultSet(const ValueGenerator &generator, const SkipValue &skip);
39 { 39 ResultSet(const IdGenerator &generator);
40 ResultSet(const QVector<QByteArray> &resultSet);
41 ResultSet(const ResultSet &other);
40 42
41 } 43 bool next();
44 bool next(std::function<bool(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &value, Sink::Operation)> callback);
42 45
43 ResultSet(const ValueGenerator &generator, const SkipValue &skip) 46 void skip(int number);
44 : mIt(nullptr),
45 mValueGenerator(generator),
46 mSkip(skip)
47 {
48 47
49 } 48 QByteArray id();
50 49
51 ResultSet(const IdGenerator &generator) 50 bool isEmpty();
52 : mIt(nullptr),
53 mGenerator(generator),
54 mSkip([this]() {
55 mGenerator();
56 })
57 {
58
59 }
60
61 ResultSet(const QVector<QByteArray> &resultSet)
62 : mResultSet(resultSet),
63 mIt(nullptr),
64 mSkip([this]() {
65 mGenerator();
66 })
67 {
68
69 }
70
71 bool next()
72 {
73 if (mGenerator) {
74 mCurrentValue = mGenerator();
75 } else {
76 if (!mIt) {
77 mIt = mResultSet.constBegin();
78 } else {
79 mIt++;
80 }
81 return mIt != mResultSet.constEnd();
82 }
83 if (!mCurrentValue.isNull()) {
84 return true;
85 }
86 return false;
87 }
88
89 bool next(std::function<bool(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &value, Sink::Operation)> callback)
90 {
91 Q_ASSERT(mValueGenerator);
92 return mValueGenerator(callback);
93 }
94
95 bool next(std::function<void(const QByteArray &key)> callback)
96 {
97 if (mGenerator) {
98 mCurrentValue = mGenerator();
99 } else {
100 if (!mIt) {
101 mIt = mResultSet.constBegin();
102 } else {
103 mIt++;
104 }
105 return mIt != mResultSet.constEnd();
106 }
107 return false;
108 }
109
110 void skip(int number)
111 {
112 Q_ASSERT(mSkip);
113 for (int i = 0; i < number; i++) {
114 mSkip();
115 }
116 }
117
118 QByteArray id()
119 {
120 if (mIt) {
121 return *mIt;
122 } else {
123 return mCurrentValue;
124 }
125 }
126
127 bool isEmpty()
128 {
129 return mResultSet.isEmpty();
130 }
131 51
132 private: 52 private:
133 QVector<QByteArray> mResultSet; 53 QVector<QByteArray> mResultSet;
@@ -136,5 +56,6 @@ class ResultSet {
136 IdGenerator mGenerator; 56 IdGenerator mGenerator;
137 ValueGenerator mValueGenerator; 57 ValueGenerator mValueGenerator;
138 SkipValue mSkip; 58 SkipValue mSkip;
59 bool mFirst;
139}; 60};
140 61
diff --git a/common/store.cpp b/common/store.cpp
index 2f88c6d..6847d22 100644
--- a/common/store.cpp
+++ b/common/store.cpp
@@ -75,12 +75,13 @@ static QList<QByteArray> getResources(const QList<QByteArray> &resourceFilter, c
75template <class DomainType> 75template <class DomainType>
76QSharedPointer<QAbstractItemModel> Store::loadModel(Query query) 76QSharedPointer<QAbstractItemModel> Store::loadModel(Query query)
77{ 77{
78 Trace() << "Query: "; 78 Trace() << "Query: " << ApplicationDomain::getTypeName<DomainType>();
79 Trace() << " Requested: " << query.requestedProperties; 79 Trace() << " Requested: " << query.requestedProperties;
80 Trace() << " Filter: " << query.propertyFilter; 80 Trace() << " Filter: " << query.propertyFilter;
81 Trace() << " Parent: " << query.parentProperty; 81 Trace() << " Parent: " << query.parentProperty;
82 Trace() << " Ids: " << query.ids; 82 Trace() << " Ids: " << query.ids;
83 Trace() << " IsLive: " << query.liveQuery; 83 Trace() << " IsLive: " << query.liveQuery;
84 Trace() << " Sorting: " << query.sortProperty;
84 auto model = QSharedPointer<ModelResult<DomainType, typename DomainType::Ptr> >::create(query, query.requestedProperties); 85 auto model = QSharedPointer<ModelResult<DomainType, typename DomainType::Ptr> >::create(query, query.requestedProperties);
85 86
86 //* Client defines lifetime of model 87 //* Client defines lifetime of model
diff --git a/common/typeindex.cpp b/common/typeindex.cpp
index 3ee2492..ddf5df5 100644
--- a/common/typeindex.cpp
+++ b/common/typeindex.cpp
@@ -36,6 +36,10 @@ static QByteArray getByteArray(const QVariant &value)
36 36
37static QByteArray toSortableByteArray(const QDateTime &date) 37static QByteArray toSortableByteArray(const QDateTime &date)
38{ 38{
39 //Sort invalid last
40 if (!date.isValid()) {
41 return QByteArray::number(std::numeric_limits<unsigned int>::max());
42 }
39 return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t()); 43 return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t());
40} 44}
41 45
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
index 3bb0427..2531c25 100644
--- a/tests/querytest.cpp
+++ b/tests/querytest.cpp
@@ -305,16 +305,22 @@ private slots:
305 query.propertyFilter.insert("folder", folderEntity->identifier()); 305 query.propertyFilter.insert("folder", folderEntity->identifier());
306 query.sortProperty = "date"; 306 query.sortProperty = "date";
307 query.limit = 1; 307 query.limit = 1;
308 query.liveQuery = false;
308 309
309 //Ensure all local data is processed 310 //Ensure all local data is processed
310 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); 311 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
311 312
312 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data
313 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 313 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
314 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 314 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
315 //The model is not sorted, but the limited set is sorted, so we can only test for the latest result. 315 //The model is not sorted, but the limited set is sorted, so we can only test for the latest result.
316 QCOMPARE(model->rowCount(), 1); 316 QCOMPARE(model->rowCount(), 1);
317 QCOMPARE(model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getProperty("uid").toByteArray(), QByteArray("testLatest")); 317 QCOMPARE(model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getProperty("uid").toByteArray(), QByteArray("testLatest"));
318
319 model->fetchMore(QModelIndex());
320 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
321 QCOMPARE(model->rowCount(), 2);
322 //We can't make any assumptions about the order of the indexes
323 // QCOMPARE(model->index(1, 0).data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getProperty("uid").toByteArray(), QByteArray("testSecond"));
318 } 324 }
319}; 325};
320 326