diff options
author | Rémi Nicole <nicole@kolabsystems.com> | 2018-06-19 11:04:17 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-06-19 11:10:47 +0200 |
commit | 077e3cb30ace5f6ee20ee15e0d32d2bfb197fde0 (patch) | |
tree | 3cfdaf0912ef22dba71755b4332354d579f6e7cf | |
parent | 1ff4456e5dc2b9a9dfa80047f9e5a4a9e1395cdf (diff) | |
download | sink-077e3cb30ace5f6ee20ee15e0d32d2bfb197fde0.tar.gz sink-077e3cb30ace5f6ee20ee15e0d32d2bfb197fde0.zip |
Implement Overlap queries
Summary:
Notes:
- Introduces the concept of queries on multiple properties (which meant changing query's internals a bit)
- Dates are stored as well as the "reference" in the index to allow quick filtering without fetching the whole entity
- Buckets are weeks starting on Monday (guaranteed by the use of the Julian calendar)
- Some size improvements are definitely possible (dates are padded numbers again, not using integer databases, Julian calendar starts at a very old date, etc.)
Test Plan: Tested in querytest
Reviewers: cmollekopf
Reviewed By: cmollekopf
Tags: #sink
Differential Revision: https://phabricator.kde.org/D13477
-rw-r--r-- | common/datastorequery.cpp | 17 | ||||
-rw-r--r-- | common/domain/typeimplementations.cpp | 3 | ||||
-rw-r--r-- | common/domain/typeimplementations_p.h | 20 | ||||
-rw-r--r-- | common/query.cpp | 13 | ||||
-rw-r--r-- | common/query.h | 44 | ||||
-rw-r--r-- | common/resourcefacade.cpp | 8 | ||||
-rw-r--r-- | common/storage/entitystore.cpp | 2 | ||||
-rw-r--r-- | common/storage/entitystore.h | 2 | ||||
-rw-r--r-- | common/store.cpp | 12 | ||||
-rw-r--r-- | common/typeindex.cpp | 122 | ||||
-rw-r--r-- | common/typeindex.h | 14 | ||||
-rw-r--r-- | tests/querytest.cpp | 159 |
12 files changed, 382 insertions, 34 deletions
diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp index 12c0ae1..263d3ea 100644 --- a/common/datastorequery.cpp +++ b/common/datastorequery.cpp | |||
@@ -119,7 +119,7 @@ class Filter : public FilterBase { | |||
119 | public: | 119 | public: |
120 | typedef QSharedPointer<Filter> Ptr; | 120 | typedef QSharedPointer<Filter> Ptr; |
121 | 121 | ||
122 | QHash<QByteArray, Sink::QueryBase::Comparator> propertyFilter; | 122 | QHash<QByteArrayList, Sink::QueryBase::Comparator> propertyFilter; |
123 | 123 | ||
124 | Filter(FilterBase::Ptr source, DataStoreQuery *store) | 124 | Filter(FilterBase::Ptr source, DataStoreQuery *store) |
125 | : FilterBase(source, store) | 125 | : FilterBase(source, store) |
@@ -158,7 +158,16 @@ public: | |||
158 | 158 | ||
159 | bool matchesFilter(const ApplicationDomain::ApplicationDomainType &entity) { | 159 | bool matchesFilter(const ApplicationDomain::ApplicationDomainType &entity) { |
160 | for (const auto &filterProperty : propertyFilter.keys()) { | 160 | for (const auto &filterProperty : propertyFilter.keys()) { |
161 | const auto property = entity.getProperty(filterProperty); | 161 | QVariant property; |
162 | if (filterProperty.size() == 1) { | ||
163 | property = entity.getProperty(filterProperty[0]); | ||
164 | } else { | ||
165 | QVariantList propList; | ||
166 | for (const auto &propName : filterProperty) { | ||
167 | propList.push_back(entity.getProperty(propName)); | ||
168 | } | ||
169 | property = propList; | ||
170 | } | ||
162 | const auto comparator = propertyFilter.value(filterProperty); | 171 | const auto comparator = propertyFilter.value(filterProperty); |
163 | //We can't deal with a fulltext filter | 172 | //We can't deal with a fulltext filter |
164 | if (comparator.comparator == QueryBase::Comparator::Fulltext) { | 173 | if (comparator.comparator == QueryBase::Comparator::Fulltext) { |
@@ -420,7 +429,7 @@ public: | |||
420 | })) | 429 | })) |
421 | {} | 430 | {} |
422 | mBloomed = true; | 431 | mBloomed = true; |
423 | propertyFilter.insert(mBloomProperty, mBloomValue); | 432 | propertyFilter.insert({mBloomProperty}, mBloomValue); |
424 | return foundValue; | 433 | return foundValue; |
425 | } else { | 434 | } else { |
426 | //Filter on bloom value | 435 | //Filter on bloom value |
@@ -598,7 +607,7 @@ void DataStoreQuery::setupQuery(const Sink::QueryBase &query_) | |||
598 | //We have a set of ids as a starting point | 607 | //We have a set of ids as a starting point |
599 | return Source::Ptr::create(query.ids().toVector(), this); | 608 | return Source::Ptr::create(query.ids().toVector(), this); |
600 | } else { | 609 | } else { |
601 | QSet<QByteArray> appliedFilters; | 610 | QSet<QByteArrayList> appliedFilters; |
602 | auto resultSet = mStore.indexLookup(mType, query, appliedFilters, appliedSorting); | 611 | auto resultSet = mStore.indexLookup(mType, query, appliedFilters, appliedSorting); |
603 | if (!appliedFilters.isEmpty()) { | 612 | if (!appliedFilters.isEmpty()) { |
604 | //We have an index lookup as starting point | 613 | //We have an index lookup as starting point |
diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp index a8f4baf..2b2d2ac 100644 --- a/common/domain/typeimplementations.cpp +++ b/common/domain/typeimplementations.cpp | |||
@@ -65,7 +65,8 @@ typedef IndexConfig<Addressbook, | |||
65 | 65 | ||
66 | typedef IndexConfig<Event, | 66 | typedef IndexConfig<Event, |
67 | ValueIndex<Event::Uid>, | 67 | ValueIndex<Event::Uid>, |
68 | SortedIndex<Event::StartTime> | 68 | SortedIndex<Event::StartTime>, |
69 | SampledPeriodIndex<Event::StartTime, Event::EndTime> | ||
69 | > EventIndexConfig; | 70 | > EventIndexConfig; |
70 | 71 | ||
71 | typedef IndexConfig<Todo, | 72 | typedef IndexConfig<Todo, |
diff --git a/common/domain/typeimplementations_p.h b/common/domain/typeimplementations_p.h index fc08048..51af113 100644 --- a/common/domain/typeimplementations_p.h +++ b/common/domain/typeimplementations_p.h | |||
@@ -126,6 +126,26 @@ public: | |||
126 | } | 126 | } |
127 | }; | 127 | }; |
128 | 128 | ||
129 | template <typename RangeBeginProperty, typename RangeEndProperty> | ||
130 | class SampledPeriodIndex | ||
131 | { | ||
132 | static_assert(std::is_same<typename RangeBeginProperty::Type, QDateTime>::value && | ||
133 | std::is_same<typename RangeEndProperty::Type, QDateTime>::value, | ||
134 | "Date range index is not supported for types other than 'QDateTime's"); | ||
135 | |||
136 | public: | ||
137 | static void configure(TypeIndex &index) | ||
138 | { | ||
139 | index.addSampledPeriodIndex<RangeBeginProperty, RangeEndProperty>(); | ||
140 | } | ||
141 | |||
142 | template <typename EntityType> | ||
143 | static QMap<QByteArray, int> databases() | ||
144 | { | ||
145 | return {{QByteArray{EntityType::name} +".index." + RangeBeginProperty::name + ".range." + RangeEndProperty::name, 1}}; | ||
146 | } | ||
147 | }; | ||
148 | |||
129 | template <typename EntityType, typename ... Indexes> | 149 | template <typename EntityType, typename ... Indexes> |
130 | class IndexConfig | 150 | class IndexConfig |
131 | { | 151 | { |
diff --git a/common/query.cpp b/common/query.cpp index 404a304..ceb1897 100644 --- a/common/query.cpp +++ b/common/query.cpp | |||
@@ -179,6 +179,19 @@ bool QueryBase::Comparator::matches(const QVariant &v) const | |||
179 | 179 | ||
180 | return range[0] <= v && v <= range[1]; | 180 | return range[0] <= v && v <= range[1]; |
181 | } | 181 | } |
182 | case Overlap: { | ||
183 | auto bounds = value.value<QList<QVariant>>(); | ||
184 | if (bounds.size() < 2) { | ||
185 | return false; | ||
186 | } | ||
187 | |||
188 | auto range = v.value<QList<QVariant>>(); | ||
189 | if (range.size() < 2) { | ||
190 | return false; | ||
191 | } | ||
192 | |||
193 | return range[0] <= bounds[1] && bounds[0] <= range[1]; | ||
194 | } | ||
182 | case Fulltext: | 195 | case Fulltext: |
183 | case Invalid: | 196 | case Invalid: |
184 | default: | 197 | default: |
diff --git a/common/query.h b/common/query.h index 7130116..cb9c8ca 100644 --- a/common/query.h +++ b/common/query.h | |||
@@ -37,6 +37,7 @@ public: | |||
37 | Contains, | 37 | Contains, |
38 | In, | 38 | In, |
39 | Within, | 39 | Within, |
40 | Overlap, | ||
40 | Fulltext | 41 | Fulltext |
41 | }; | 42 | }; |
42 | 43 | ||
@@ -53,7 +54,7 @@ public: | |||
53 | class SINK_EXPORT Filter { | 54 | class SINK_EXPORT Filter { |
54 | public: | 55 | public: |
55 | QByteArrayList ids; | 56 | QByteArrayList ids; |
56 | QHash<QByteArray, Comparator> propertyFilter; | 57 | QHash<QByteArrayList, Comparator> propertyFilter; |
57 | bool operator==(const Filter &other) const; | 58 | bool operator==(const Filter &other) const; |
58 | }; | 59 | }; |
59 | 60 | ||
@@ -64,7 +65,12 @@ public: | |||
64 | 65 | ||
65 | Comparator getFilter(const QByteArray &property) const | 66 | Comparator getFilter(const QByteArray &property) const |
66 | { | 67 | { |
67 | return mBaseFilterStage.propertyFilter.value(property); | 68 | return mBaseFilterStage.propertyFilter.value({property}); |
69 | } | ||
70 | |||
71 | Comparator getFilter(const QByteArrayList &properties) const | ||
72 | { | ||
73 | return mBaseFilterStage.propertyFilter.value(properties); | ||
68 | } | 74 | } |
69 | 75 | ||
70 | template <class T> | 76 | template <class T> |
@@ -73,9 +79,15 @@ public: | |||
73 | return getFilter(T::name); | 79 | return getFilter(T::name); |
74 | } | 80 | } |
75 | 81 | ||
82 | template <class T1, class T2, class... Rest> | ||
83 | Comparator getFilter() const | ||
84 | { | ||
85 | return getFilter({T1::name, T2::name, Rest::name...}); | ||
86 | } | ||
87 | |||
76 | bool hasFilter(const QByteArray &property) const | 88 | bool hasFilter(const QByteArray &property) const |
77 | { | 89 | { |
78 | return mBaseFilterStage.propertyFilter.contains(property); | 90 | return mBaseFilterStage.propertyFilter.contains({property}); |
79 | } | 91 | } |
80 | 92 | ||
81 | template <class T> | 93 | template <class T> |
@@ -94,7 +106,7 @@ public: | |||
94 | return mId; | 106 | return mId; |
95 | } | 107 | } |
96 | 108 | ||
97 | void setBaseFilters(const QHash<QByteArray, Comparator> &filter) | 109 | void setBaseFilters(const QHash<QByteArrayList, Comparator> &filter) |
98 | { | 110 | { |
99 | mBaseFilterStage.propertyFilter = filter; | 111 | mBaseFilterStage.propertyFilter = filter; |
100 | } | 112 | } |
@@ -104,7 +116,7 @@ public: | |||
104 | mBaseFilterStage = filter; | 116 | mBaseFilterStage = filter; |
105 | } | 117 | } |
106 | 118 | ||
107 | QHash<QByteArray, Comparator> getBaseFilters() const | 119 | QHash<QByteArrayList, Comparator> getBaseFilters() const |
108 | { | 120 | { |
109 | return mBaseFilterStage.propertyFilter; | 121 | return mBaseFilterStage.propertyFilter; |
110 | } | 122 | } |
@@ -131,7 +143,12 @@ public: | |||
131 | 143 | ||
132 | void filter(const QByteArray &property, const QueryBase::Comparator &comparator) | 144 | void filter(const QByteArray &property, const QueryBase::Comparator &comparator) |
133 | { | 145 | { |
134 | mBaseFilterStage.propertyFilter.insert(property, comparator); | 146 | mBaseFilterStage.propertyFilter.insert({property}, comparator); |
147 | } | ||
148 | |||
149 | void filter(const QByteArrayList &properties, const QueryBase::Comparator &comparator) | ||
150 | { | ||
151 | mBaseFilterStage.propertyFilter.insert(properties, comparator); | ||
135 | } | 152 | } |
136 | 153 | ||
137 | void setType(const QByteArray &type) | 154 | void setType(const QByteArray &type) |
@@ -373,6 +390,13 @@ public: | |||
373 | return *this; | 390 | return *this; |
374 | } | 391 | } |
375 | 392 | ||
393 | template <typename T1, typename T2, typename... Rest> | ||
394 | Query &filter(const QueryBase::Comparator &comparator) | ||
395 | { | ||
396 | QueryBase::filter({T1::name, T2::name, Rest::name...}, comparator); | ||
397 | return *this; | ||
398 | } | ||
399 | |||
376 | Query &filter(const QByteArray &id) | 400 | Query &filter(const QByteArray &id) |
377 | { | 401 | { |
378 | QueryBase::filter(id); | 402 | QueryBase::filter(id); |
@@ -465,13 +489,13 @@ public: | |||
465 | template <typename T> | 489 | template <typename T> |
466 | Query &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity) | 490 | Query &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity) |
467 | { | 491 | { |
468 | mResourceFilter.propertyFilter.insert(T::name, Comparator(entity.identifier())); | 492 | mResourceFilter.propertyFilter.insert({T::name}, Comparator(entity.identifier())); |
469 | return *this; | 493 | return *this; |
470 | } | 494 | } |
471 | 495 | ||
472 | Query &resourceFilter(const QByteArray &name, const Comparator &comparator) | 496 | Query &resourceFilter(const QByteArray &name, const Comparator &comparator) |
473 | { | 497 | { |
474 | mResourceFilter.propertyFilter.insert(name, comparator); | 498 | mResourceFilter.propertyFilter.insert({name}, comparator); |
475 | return *this; | 499 | return *this; |
476 | } | 500 | } |
477 | 501 | ||
@@ -531,13 +555,13 @@ public: | |||
531 | template <typename T> | 555 | template <typename T> |
532 | SyncScope &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity) | 556 | SyncScope &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity) |
533 | { | 557 | { |
534 | mResourceFilter.propertyFilter.insert(T::name, Comparator(entity.identifier())); | 558 | mResourceFilter.propertyFilter.insert({T::name}, Comparator(entity.identifier())); |
535 | return *this; | 559 | return *this; |
536 | } | 560 | } |
537 | 561 | ||
538 | SyncScope &resourceFilter(const QByteArray &name, const Comparator &comparator) | 562 | SyncScope &resourceFilter(const QByteArray &name, const Comparator &comparator) |
539 | { | 563 | { |
540 | mResourceFilter.propertyFilter.insert(name, comparator); | 564 | mResourceFilter.propertyFilter.insert({name}, comparator); |
541 | return *this; | 565 | return *this; |
542 | } | 566 | } |
543 | 567 | ||
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp index 7998692..90194d4 100644 --- a/common/resourcefacade.cpp +++ b/common/resourcefacade.cpp | |||
@@ -80,13 +80,13 @@ typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain:: | |||
80 | return object; | 80 | return object; |
81 | } | 81 | } |
82 | 82 | ||
83 | static bool matchesFilter(const QHash<QByteArray, Query::Comparator> &filter, const ApplicationDomain::ApplicationDomainType &entity) | 83 | static bool matchesFilter(const QHash<QByteArrayList, Query::Comparator> &filter, const ApplicationDomain::ApplicationDomainType &entity) |
84 | { | 84 | { |
85 | for (const auto &filterProperty : filter.keys()) { | 85 | for (const auto &filterProperty : filter.keys()) { |
86 | if (filterProperty == ApplicationDomain::SinkResource::ResourceType::name) { | 86 | if (filterProperty[0] == ApplicationDomain::SinkResource::ResourceType::name) { |
87 | continue; | 87 | continue; |
88 | } | 88 | } |
89 | if (!filter.value(filterProperty).matches(entity.getProperty(filterProperty))) { | 89 | if (!filter.value(filterProperty).matches(entity.getProperty(filterProperty[0]))) { |
90 | return false; | 90 | return false; |
91 | } | 91 | } |
92 | } | 92 | } |
@@ -432,7 +432,7 @@ KAsync::Job<void> AccountFacade::remove(const Sink::ApplicationDomain::SinkAccou | |||
432 | //Remove all identities | 432 | //Remove all identities |
433 | job = job.then(Store::fetch<Identity>(Sink::Query{}.filter<Identity::Account>(account))) | 433 | job = job.then(Store::fetch<Identity>(Sink::Query{}.filter<Identity::Account>(account))) |
434 | .each([] (const Identity::Ptr &identity) { return Store::remove(*identity); }); | 434 | .each([] (const Identity::Ptr &identity) { return Store::remove(*identity); }); |
435 | 435 | ||
436 | return job.then(LocalStorageFacade<Sink::ApplicationDomain::SinkAccount>::remove(account)); | 436 | return job.then(LocalStorageFacade<Sink::ApplicationDomain::SinkAccount>::remove(account)); |
437 | } | 437 | } |
438 | 438 | ||
diff --git a/common/storage/entitystore.cpp b/common/storage/entitystore.cpp index 230dbc7..4fe7e3b 100644 --- a/common/storage/entitystore.cpp +++ b/common/storage/entitystore.cpp | |||
@@ -443,7 +443,7 @@ QVector<QByteArray> EntityStore::fullScan(const QByteArray &type) | |||
443 | return keys.toList().toVector(); | 443 | return keys.toList().toVector(); |
444 | } | 444 | } |
445 | 445 | ||
446 | QVector<QByteArray> EntityStore::indexLookup(const QByteArray &type, const QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting) | 446 | QVector<QByteArray> EntityStore::indexLookup(const QByteArray &type, const QueryBase &query, QSet<QByteArrayList> &appliedFilters, QByteArray &appliedSorting) |
447 | { | 447 | { |
448 | if (!d->exists()) { | 448 | if (!d->exists()) { |
449 | SinkTraceCtx(d->logCtx) << "Database is not existing: " << type; | 449 | SinkTraceCtx(d->logCtx) << "Database is not existing: " << type; |
diff --git a/common/storage/entitystore.h b/common/storage/entitystore.h index d79a0b5..ffa70b9 100644 --- a/common/storage/entitystore.h +++ b/common/storage/entitystore.h | |||
@@ -57,7 +57,7 @@ public: | |||
57 | bool hasTransaction() const; | 57 | bool hasTransaction() const; |
58 | 58 | ||
59 | QVector<QByteArray> fullScan(const QByteArray &type); | 59 | QVector<QByteArray> fullScan(const QByteArray &type); |
60 | QVector<QByteArray> indexLookup(const QByteArray &type, const QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting); | 60 | QVector<QByteArray> indexLookup(const QByteArray &type, const QueryBase &query, QSet<QByteArrayList> &appliedFilters, QByteArray &appliedSorting); |
61 | QVector<QByteArray> indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value); | 61 | QVector<QByteArray> indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value); |
62 | void indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value, const std::function<void(const QByteArray &uid)> &callback); | 62 | void indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value, const std::function<void(const QByteArray &uid)> &callback); |
63 | template<typename EntityType, typename PropertyType> | 63 | template<typename EntityType, typename PropertyType> |
diff --git a/common/store.cpp b/common/store.cpp index be2488a..0328c7f 100644 --- a/common/store.cpp +++ b/common/store.cpp | |||
@@ -117,11 +117,13 @@ QPair<typename AggregatingResultEmitter<typename DomainType::Ptr>::Ptr, typenam | |||
117 | 117 | ||
118 | //Filter resources by available content types (unless the query already specifies a capability filter) | 118 | //Filter resources by available content types (unless the query already specifies a capability filter) |
119 | auto resourceFilter = query.getResourceFilter(); | 119 | auto resourceFilter = query.getResourceFilter(); |
120 | if (!resourceFilter.propertyFilter.contains(ApplicationDomain::SinkResource::Capabilities::name)) { | 120 | if (!resourceFilter.propertyFilter.contains({ApplicationDomain::SinkResource::Capabilities::name})) { |
121 | resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{ApplicationDomain::getTypeName<DomainType>(), Query::Comparator::Contains}); | 121 | resourceFilter.propertyFilter.insert({ApplicationDomain::SinkResource::Capabilities::name}, Query::Comparator{ApplicationDomain::getTypeName<DomainType>(), Query::Comparator::Contains}); |
122 | } | 122 | } |
123 | resourceQuery.setFilter(resourceFilter); | 123 | resourceQuery.setFilter(resourceFilter); |
124 | resourceQuery.requestedProperties << resourceFilter.propertyFilter.keys(); | 124 | for (auto const &properties : resourceFilter.propertyFilter.keys()) { |
125 | resourceQuery.requestedProperties << properties; | ||
126 | } | ||
125 | 127 | ||
126 | auto result = facade->load(resourceQuery, resourceCtx); | 128 | auto result = facade->load(resourceQuery, resourceCtx); |
127 | auto emitter = result.second; | 129 | auto emitter = result.second; |
@@ -403,8 +405,8 @@ KAsync::Job<void> Store::synchronize(const Sink::SyncScope &scope) | |||
403 | { | 405 | { |
404 | auto resourceFilter = scope.getResourceFilter(); | 406 | auto resourceFilter = scope.getResourceFilter(); |
405 | //Filter resources by type by default | 407 | //Filter resources by type by default |
406 | if (!resourceFilter.propertyFilter.contains(ApplicationDomain::SinkResource::Capabilities::name) && !scope.type().isEmpty()) { | 408 | if (!resourceFilter.propertyFilter.contains({ApplicationDomain::SinkResource::Capabilities::name}) && !scope.type().isEmpty()) { |
407 | resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{scope.type(), Query::Comparator::Contains}); | 409 | resourceFilter.propertyFilter.insert({ApplicationDomain::SinkResource::Capabilities::name}, Query::Comparator{scope.type(), Query::Comparator::Contains}); |
408 | } | 410 | } |
409 | Sink::Query query; | 411 | Sink::Query query; |
410 | query.setFilter(resourceFilter); | 412 | query.setFilter(resourceFilter); |
diff --git a/common/typeindex.cpp b/common/typeindex.cpp index 41821cb..6aa3796 100644 --- a/common/typeindex.cpp +++ b/common/typeindex.cpp | |||
@@ -53,6 +53,12 @@ static QByteArray getByteArray(const QVariant &value) | |||
53 | return "toplevel"; | 53 | return "toplevel"; |
54 | } | 54 | } |
55 | 55 | ||
56 | template <typename T> | ||
57 | static QByteArray padNumber(T number) | ||
58 | { | ||
59 | static T uint_num_digits = (T)std::log10(std::numeric_limits<T>::max()) + 1; | ||
60 | return QByteArray::number(number).rightJustified(uint_num_digits, '0'); | ||
61 | } | ||
56 | 62 | ||
57 | static QByteArray toSortableByteArrayImpl(const QDateTime &date) | 63 | static QByteArray toSortableByteArrayImpl(const QDateTime &date) |
58 | { | 64 | { |
@@ -60,8 +66,7 @@ static QByteArray toSortableByteArrayImpl(const QDateTime &date) | |||
60 | if (!date.isValid()) { | 66 | if (!date.isValid()) { |
61 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); | 67 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); |
62 | } | 68 | } |
63 | static unsigned int uint_num_digits = (unsigned int)std::log10(std::numeric_limits<unsigned int>::max()) + 1; | 69 | return padNumber(std::numeric_limits<unsigned int>::max() - date.toTime_t()); |
64 | return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t()).rightJustified(uint_num_digits, '0'); | ||
65 | } | 70 | } |
66 | 71 | ||
67 | static QByteArray toSortableByteArray(const QVariant &value) | 72 | static QByteArray toSortableByteArray(const QVariant &value) |
@@ -99,6 +104,22 @@ QByteArray TypeIndex::sortedIndexName(const QByteArray &property) const | |||
99 | return mType + ".index." + property + ".sorted"; | 104 | return mType + ".index." + property + ".sorted"; |
100 | } | 105 | } |
101 | 106 | ||
107 | QByteArray TypeIndex::sampledPeriodIndexName(const QByteArray &rangeBeginProperty, const QByteArray &rangeEndProperty) const | ||
108 | { | ||
109 | return mType + ".index." + rangeBeginProperty + ".range." + rangeEndProperty; | ||
110 | } | ||
111 | |||
112 | static unsigned int bucketOf(const QVariant &value) | ||
113 | { | ||
114 | switch (value.type()) { | ||
115 | case QMetaType::QDateTime: | ||
116 | return value.value<QDateTime>().date().toJulianDay() / 7; | ||
117 | default: | ||
118 | SinkError() << "Not knowing how to get the bucket of a" << value.typeName(); | ||
119 | return {}; | ||
120 | } | ||
121 | } | ||
122 | |||
102 | template <> | 123 | template <> |
103 | void TypeIndex::addProperty<QByteArray>(const QByteArray &property) | 124 | void TypeIndex::addProperty<QByteArray>(const QByteArray &property) |
104 | { | 125 | { |
@@ -202,6 +223,41 @@ void TypeIndex::addPropertyWithSorting<ApplicationDomain::Reference, QDateTime>( | |||
202 | addPropertyWithSorting<QByteArray, QDateTime>(property, sortProperty); | 223 | addPropertyWithSorting<QByteArray, QDateTime>(property, sortProperty); |
203 | } | 224 | } |
204 | 225 | ||
226 | template <> | ||
227 | void TypeIndex::addSampledPeriodIndex<QDateTime, QDateTime>( | ||
228 | const QByteArray &beginProperty, const QByteArray &endProperty) | ||
229 | { | ||
230 | auto indexer = [=](bool add, const QByteArray &identifier, const QVariant &begin, | ||
231 | const QVariant &end, Sink::Storage::DataStore::Transaction &transaction) { | ||
232 | SinkTraceCtx(mLogCtx) << "Adding entity to sampled period index"; | ||
233 | const auto beginDate = begin.toDateTime(); | ||
234 | const auto endDate = end.toDateTime(); | ||
235 | |||
236 | auto beginBucket = bucketOf(beginDate); | ||
237 | auto endBucket = bucketOf(endDate); | ||
238 | |||
239 | if (beginBucket > endBucket) { | ||
240 | SinkError() << "End bucket greater than begin bucket"; | ||
241 | return; | ||
242 | } | ||
243 | |||
244 | Index index(sampledPeriodIndexName(beginProperty, endProperty), transaction); | ||
245 | for (auto bucket = beginBucket; bucket <= endBucket; ++bucket) { | ||
246 | QByteArray bucketKey = padNumber(bucket); | ||
247 | if (add) { | ||
248 | SinkTraceCtx(mLogCtx) << "Adding entity to bucket:" << bucketKey; | ||
249 | index.add(bucketKey, identifier); | ||
250 | } else { | ||
251 | SinkTraceCtx(mLogCtx) << "Removing entity from bucket:" << bucketKey; | ||
252 | index.remove(bucketKey, identifier); | ||
253 | } | ||
254 | } | ||
255 | }; | ||
256 | |||
257 | mSampledPeriodProperties.insert({ beginProperty, endProperty }); | ||
258 | mSampledPeriodIndexer.insert({ beginProperty, endProperty }, indexer); | ||
259 | } | ||
260 | |||
205 | void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) | 261 | void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) |
206 | { | 262 | { |
207 | for (const auto &property : mProperties) { | 263 | for (const auto &property : mProperties) { |
@@ -209,6 +265,12 @@ void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink:: | |||
209 | auto indexer = mIndexer.value(property); | 265 | auto indexer = mIndexer.value(property); |
210 | indexer(add, identifier, value, transaction); | 266 | indexer(add, identifier, value, transaction); |
211 | } | 267 | } |
268 | for (const auto &properties : mSampledPeriodProperties) { | ||
269 | const auto beginValue = entity.getProperty(properties.first); | ||
270 | const auto endValue = entity.getProperty(properties.second); | ||
271 | auto indexer = mSampledPeriodIndexer.value(properties); | ||
272 | indexer(add, identifier, beginValue, endValue, transaction); | ||
273 | } | ||
212 | for (const auto &property : mSortedProperties) { | 274 | for (const auto &property : mSortedProperties) { |
213 | const auto value = entity.getProperty(property); | 275 | const auto value = entity.getProperty(property); |
214 | auto indexer = mSortIndexer.value(property); | 276 | auto indexer = mSortIndexer.value(property); |
@@ -312,7 +374,38 @@ static QVector<QByteArray> sortedIndexLookup(Index &index, QueryBase::Comparator | |||
312 | return keys; | 374 | return keys; |
313 | } | 375 | } |
314 | 376 | ||
315 | QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) | 377 | static QVector<QByteArray> sampledIndexLookup(Index &index, QueryBase::Comparator filter) |
378 | { | ||
379 | if (filter.comparator != Query::Comparator::Overlap) { | ||
380 | SinkWarning() << "Comparisons other than Overlap not supported on sampled period indexes"; | ||
381 | return {}; | ||
382 | } | ||
383 | |||
384 | QVector<QByteArray> keys; | ||
385 | |||
386 | auto bounds = filter.value.value<QVariantList>(); | ||
387 | |||
388 | QByteArray lowerBound = toSortableByteArray(bounds[0]); | ||
389 | QByteArray upperBound = toSortableByteArray(bounds[1]); | ||
390 | |||
391 | QByteArray lowerBucket = padNumber(bucketOf(bounds[0])); | ||
392 | QByteArray upperBucket = padNumber(bucketOf(bounds[1])); | ||
393 | |||
394 | SinkTrace() << "Looking up from bucket:" << lowerBucket << "to:" << upperBucket; | ||
395 | |||
396 | index.rangeLookup(lowerBucket, upperBucket, | ||
397 | [&](const QByteArray &value) { | ||
398 | keys << value.data(); | ||
399 | }, | ||
400 | [bounds](const Index::Error &error) { | ||
401 | SinkWarning() << "Lookup error in index:" << error.message | ||
402 | << "with bounds:" << bounds[0] << bounds[1]; | ||
403 | }); | ||
404 | |||
405 | return keys; | ||
406 | } | ||
407 | |||
408 | QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArrayList> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) | ||
316 | { | 409 | { |
317 | const auto baseFilters = query.getBaseFilters(); | 410 | const auto baseFilters = query.getBaseFilters(); |
318 | for (auto it = baseFilters.constBegin(); it != baseFilters.constEnd(); it++) { | 411 | for (auto it = baseFilters.constBegin(); it != baseFilters.constEnd(); it++) { |
@@ -325,11 +418,28 @@ QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArr | |||
325 | } | 418 | } |
326 | } | 419 | } |
327 | 420 | ||
421 | for (auto it = baseFilters.constBegin(); it != baseFilters.constEnd(); it++) { | ||
422 | if (it.value().comparator == QueryBase::Comparator::Overlap) { | ||
423 | if (mSampledPeriodProperties.contains({it.key()[0], it.key()[1]})) { | ||
424 | Index index(sampledPeriodIndexName(it.key()[0], it.key()[1]), transaction); | ||
425 | const auto keys = sampledIndexLookup(index, query.getFilter(it.key())); | ||
426 | // The filter is not completely applied, we need post-filtering | ||
427 | // in the case the overlap period is not completely aligned | ||
428 | // with a week starting on monday | ||
429 | //appliedFilters << it.key(); | ||
430 | SinkTraceCtx(mLogCtx) << "Sampled period index lookup on" << it.key() << "found" << keys.size() << "keys."; | ||
431 | return keys; | ||
432 | } else { | ||
433 | SinkWarning() << "Overlap search without sampled period index"; | ||
434 | } | ||
435 | } | ||
436 | } | ||
437 | |||
328 | for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { | 438 | for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { |
329 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { | 439 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { |
330 | Index index(indexName(it.key(), it.value()), transaction); | 440 | Index index(indexName(it.key(), it.value()), transaction); |
331 | const auto keys = indexLookup(index, query.getFilter(it.key())); | 441 | const auto keys = indexLookup(index, query.getFilter(it.key())); |
332 | appliedFilters << it.key(); | 442 | appliedFilters.insert({it.key()}); |
333 | appliedSorting = it.value(); | 443 | appliedSorting = it.value(); |
334 | SinkTraceCtx(mLogCtx) << "Grouped sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; | 444 | SinkTraceCtx(mLogCtx) << "Grouped sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; |
335 | return keys; | 445 | return keys; |
@@ -340,7 +450,7 @@ QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArr | |||
340 | if (query.hasFilter(property)) { | 450 | if (query.hasFilter(property)) { |
341 | Index index(sortedIndexName(property), transaction); | 451 | Index index(sortedIndexName(property), transaction); |
342 | const auto keys = sortedIndexLookup(index, query.getFilter(property)); | 452 | const auto keys = sortedIndexLookup(index, query.getFilter(property)); |
343 | appliedFilters << property; | 453 | appliedFilters.insert({property}); |
344 | SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << property << " found " << keys.size() << " keys."; | 454 | SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << property << " found " << keys.size() << " keys."; |
345 | return keys; | 455 | return keys; |
346 | } | 456 | } |
@@ -350,7 +460,7 @@ QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArr | |||
350 | if (query.hasFilter(property)) { | 460 | if (query.hasFilter(property)) { |
351 | Index index(indexName(property), transaction); | 461 | Index index(indexName(property), transaction); |
352 | const auto keys = indexLookup(index, query.getFilter(property)); | 462 | const auto keys = indexLookup(index, query.getFilter(property)); |
353 | appliedFilters << property; | 463 | appliedFilters.insert({property}); |
354 | SinkTraceCtx(mLogCtx) << "Index lookup on " << property << " found " << keys.size() << " keys."; | 464 | SinkTraceCtx(mLogCtx) << "Index lookup on " << property << " found " << keys.size() << " keys."; |
355 | return keys; | 465 | return keys; |
356 | } | 466 | } |
diff --git a/common/typeindex.h b/common/typeindex.h index 793dc1e..a8c0e10 100644 --- a/common/typeindex.h +++ b/common/typeindex.h | |||
@@ -73,10 +73,19 @@ public: | |||
73 | mCustomIndexer << CustomIndexer::Ptr::create(); | 73 | mCustomIndexer << CustomIndexer::Ptr::create(); |
74 | } | 74 | } |
75 | 75 | ||
76 | template <typename Begin, typename End> | ||
77 | void addSampledPeriodIndex(const QByteArray &beginProperty, const QByteArray &endProperty); | ||
78 | |||
79 | template <typename Begin, typename End> | ||
80 | void addSampledPeriodIndex() | ||
81 | { | ||
82 | addSampledPeriodIndex<typename Begin::Type, typename End::Type>(Begin::name, End::name); | ||
83 | } | ||
84 | |||
76 | void add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); | 85 | void add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
77 | void remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); | 86 | void remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
78 | 87 | ||
79 | QVector<QByteArray> query(const Sink::QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); | 88 | QVector<QByteArray> query(const Sink::QueryBase &query, QSet<QByteArrayList> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
80 | QVector<QByteArray> lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction); | 89 | QVector<QByteArray> lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction); |
81 | 90 | ||
82 | template <typename Left, typename Right> | 91 | template <typename Left, typename Right> |
@@ -115,6 +124,7 @@ private: | |||
115 | void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); | 124 | void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
116 | QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; | 125 | QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; |
117 | QByteArray sortedIndexName(const QByteArray &property) const; | 126 | QByteArray sortedIndexName(const QByteArray &property) const; |
127 | QByteArray sampledPeriodIndexName(const QByteArray &rangeBeginProperty, const QByteArray &rangeEndProperty) const; | ||
118 | Sink::Log::Context mLogCtx; | 128 | Sink::Log::Context mLogCtx; |
119 | QByteArray mType; | 129 | QByteArray mType; |
120 | QByteArrayList mProperties; | 130 | QByteArrayList mProperties; |
@@ -122,9 +132,11 @@ private: | |||
122 | QMap<QByteArray, QByteArray> mGroupedSortedProperties; | 132 | QMap<QByteArray, QByteArray> mGroupedSortedProperties; |
123 | //<Property, ResultProperty> | 133 | //<Property, ResultProperty> |
124 | QMap<QByteArray, QByteArray> mSecondaryProperties; | 134 | QMap<QByteArray, QByteArray> mSecondaryProperties; |
135 | QSet<QPair<QByteArray, QByteArray>> mSampledPeriodProperties; | ||
125 | QList<Sink::Indexer::Ptr> mCustomIndexer; | 136 | QList<Sink::Indexer::Ptr> mCustomIndexer; |
126 | Sink::Storage::DataStore::Transaction *mTransaction; | 137 | Sink::Storage::DataStore::Transaction *mTransaction; |
127 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction)>> mIndexer; | 138 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction)>> mIndexer; |
128 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction)>> mSortIndexer; | 139 | 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; | 140 | QHash<QByteArray, std::function<void(bool, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction)>> mGroupedSortIndexer; |
141 | QHash<QPair<QByteArray, QByteArray>, std::function<void(bool, const QByteArray &identifier, const QVariant &begin, const QVariant &end, Sink::Storage::DataStore::Transaction &transaction)>> mSampledPeriodIndexer; | ||
130 | }; | 142 | }; |
diff --git a/tests/querytest.cpp b/tests/querytest.cpp index 36b6e90..b52ba96 100644 --- a/tests/querytest.cpp +++ b/tests/querytest.cpp | |||
@@ -59,7 +59,7 @@ private slots: | |||
59 | 59 | ||
60 | Sink::QueryBase::Filter filter; | 60 | Sink::QueryBase::Filter filter; |
61 | filter.ids << "id"; | 61 | filter.ids << "id"; |
62 | filter.propertyFilter.insert("foo", QVariant::fromValue(QByteArray("bar"))); | 62 | filter.propertyFilter.insert({"foo"}, QVariant::fromValue(QByteArray("bar"))); |
63 | 63 | ||
64 | Sink::Query query; | 64 | Sink::Query query; |
65 | query.setFilter(filter); | 65 | query.setFilter(filter); |
@@ -1617,6 +1617,163 @@ private slots: | |||
1617 | QCOMPARE(model->rowCount(), 4); | 1617 | QCOMPARE(model->rowCount(), 4); |
1618 | } | 1618 | } |
1619 | } | 1619 | } |
1620 | |||
1621 | void eventsWithDates() | ||
1622 | { | ||
1623 | { | ||
1624 | Event event("sink.dummy.instance1"); | ||
1625 | event.setExtractedStartTime(QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate)); | ||
1626 | event.setExtractedEndTime(QDateTime::fromString("2018-05-23T13:00:00Z", Qt::ISODate)); | ||
1627 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1628 | } | ||
1629 | { | ||
1630 | Event event("sink.dummy.instance1"); | ||
1631 | event.setExtractedStartTime(QDateTime::fromString("2018-05-23T13:00:00Z", Qt::ISODate)); | ||
1632 | event.setExtractedEndTime(QDateTime::fromString("2018-05-23T14:00:00Z", Qt::ISODate)); | ||
1633 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1634 | } | ||
1635 | { | ||
1636 | Event event("sink.dummy.instance1"); | ||
1637 | event.setExtractedStartTime(QDateTime::fromString("2018-05-23T14:00:00Z", Qt::ISODate)); | ||
1638 | event.setExtractedEndTime(QDateTime::fromString("2018-05-23T15:00:00Z", Qt::ISODate)); | ||
1639 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1640 | } | ||
1641 | { | ||
1642 | Event event("sink.dummy.instance1"); | ||
1643 | event.setExtractedStartTime(QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate)); | ||
1644 | event.setExtractedEndTime(QDateTime::fromString("2018-05-23T14:00:00Z", Qt::ISODate)); | ||
1645 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1646 | } | ||
1647 | { | ||
1648 | Event event("sink.dummy.instance1"); | ||
1649 | event.setExtractedStartTime(QDateTime::fromString("2018-05-24T12:00:00Z", Qt::ISODate)); | ||
1650 | event.setExtractedEndTime(QDateTime::fromString("2018-05-24T14:00:00Z", Qt::ISODate)); | ||
1651 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1652 | } | ||
1653 | { | ||
1654 | Event event("sink.dummy.instance1"); | ||
1655 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1656 | } | ||
1657 | |||
1658 | VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1")); | ||
1659 | } | ||
1660 | |||
1661 | void testOverlap() | ||
1662 | { | ||
1663 | eventsWithDates(); | ||
1664 | |||
1665 | { | ||
1666 | Sink::Query query; | ||
1667 | query.resourceFilter("sink.dummy.instance1"); | ||
1668 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1669 | QVariantList{ QDateTime::fromString("2018-05-22T12:00:00Z", Qt::ISODate), | ||
1670 | QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) }, | ||
1671 | QueryBase::Comparator::Overlap)); | ||
1672 | auto model = Sink::Store::loadModel<Event>(query); | ||
1673 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1674 | QCOMPARE(model->rowCount(), 5); | ||
1675 | } | ||
1676 | |||
1677 | { | ||
1678 | Sink::Query query; | ||
1679 | query.resourceFilter("sink.dummy.instance1"); | ||
1680 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1681 | QVariantList{ QDateTime::fromString("2018-05-22T12:30:00Z", Qt::ISODate), | ||
1682 | QDateTime::fromString("2018-05-22T12:31:00Z", Qt::ISODate) }, | ||
1683 | QueryBase::Comparator::Overlap)); | ||
1684 | auto model = Sink::Store::loadModel<Event>(query); | ||
1685 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1686 | QCOMPARE(model->rowCount(), 0); | ||
1687 | } | ||
1688 | |||
1689 | { | ||
1690 | Sink::Query query; | ||
1691 | query.resourceFilter("sink.dummy.instance1"); | ||
1692 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1693 | QVariantList{ QDateTime::fromString("2018-05-24T10:00:00Z", Qt::ISODate), | ||
1694 | QDateTime::fromString("2018-05-24T11:00:00Z", Qt::ISODate) }, | ||
1695 | QueryBase::Comparator::Overlap)); | ||
1696 | auto model = Sink::Store::loadModel<Event>(query); | ||
1697 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1698 | QCOMPARE(model->rowCount(), 0); | ||
1699 | } | ||
1700 | |||
1701 | { | ||
1702 | Sink::Query query; | ||
1703 | query.resourceFilter("sink.dummy.instance1"); | ||
1704 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1705 | QVariantList{ QDateTime::fromString("2018-05-23T12:30:00Z", Qt::ISODate), | ||
1706 | QDateTime::fromString("2018-05-23T12:31:00Z", Qt::ISODate) }, | ||
1707 | QueryBase::Comparator::Overlap)); | ||
1708 | auto model = Sink::Store::loadModel<Event>(query); | ||
1709 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1710 | QCOMPARE(model->rowCount(), 2); | ||
1711 | } | ||
1712 | |||
1713 | { | ||
1714 | Sink::Query query; | ||
1715 | query.resourceFilter("sink.dummy.instance1"); | ||
1716 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1717 | QVariantList{ QDateTime::fromString("2018-05-22T12:30:00Z", Qt::ISODate), | ||
1718 | QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate) }, | ||
1719 | QueryBase::Comparator::Overlap)); | ||
1720 | auto model = Sink::Store::loadModel<Event>(query); | ||
1721 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1722 | QCOMPARE(model->rowCount(), 2); | ||
1723 | } | ||
1724 | |||
1725 | { | ||
1726 | Sink::Query query; | ||
1727 | query.resourceFilter("sink.dummy.instance1"); | ||
1728 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1729 | QVariantList{ QDateTime::fromString("2018-05-23T14:30:00Z", Qt::ISODate), | ||
1730 | QDateTime::fromString("2018-05-23T16:00:00Z", Qt::ISODate) }, | ||
1731 | QueryBase::Comparator::Overlap)); | ||
1732 | auto model = Sink::Store::loadModel<Event>(query); | ||
1733 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1734 | QCOMPARE(model->rowCount(), 1); | ||
1735 | } | ||
1736 | |||
1737 | } | ||
1738 | |||
1739 | void testOverlapLive() | ||
1740 | { | ||
1741 | eventsWithDates(); | ||
1742 | |||
1743 | { | ||
1744 | Sink::Query query; | ||
1745 | query.resourceFilter("sink.dummy.instance1"); | ||
1746 | query.setFlags(Query::LiveQuery); | ||
1747 | query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator( | ||
1748 | QVariantList{ QDateTime::fromString("2018-05-22T12:00:00Z", Qt::ISODate), | ||
1749 | QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) }, | ||
1750 | QueryBase::Comparator::Overlap)); | ||
1751 | auto model = Sink::Store::loadModel<Event>(query); | ||
1752 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1753 | QCOMPARE(model->rowCount(), 5); | ||
1754 | |||
1755 | Event event = Event::createEntity<Event>("sink.dummy.instance1"); | ||
1756 | event.setExtractedStartTime(QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate)); | ||
1757 | event.setExtractedEndTime(QDateTime::fromString("2018-05-23T13:00:00Z", Qt::ISODate)); | ||
1758 | VERIFYEXEC(Sink::Store::create<Event>(event)); | ||
1759 | |||
1760 | Event event2 = Event::createEntity<Event>("sink.dummy.instance1"); | ||
1761 | event2.setExtractedStartTime(QDateTime::fromString("2018-05-33T12:00:00Z", Qt::ISODate)); | ||
1762 | event2.setExtractedEndTime(QDateTime::fromString("2018-05-33T13:00:00Z", Qt::ISODate)); | ||
1763 | VERIFYEXEC(Sink::Store::create<Event>(event2)); | ||
1764 | |||
1765 | QTest::qWait(500); | ||
1766 | QCOMPARE(model->rowCount(), 6); | ||
1767 | |||
1768 | VERIFYEXEC(Sink::Store::remove<Event>(event)); | ||
1769 | VERIFYEXEC(Sink::Store::remove<Event>(event2)); | ||
1770 | |||
1771 | QTest::qWait(500); | ||
1772 | QCOMPARE(model->rowCount(), 5); | ||
1773 | } | ||
1774 | |||
1775 | } | ||
1776 | |||
1620 | }; | 1777 | }; |
1621 | 1778 | ||
1622 | QTEST_MAIN(QueryTest) | 1779 | QTEST_MAIN(QueryTest) |