diff options
author | Rémi Nicole <nicole@kolabsystems.com> | 2018-05-28 10:08:49 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-05-28 10:09:31 +0200 |
commit | f400cee1d5896577c22626d0cf50478057989857 (patch) | |
tree | e24e77562e0e893f281965c512ca4b74f88b20d3 | |
parent | 411c7cdad70c5c7902002545fd107ed1b2ac06ac (diff) | |
download | sink-f400cee1d5896577c22626d0cf50478057989857.tar.gz sink-f400cee1d5896577c22626d0cf50478057989857.zip |
Implement ranged queries
Summary:
Notes:
- For now, only for QDateTime indexes
- Invalid QDateTimes are stored in the index (subject to change)
- Should be a drop-in replacement from ValueIndexes (except for `In` and `Contains` queries)
Reviewers: cmollekopf
Tags: #sink
Differential Revision: https://phabricator.kde.org/D13105
-rw-r--r-- | common/domain/typeimplementations.cpp | 5 | ||||
-rw-r--r-- | common/domain/typeimplementations_p.h | 18 | ||||
-rw-r--r-- | common/index.cpp | 14 | ||||
-rw-r--r-- | common/index.h | 4 | ||||
-rw-r--r-- | common/query.cpp | 12 | ||||
-rw-r--r-- | common/query.h | 1 | ||||
-rw-r--r-- | common/typeindex.cpp | 121 | ||||
-rw-r--r-- | common/typeindex.h | 13 | ||||
-rw-r--r-- | tests/querytest.cpp | 107 |
9 files changed, 274 insertions, 21 deletions
diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp index e65c998..a8f4baf 100644 --- a/common/domain/typeimplementations.cpp +++ b/common/domain/typeimplementations.cpp | |||
@@ -38,7 +38,7 @@ using namespace Sink::ApplicationDomain; | |||
38 | MAPPER.addMapping<ENTITYTYPE::PROPERTY, Sink::ApplicationDomain::Buffer::ENTITYTYPE, Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder>(&Sink::ApplicationDomain::Buffer::ENTITYTYPE::LOWERCASEPROPERTY, &Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder::add_##LOWERCASEPROPERTY); | 38 | MAPPER.addMapping<ENTITYTYPE::PROPERTY, Sink::ApplicationDomain::Buffer::ENTITYTYPE, Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder>(&Sink::ApplicationDomain::Buffer::ENTITYTYPE::LOWERCASEPROPERTY, &Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder::add_##LOWERCASEPROPERTY); |
39 | 39 | ||
40 | typedef IndexConfig<Mail, | 40 | typedef IndexConfig<Mail, |
41 | ValueIndex<Mail::Date>, | 41 | SortedIndex<Mail::Date>, |
42 | ValueIndex<Mail::Folder>, | 42 | ValueIndex<Mail::Folder>, |
43 | ValueIndex<Mail::ParentMessageId>, | 43 | ValueIndex<Mail::ParentMessageId>, |
44 | ValueIndex<Mail::MessageId>, | 44 | ValueIndex<Mail::MessageId>, |
@@ -64,7 +64,8 @@ typedef IndexConfig<Addressbook, | |||
64 | > AddressbookIndexConfig; | 64 | > AddressbookIndexConfig; |
65 | 65 | ||
66 | typedef IndexConfig<Event, | 66 | typedef IndexConfig<Event, |
67 | ValueIndex<Event::Uid> | 67 | ValueIndex<Event::Uid>, |
68 | SortedIndex<Event::StartTime> | ||
68 | > EventIndexConfig; | 69 | > EventIndexConfig; |
69 | 70 | ||
70 | typedef IndexConfig<Todo, | 71 | typedef IndexConfig<Todo, |
diff --git a/common/domain/typeimplementations_p.h b/common/domain/typeimplementations_p.h index 6f77a2d..fc08048 100644 --- a/common/domain/typeimplementations_p.h +++ b/common/domain/typeimplementations_p.h | |||
@@ -62,7 +62,7 @@ public: | |||
62 | }; | 62 | }; |
63 | 63 | ||
64 | 64 | ||
65 | template <typename Property, typename SortProperty> | 65 | template <typename Property, typename SortProperty = void> |
66 | class SortedIndex | 66 | class SortedIndex |
67 | { | 67 | { |
68 | public: | 68 | public: |
@@ -78,6 +78,22 @@ public: | |||
78 | } | 78 | } |
79 | }; | 79 | }; |
80 | 80 | ||
81 | template <typename SortProperty> | ||
82 | class SortedIndex<SortProperty, void> | ||
83 | { | ||
84 | public: | ||
85 | static void configure(TypeIndex &index) | ||
86 | { | ||
87 | index.addSortedProperty<SortProperty>(); | ||
88 | } | ||
89 | |||
90 | template <typename EntityType> | ||
91 | static QMap<QByteArray, int> databases() | ||
92 | { | ||
93 | return {{QByteArray{EntityType::name} +".index." + SortProperty::name + ".sorted", 1}}; | ||
94 | } | ||
95 | }; | ||
96 | |||
81 | template <typename Property, typename SecondaryProperty> | 97 | template <typename Property, typename SecondaryProperty> |
82 | class SecondaryIndex | 98 | class SecondaryIndex |
83 | { | 99 | { |
diff --git a/common/index.cpp b/common/index.cpp index ff87ae2..94b2eea 100644 --- a/common/index.cpp +++ b/common/index.cpp | |||
@@ -59,3 +59,17 @@ QByteArray Index::lookup(const QByteArray &key) | |||
59 | lookup(key, [&](const QByteArray &value) { result = QByteArray(value.constData(), value.size()); }, [](const Index::Error &) { }); | 59 | lookup(key, [&](const QByteArray &value) { result = QByteArray(value.constData(), value.size()); }, [](const Index::Error &) { }); |
60 | return result; | 60 | return result; |
61 | } | 61 | } |
62 | |||
63 | void Index::rangeLookup(const QByteArray &lowerBound, const QByteArray &upperBound, | ||
64 | const std::function<void(const QByteArray &value)> &resultHandler, | ||
65 | const std::function<void(const Error &error)> &errorHandler) | ||
66 | { | ||
67 | mDb.findAllInRange(lowerBound, upperBound, | ||
68 | [&](const QByteArray &key, const QByteArray &value) { | ||
69 | resultHandler(value); | ||
70 | }, | ||
71 | [&](const Sink::Storage::DataStore::Error &error) { | ||
72 | SinkWarningCtx(mLogCtx) << "Error while retrieving value:" << error << mName; | ||
73 | errorHandler(Error(error.store, error.code, error.message)); | ||
74 | }); | ||
75 | } | ||
diff --git a/common/index.h b/common/index.h index f16a426..043cc90 100644 --- a/common/index.h +++ b/common/index.h | |||
@@ -40,6 +40,10 @@ public: | |||
40 | bool matchSubStringKeys = false); | 40 | bool matchSubStringKeys = false); |
41 | QByteArray lookup(const QByteArray &key); | 41 | QByteArray lookup(const QByteArray &key); |
42 | 42 | ||
43 | void rangeLookup(const QByteArray &lowerBound, const QByteArray &upperBound, | ||
44 | const std::function<void(const QByteArray &value)> &resultHandler, | ||
45 | const std::function<void(const Error &error)> &errorHandler); | ||
46 | |||
43 | private: | 47 | private: |
44 | Q_DISABLE_COPY(Index); | 48 | Q_DISABLE_COPY(Index); |
45 | Sink::Storage::DataStore::Transaction mTransaction; | 49 | Sink::Storage::DataStore::Transaction mTransaction; |
diff --git a/common/query.cpp b/common/query.cpp index 5f6d095..404a304 100644 --- a/common/query.cpp +++ b/common/query.cpp | |||
@@ -132,8 +132,8 @@ bool QueryBase::Filter::operator==(const QueryBase::Filter &other) const | |||
132 | 132 | ||
133 | bool QueryBase::operator==(const QueryBase &other) const | 133 | bool QueryBase::operator==(const QueryBase &other) const |
134 | { | 134 | { |
135 | auto ret = mType == other.mType | 135 | auto ret = mType == other.mType |
136 | && mSortProperty == other.mSortProperty | 136 | && mSortProperty == other.mSortProperty |
137 | && mBaseFilterStage == other.mBaseFilterStage; | 137 | && mBaseFilterStage == other.mBaseFilterStage; |
138 | return ret; | 138 | return ret; |
139 | } | 139 | } |
@@ -171,6 +171,14 @@ bool QueryBase::Comparator::matches(const QVariant &v) const | |||
171 | return false; | 171 | return false; |
172 | } | 172 | } |
173 | return value.value<QByteArrayList>().contains(v.toByteArray()); | 173 | return value.value<QByteArrayList>().contains(v.toByteArray()); |
174 | case Within: { | ||
175 | auto range = value.value<QList<QVariant>>(); | ||
176 | if (range.size() < 2) { | ||
177 | return false; | ||
178 | } | ||
179 | |||
180 | return range[0] <= v && v <= range[1]; | ||
181 | } | ||
174 | case Fulltext: | 182 | case Fulltext: |
175 | case Invalid: | 183 | case Invalid: |
176 | default: | 184 | default: |
diff --git a/common/query.h b/common/query.h index f0c1e01..7130116 100644 --- a/common/query.h +++ b/common/query.h | |||
@@ -36,6 +36,7 @@ public: | |||
36 | Equals, | 36 | Equals, |
37 | Contains, | 37 | Contains, |
38 | In, | 38 | In, |
39 | Within, | ||
39 | Fulltext | 40 | Fulltext |
40 | }; | 41 | }; |
41 | 42 | ||
diff --git a/common/typeindex.cpp b/common/typeindex.cpp index 180343a..589b770 100644 --- a/common/typeindex.cpp +++ b/common/typeindex.cpp | |||
@@ -21,9 +21,12 @@ | |||
21 | #include "log.h" | 21 | #include "log.h" |
22 | #include "index.h" | 22 | #include "index.h" |
23 | #include "fulltextindex.h" | 23 | #include "fulltextindex.h" |
24 | |||
24 | #include <QDateTime> | 25 | #include <QDateTime> |
25 | #include <QDataStream> | 26 | #include <QDataStream> |
26 | 27 | ||
28 | #include <cmath> | ||
29 | |||
27 | using namespace Sink; | 30 | using namespace Sink; |
28 | 31 | ||
29 | static QByteArray getByteArray(const QVariant &value) | 32 | static QByteArray getByteArray(const QVariant &value) |
@@ -50,15 +53,34 @@ static QByteArray getByteArray(const QVariant &value) | |||
50 | return "toplevel"; | 53 | return "toplevel"; |
51 | } | 54 | } |
52 | 55 | ||
53 | static QByteArray toSortableByteArray(const QDateTime &date) | 56 | |
57 | static QByteArray toSortableByteArrayImpl(const QDateTime &date) | ||
54 | { | 58 | { |
55 | // Sort invalid last | 59 | // Sort invalid last |
56 | if (!date.isValid()) { | 60 | if (!date.isValid()) { |
57 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); | 61 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); |
58 | } | 62 | } |
59 | return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t()); | 63 | static unsigned int uint_num_digits = std::log10(std::numeric_limits<unsigned int>::max()) + 1; |
64 | return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t()).rightJustified(uint_num_digits, '0'); | ||
60 | } | 65 | } |
61 | 66 | ||
67 | static QByteArray toSortableByteArray(const QVariant &value) | ||
68 | { | ||
69 | if (!value.isValid()) { | ||
70 | // FIXME: we don't know the type, so we don't know what to return | ||
71 | // This mean we're fixing every sorted index keys to use unsigned int | ||
72 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); | ||
73 | } | ||
74 | |||
75 | switch (value.type()) { | ||
76 | case QMetaType::QDateTime: | ||
77 | return toSortableByteArrayImpl(value.toDateTime()); | ||
78 | default: | ||
79 | SinkWarning() << "Not knowing how to convert a" << value.typeName() | ||
80 | << "to a sortable key, falling back to default conversion"; | ||
81 | return getByteArray(value); | ||
82 | } | ||
83 | } | ||
62 | 84 | ||
63 | TypeIndex::TypeIndex(const QByteArray &type, const Sink::Log::Context &ctx) : mLogCtx(ctx), mType(type) | 85 | TypeIndex::TypeIndex(const QByteArray &type, const Sink::Log::Context &ctx) : mLogCtx(ctx), mType(type) |
64 | { | 86 | { |
@@ -72,6 +94,11 @@ QByteArray TypeIndex::indexName(const QByteArray &property, const QByteArray &so | |||
72 | return mType + ".index." + property + ".sort." + sortProperty; | 94 | return mType + ".index." + property + ".sort." + sortProperty; |
73 | } | 95 | } |
74 | 96 | ||
97 | QByteArray TypeIndex::sortedIndexName(const QByteArray &property) const | ||
98 | { | ||
99 | return mType + ".index." + property + ".sorted"; | ||
100 | } | ||
101 | |||
75 | template <> | 102 | template <> |
76 | void TypeIndex::addProperty<QByteArray>(const QByteArray &property) | 103 | void TypeIndex::addProperty<QByteArray>(const QByteArray &property) |
77 | { | 104 | { |
@@ -138,6 +165,22 @@ void TypeIndex::addProperty<ApplicationDomain::Reference>(const QByteArray &prop | |||
138 | } | 165 | } |
139 | 166 | ||
140 | template <> | 167 | template <> |
168 | void TypeIndex::addSortedProperty<QDateTime>(const QByteArray &property) | ||
169 | { | ||
170 | auto indexer = [this, property](bool add, const QByteArray &identifier, const QVariant &value, | ||
171 | Sink::Storage::DataStore::Transaction &transaction) { | ||
172 | const auto sortableDate = toSortableByteArray(value); | ||
173 | if (add) { | ||
174 | Index(sortedIndexName(property), transaction).add(sortableDate, identifier); | ||
175 | } else { | ||
176 | Index(sortedIndexName(property), transaction).remove(sortableDate, identifier); | ||
177 | } | ||
178 | }; | ||
179 | mSortIndexer.insert(property, indexer); | ||
180 | mSortedProperties << property; | ||
181 | } | ||
182 | |||
183 | template <> | ||
141 | void TypeIndex::addPropertyWithSorting<QByteArray, QDateTime>(const QByteArray &property, const QByteArray &sortProperty) | 184 | void TypeIndex::addPropertyWithSorting<QByteArray, QDateTime>(const QByteArray &property, const QByteArray &sortProperty) |
142 | { | 185 | { |
143 | auto indexer = [=](bool add, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction) { | 186 | auto indexer = [=](bool add, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction) { |
@@ -149,8 +192,8 @@ void TypeIndex::addPropertyWithSorting<QByteArray, QDateTime>(const QByteArray & | |||
149 | Index(indexName(property, sortProperty), transaction).remove(propertyValue + toSortableByteArray(date), identifier); | 192 | Index(indexName(property, sortProperty), transaction).remove(propertyValue + toSortableByteArray(date), identifier); |
150 | } | 193 | } |
151 | }; | 194 | }; |
152 | mSortIndexer.insert(property + sortProperty, indexer); | 195 | mGroupedSortIndexer.insert(property + sortProperty, indexer); |
153 | mSortedProperties.insert(property, sortProperty); | 196 | mGroupedSortedProperties.insert(property, sortProperty); |
154 | } | 197 | } |
155 | 198 | ||
156 | template <> | 199 | template <> |
@@ -166,10 +209,15 @@ void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink:: | |||
166 | auto indexer = mIndexer.value(property); | 209 | auto indexer = mIndexer.value(property); |
167 | indexer(add, identifier, value, transaction); | 210 | indexer(add, identifier, value, transaction); |
168 | } | 211 | } |
169 | for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { | 212 | for (const auto &property : mSortedProperties) { |
213 | const auto value = entity.getProperty(property); | ||
214 | auto indexer = mSortIndexer.value(property); | ||
215 | indexer(add, identifier, value, transaction); | ||
216 | } | ||
217 | for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { | ||
170 | const auto value = entity.getProperty(it.key()); | 218 | const auto value = entity.getProperty(it.key()); |
171 | const auto sortValue = entity.getProperty(it.value()); | 219 | const auto sortValue = entity.getProperty(it.value()); |
172 | auto indexer = mSortIndexer.value(it.key() + it.value()); | 220 | auto indexer = mGroupedSortIndexer.value(it.key() + it.value()); |
173 | indexer(add, identifier, value, sortValue, transaction); | 221 | indexer(add, identifier, value, sortValue, transaction); |
174 | } | 222 | } |
175 | for (const auto &indexer : mCustomIndexer) { | 223 | for (const auto &indexer : mCustomIndexer) { |
@@ -207,22 +255,60 @@ void TypeIndex::remove(const QByteArray &identifier, const Sink::ApplicationDoma | |||
207 | updateIndex(false, identifier, entity, transaction, resourceInstanceId); | 255 | updateIndex(false, identifier, entity, transaction, resourceInstanceId); |
208 | } | 256 | } |
209 | 257 | ||
210 | static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filter) | 258 | static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filter, |
259 | std::function<QByteArray(const QVariant &)> valueToKey = getByteArray) | ||
211 | { | 260 | { |
212 | QVector<QByteArray> keys; | 261 | QVector<QByteArray> keys; |
213 | QByteArrayList lookupKeys; | 262 | QByteArrayList lookupKeys; |
214 | if (filter.comparator == Query::Comparator::Equals) { | 263 | if (filter.comparator == Query::Comparator::Equals) { |
215 | lookupKeys << getByteArray(filter.value); | 264 | lookupKeys << valueToKey(filter.value); |
216 | } else if (filter.comparator == Query::Comparator::In) { | 265 | } else if (filter.comparator == Query::Comparator::In) { |
217 | lookupKeys = filter.value.value<QByteArrayList>(); | 266 | for(const QVariant &value : filter.value.value<QVariantList>()) { |
267 | lookupKeys << valueToKey(value); | ||
268 | } | ||
218 | } else { | 269 | } else { |
219 | Q_ASSERT(false); | 270 | Q_ASSERT(false); |
220 | } | 271 | } |
221 | 272 | ||
222 | for (const auto &lookupKey : lookupKeys) { | 273 | for (const auto &lookupKey : lookupKeys) { |
223 | index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, | 274 | index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, |
224 | [lookupKey](const Index::Error &error) { SinkWarning() << "Lookup error in index: " << error.message << lookupKey; }, true); | 275 | [lookupKey](const Index::Error &error) { |
276 | SinkWarning() << "Lookup error in index: " << error.message << lookupKey; | ||
277 | }, | ||
278 | true); | ||
279 | } | ||
280 | return keys; | ||
281 | } | ||
282 | |||
283 | static QVector<QByteArray> sortedIndexLookup(Index &index, QueryBase::Comparator filter) | ||
284 | { | ||
285 | if (filter.comparator == Query::Comparator::In || filter.comparator == Query::Comparator::Contains) { | ||
286 | SinkWarning() << "In and Contains comparison not supported on sorted indexes"; | ||
287 | } | ||
288 | |||
289 | if (filter.comparator != Query::Comparator::Within) { | ||
290 | return indexLookup(index, filter, toSortableByteArray); | ||
291 | } | ||
292 | |||
293 | QVector<QByteArray> keys; | ||
294 | |||
295 | QByteArray lowerBound, upperBound; | ||
296 | auto bounds = filter.value.value<QVariantList>(); | ||
297 | if (bounds[0].canConvert<QDateTime>()) { | ||
298 | // Inverse the bounds because dates are stored newest first | ||
299 | upperBound = toSortableByteArray(bounds[0].toDateTime()); | ||
300 | lowerBound = toSortableByteArray(bounds[1].toDateTime()); | ||
301 | } else { | ||
302 | lowerBound = bounds[0].toByteArray(); | ||
303 | upperBound = bounds[1].toByteArray(); | ||
225 | } | 304 | } |
305 | |||
306 | index.rangeLookup(lowerBound, upperBound, [&](const QByteArray &value) { keys << value; }, | ||
307 | [bounds](const Index::Error &error) { | ||
308 | SinkWarning() << "Lookup error in index:" << error.message | ||
309 | << "with bounds:" << bounds[0] << bounds[1]; | ||
310 | }); | ||
311 | |||
226 | return keys; | 312 | return keys; |
227 | } | 313 | } |
228 | 314 | ||
@@ -239,16 +325,27 @@ QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArr | |||
239 | } | 325 | } |
240 | } | 326 | } |
241 | 327 | ||
242 | for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { | 328 | for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { |
243 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { | 329 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { |
244 | Index index(indexName(it.key(), it.value()), transaction); | 330 | Index index(indexName(it.key(), it.value()), transaction); |
245 | const auto keys = indexLookup(index, query.getFilter(it.key())); | 331 | const auto keys = indexLookup(index, query.getFilter(it.key())); |
246 | appliedFilters << it.key(); | 332 | appliedFilters << it.key(); |
247 | appliedSorting = it.value(); | 333 | appliedSorting = it.value(); |
248 | SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; | 334 | SinkTraceCtx(mLogCtx) << "Grouped sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; |
249 | return keys; | 335 | return keys; |
250 | } | 336 | } |
251 | } | 337 | } |
338 | |||
339 | for (const auto &property : mSortedProperties) { | ||
340 | if (query.hasFilter(property)) { | ||
341 | Index index(sortedIndexName(property), transaction); | ||
342 | const auto keys = sortedIndexLookup(index, query.getFilter(property)); | ||
343 | appliedFilters << property; | ||
344 | SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << property << " found " << keys.size() << " keys."; | ||
345 | return keys; | ||
346 | } | ||
347 | } | ||
348 | |||
252 | for (const auto &property : mProperties) { | 349 | for (const auto &property : mProperties) { |
253 | if (query.hasFilter(property)) { | 350 | if (query.hasFilter(property)) { |
254 | Index index(indexName(property), transaction); | 351 | Index index(indexName(property), transaction); |
diff --git a/common/typeindex.h b/common/typeindex.h index b8b4d52..793dc1e 100644 --- a/common/typeindex.h +++ b/common/typeindex.h | |||
@@ -38,6 +38,8 @@ public: | |||
38 | 38 | ||
39 | template <typename T> | 39 | template <typename T> |
40 | void addProperty(const QByteArray &property); | 40 | void addProperty(const QByteArray &property); |
41 | template <typename T> | ||
42 | void addSortedProperty(const QByteArray &property); | ||
41 | template <typename T, typename S> | 43 | template <typename T, typename S> |
42 | void addPropertyWithSorting(const QByteArray &property, const QByteArray &sortProperty); | 44 | void addPropertyWithSorting(const QByteArray &property, const QByteArray &sortProperty); |
43 | 45 | ||
@@ -54,9 +56,9 @@ public: | |||
54 | } | 56 | } |
55 | 57 | ||
56 | template <typename T> | 58 | template <typename T> |
57 | void addPropertyWithSorting() | 59 | void addSortedProperty() |
58 | { | 60 | { |
59 | addPropertyWithSorting<typename T::Type>(T::name); | 61 | addSortedProperty<typename T::Type>(T::name); |
60 | } | 62 | } |
61 | 63 | ||
62 | template <typename Left, typename Right> | 64 | template <typename Left, typename Right> |
@@ -112,14 +114,17 @@ private: | |||
112 | friend class Sink::Storage::EntityStore; | 114 | friend class Sink::Storage::EntityStore; |
113 | void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); | 115 | void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
114 | QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; | 116 | QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; |
117 | QByteArray sortedIndexName(const QByteArray &property) const; | ||
115 | Sink::Log::Context mLogCtx; | 118 | Sink::Log::Context mLogCtx; |
116 | QByteArray mType; | 119 | QByteArray mType; |
117 | QByteArrayList mProperties; | 120 | QByteArrayList mProperties; |
118 | QMap<QByteArray, QByteArray> mSortedProperties; | 121 | QByteArrayList mSortedProperties; |
122 | QMap<QByteArray, QByteArray> mGroupedSortedProperties; | ||
119 | //<Property, ResultProperty> | 123 | //<Property, ResultProperty> |
120 | QMap<QByteArray, QByteArray> mSecondaryProperties; | 124 | QMap<QByteArray, QByteArray> mSecondaryProperties; |
121 | QList<Sink::Indexer::Ptr> mCustomIndexer; | 125 | QList<Sink::Indexer::Ptr> mCustomIndexer; |
122 | Sink::Storage::DataStore::Transaction *mTransaction; | 126 | Sink::Storage::DataStore::Transaction *mTransaction; |
123 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction)>> mIndexer; | 127 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction)>> mIndexer; |
124 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction)>> mSortIndexer; | 128 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction)>> mSortIndexer; |
129 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction)>> mGroupedSortIndexer; | ||
125 | }; | 130 | }; |
diff --git a/tests/querytest.cpp b/tests/querytest.cpp index fa016a2..36b6e90 100644 --- a/tests/querytest.cpp +++ b/tests/querytest.cpp | |||
@@ -1510,6 +1510,113 @@ private slots: | |||
1510 | } | 1510 | } |
1511 | } | 1511 | } |
1512 | 1512 | ||
1513 | void mailsWithDates() | ||
1514 | { | ||
1515 | { | ||
1516 | Mail mail("sink.dummy.instance1"); | ||
1517 | mail.setExtractedDate(QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate)); | ||
1518 | mail.setExtractedMessageId("message1"); | ||
1519 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1520 | } | ||
1521 | { | ||
1522 | Mail mail("sink.dummy.instance1"); | ||
1523 | mail.setExtractedDate(QDateTime::fromString("2018-05-23T13:50:00Z", Qt::ISODate)); | ||
1524 | mail.setExtractedMessageId("message2"); | ||
1525 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1526 | } | ||
1527 | { | ||
1528 | Mail mail("sink.dummy.instance1"); | ||
1529 | mail.setExtractedDate(QDateTime::fromString("2018-05-27T13:50:00Z", Qt::ISODate)); | ||
1530 | mail.setExtractedMessageId("message3"); | ||
1531 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1532 | } | ||
1533 | { | ||
1534 | Mail mail("sink.dummy.instance1"); | ||
1535 | mail.setExtractedMessageId("message4"); | ||
1536 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1537 | } | ||
1538 | { | ||
1539 | Mail mail("sink.dummy.instance1"); | ||
1540 | mail.setExtractedDate(QDateTime::fromString("2078-05-23T13:49:41Z", Qt::ISODate)); | ||
1541 | mail.setExtractedMessageId("message5"); | ||
1542 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1543 | } | ||
1544 | VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1")); | ||
1545 | } | ||
1546 | |||
1547 | void testMailDate() | ||
1548 | { | ||
1549 | mailsWithDates(); | ||
1550 | |||
1551 | { | ||
1552 | Sink::Query query; | ||
1553 | query.resourceFilter("sink.dummy.instance1"); | ||
1554 | query.filter<Mail::Date>(QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate)); | ||
1555 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1556 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1557 | QCOMPARE(model->rowCount(), 1); | ||
1558 | } | ||
1559 | |||
1560 | { | ||
1561 | Sink::Query query; | ||
1562 | query.resourceFilter("sink.dummy.instance1"); | ||
1563 | query.filter<Mail::Date>(QDateTime::fromString("2018-05-27T13:49:41Z", Qt::ISODate)); | ||
1564 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1565 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1566 | QCOMPARE(model->rowCount(), 0); | ||
1567 | } | ||
1568 | |||
1569 | { | ||
1570 | Sink::Query query; | ||
1571 | query.resourceFilter("sink.dummy.instance1"); | ||
1572 | query.filter<Mail::Date>(QDateTime::fromString("2018-05-27T13:50:00Z", Qt::ISODate)); | ||
1573 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1574 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1575 | QCOMPARE(model->rowCount(), 1); | ||
1576 | } | ||
1577 | |||
1578 | } | ||
1579 | |||
1580 | void testMailRange() | ||
1581 | { | ||
1582 | mailsWithDates(); | ||
1583 | |||
1584 | { | ||
1585 | Sink::Query query; | ||
1586 | query.resourceFilter("sink.dummy.instance1"); | ||
1587 | query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate), QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within)); | ||
1588 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1589 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1590 | QCOMPARE(model->rowCount(), 1); | ||
1591 | } | ||
1592 | |||
1593 | { | ||
1594 | Sink::Query query; | ||
1595 | query.resourceFilter("sink.dummy.instance1"); | ||
1596 | query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-22T13:49:41Z", Qt::ISODate), QDateTime::fromString("2018-05-25T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within)); | ||
1597 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1598 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1599 | QCOMPARE(model->rowCount(), 2); | ||
1600 | } | ||
1601 | |||
1602 | { | ||
1603 | Sink::Query query; | ||
1604 | query.resourceFilter("sink.dummy.instance1"); | ||
1605 | query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-22T13:49:41Z", Qt::ISODate), QDateTime::fromString("2018-05-30T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within)); | ||
1606 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1607 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1608 | QCOMPARE(model->rowCount(), 3); | ||
1609 | } | ||
1610 | |||
1611 | { | ||
1612 | Sink::Query query; | ||
1613 | query.resourceFilter("sink.dummy.instance1"); | ||
1614 | query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-22T13:49:41Z", Qt::ISODate), QDateTime::fromString("2118-05-30T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within)); | ||
1615 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1616 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1617 | QCOMPARE(model->rowCount(), 4); | ||
1618 | } | ||
1619 | } | ||
1513 | }; | 1620 | }; |
1514 | 1621 | ||
1515 | QTEST_MAIN(QueryTest) | 1622 | QTEST_MAIN(QueryTest) |