From 077e3cb30ace5f6ee20ee15e0d32d2bfb197fde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Nicole?= Date: Tue, 19 Jun 2018 11:04:17 +0200 Subject: 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 --- common/datastorequery.cpp | 17 +++- common/domain/typeimplementations.cpp | 3 +- common/domain/typeimplementations_p.h | 20 +++++ common/query.cpp | 13 +++ common/query.h | 44 +++++++--- common/resourcefacade.cpp | 8 +- common/storage/entitystore.cpp | 2 +- common/storage/entitystore.h | 2 +- common/store.cpp | 12 +-- common/typeindex.cpp | 122 ++++++++++++++++++++++++-- common/typeindex.h | 14 ++- 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 { public: typedef QSharedPointer Ptr; - QHash propertyFilter; + QHash propertyFilter; Filter(FilterBase::Ptr source, DataStoreQuery *store) : FilterBase(source, store) @@ -158,7 +158,16 @@ public: bool matchesFilter(const ApplicationDomain::ApplicationDomainType &entity) { for (const auto &filterProperty : propertyFilter.keys()) { - const auto property = entity.getProperty(filterProperty); + QVariant property; + if (filterProperty.size() == 1) { + property = entity.getProperty(filterProperty[0]); + } else { + QVariantList propList; + for (const auto &propName : filterProperty) { + propList.push_back(entity.getProperty(propName)); + } + property = propList; + } const auto comparator = propertyFilter.value(filterProperty); //We can't deal with a fulltext filter if (comparator.comparator == QueryBase::Comparator::Fulltext) { @@ -420,7 +429,7 @@ public: })) {} mBloomed = true; - propertyFilter.insert(mBloomProperty, mBloomValue); + propertyFilter.insert({mBloomProperty}, mBloomValue); return foundValue; } else { //Filter on bloom value @@ -598,7 +607,7 @@ void DataStoreQuery::setupQuery(const Sink::QueryBase &query_) //We have a set of ids as a starting point return Source::Ptr::create(query.ids().toVector(), this); } else { - QSet appliedFilters; + QSet appliedFilters; auto resultSet = mStore.indexLookup(mType, query, appliedFilters, appliedSorting); if (!appliedFilters.isEmpty()) { //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, - SortedIndex + SortedIndex, + SampledPeriodIndex > EventIndexConfig; typedef IndexConfig +class SampledPeriodIndex +{ + static_assert(std::is_same::value && + std::is_same::value, + "Date range index is not supported for types other than 'QDateTime's"); + +public: + static void configure(TypeIndex &index) + { + index.addSampledPeriodIndex(); + } + + template + static QMap databases() + { + return {{QByteArray{EntityType::name} +".index." + RangeBeginProperty::name + ".range." + RangeEndProperty::name, 1}}; + } +}; + template class IndexConfig { 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 return range[0] <= v && v <= range[1]; } + case Overlap: { + auto bounds = value.value>(); + if (bounds.size() < 2) { + return false; + } + + auto range = v.value>(); + if (range.size() < 2) { + return false; + } + + return range[0] <= bounds[1] && bounds[0] <= range[1]; + } case Fulltext: case Invalid: 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: Contains, In, Within, + Overlap, Fulltext }; @@ -53,7 +54,7 @@ public: class SINK_EXPORT Filter { public: QByteArrayList ids; - QHash propertyFilter; + QHash propertyFilter; bool operator==(const Filter &other) const; }; @@ -64,7 +65,12 @@ public: Comparator getFilter(const QByteArray &property) const { - return mBaseFilterStage.propertyFilter.value(property); + return mBaseFilterStage.propertyFilter.value({property}); + } + + Comparator getFilter(const QByteArrayList &properties) const + { + return mBaseFilterStage.propertyFilter.value(properties); } template @@ -73,9 +79,15 @@ public: return getFilter(T::name); } + template + Comparator getFilter() const + { + return getFilter({T1::name, T2::name, Rest::name...}); + } + bool hasFilter(const QByteArray &property) const { - return mBaseFilterStage.propertyFilter.contains(property); + return mBaseFilterStage.propertyFilter.contains({property}); } template @@ -94,7 +106,7 @@ public: return mId; } - void setBaseFilters(const QHash &filter) + void setBaseFilters(const QHash &filter) { mBaseFilterStage.propertyFilter = filter; } @@ -104,7 +116,7 @@ public: mBaseFilterStage = filter; } - QHash getBaseFilters() const + QHash getBaseFilters() const { return mBaseFilterStage.propertyFilter; } @@ -131,7 +143,12 @@ public: void filter(const QByteArray &property, const QueryBase::Comparator &comparator) { - mBaseFilterStage.propertyFilter.insert(property, comparator); + mBaseFilterStage.propertyFilter.insert({property}, comparator); + } + + void filter(const QByteArrayList &properties, const QueryBase::Comparator &comparator) + { + mBaseFilterStage.propertyFilter.insert(properties, comparator); } void setType(const QByteArray &type) @@ -373,6 +390,13 @@ public: return *this; } + template + Query &filter(const QueryBase::Comparator &comparator) + { + QueryBase::filter({T1::name, T2::name, Rest::name...}, comparator); + return *this; + } + Query &filter(const QByteArray &id) { QueryBase::filter(id); @@ -465,13 +489,13 @@ public: template Query &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity) { - mResourceFilter.propertyFilter.insert(T::name, Comparator(entity.identifier())); + mResourceFilter.propertyFilter.insert({T::name}, Comparator(entity.identifier())); return *this; } Query &resourceFilter(const QByteArray &name, const Comparator &comparator) { - mResourceFilter.propertyFilter.insert(name, comparator); + mResourceFilter.propertyFilter.insert({name}, comparator); return *this; } @@ -531,13 +555,13 @@ public: template SyncScope &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity) { - mResourceFilter.propertyFilter.insert(T::name, Comparator(entity.identifier())); + mResourceFilter.propertyFilter.insert({T::name}, Comparator(entity.identifier())); return *this; } SyncScope &resourceFilter(const QByteArray &name, const Comparator &comparator) { - mResourceFilter.propertyFilter.insert(name, comparator); + mResourceFilter.propertyFilter.insert({name}, comparator); return *this; } 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 &filter, const ApplicationDomain::ApplicationDomainType &entity) +static bool matchesFilter(const QHash &filter, const ApplicationDomain::ApplicationDomainType &entity) { for (const auto &filterProperty : filter.keys()) { - if (filterProperty == ApplicationDomain::SinkResource::ResourceType::name) { + if (filterProperty[0] == ApplicationDomain::SinkResource::ResourceType::name) { continue; } - if (!filter.value(filterProperty).matches(entity.getProperty(filterProperty))) { + if (!filter.value(filterProperty).matches(entity.getProperty(filterProperty[0]))) { return false; } } @@ -432,7 +432,7 @@ KAsync::Job AccountFacade::remove(const Sink::ApplicationDomain::SinkAccou //Remove all identities job = job.then(Store::fetch(Sink::Query{}.filter(account))) .each([] (const Identity::Ptr &identity) { return Store::remove(*identity); }); - + return job.then(LocalStorageFacade::remove(account)); } 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 EntityStore::fullScan(const QByteArray &type) return keys.toList().toVector(); } -QVector EntityStore::indexLookup(const QByteArray &type, const QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting) +QVector EntityStore::indexLookup(const QByteArray &type, const QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting) { if (!d->exists()) { 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: bool hasTransaction() const; QVector fullScan(const QByteArray &type); - QVector indexLookup(const QByteArray &type, const QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting); + QVector indexLookup(const QByteArray &type, const QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting); QVector indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value); void indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value, const std::function &callback); template 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::Ptr, typenam //Filter resources by available content types (unless the query already specifies a capability filter) auto resourceFilter = query.getResourceFilter(); - if (!resourceFilter.propertyFilter.contains(ApplicationDomain::SinkResource::Capabilities::name)) { - resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{ApplicationDomain::getTypeName(), Query::Comparator::Contains}); + if (!resourceFilter.propertyFilter.contains({ApplicationDomain::SinkResource::Capabilities::name})) { + resourceFilter.propertyFilter.insert({ApplicationDomain::SinkResource::Capabilities::name}, Query::Comparator{ApplicationDomain::getTypeName(), Query::Comparator::Contains}); } resourceQuery.setFilter(resourceFilter); - resourceQuery.requestedProperties << resourceFilter.propertyFilter.keys(); + for (auto const &properties : resourceFilter.propertyFilter.keys()) { + resourceQuery.requestedProperties << properties; + } auto result = facade->load(resourceQuery, resourceCtx); auto emitter = result.second; @@ -403,8 +405,8 @@ KAsync::Job Store::synchronize(const Sink::SyncScope &scope) { auto resourceFilter = scope.getResourceFilter(); //Filter resources by type by default - if (!resourceFilter.propertyFilter.contains(ApplicationDomain::SinkResource::Capabilities::name) && !scope.type().isEmpty()) { - resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{scope.type(), Query::Comparator::Contains}); + if (!resourceFilter.propertyFilter.contains({ApplicationDomain::SinkResource::Capabilities::name}) && !scope.type().isEmpty()) { + resourceFilter.propertyFilter.insert({ApplicationDomain::SinkResource::Capabilities::name}, Query::Comparator{scope.type(), Query::Comparator::Contains}); } Sink::Query query; 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) return "toplevel"; } +template +static QByteArray padNumber(T number) +{ + static T uint_num_digits = (T)std::log10(std::numeric_limits::max()) + 1; + return QByteArray::number(number).rightJustified(uint_num_digits, '0'); +} static QByteArray toSortableByteArrayImpl(const QDateTime &date) { @@ -60,8 +66,7 @@ static QByteArray toSortableByteArrayImpl(const QDateTime &date) if (!date.isValid()) { return QByteArray::number(std::numeric_limits::max()); } - static unsigned int uint_num_digits = (unsigned int)std::log10(std::numeric_limits::max()) + 1; - return QByteArray::number(std::numeric_limits::max() - date.toTime_t()).rightJustified(uint_num_digits, '0'); + return padNumber(std::numeric_limits::max() - date.toTime_t()); } static QByteArray toSortableByteArray(const QVariant &value) @@ -99,6 +104,22 @@ QByteArray TypeIndex::sortedIndexName(const QByteArray &property) const return mType + ".index." + property + ".sorted"; } +QByteArray TypeIndex::sampledPeriodIndexName(const QByteArray &rangeBeginProperty, const QByteArray &rangeEndProperty) const +{ + return mType + ".index." + rangeBeginProperty + ".range." + rangeEndProperty; +} + +static unsigned int bucketOf(const QVariant &value) +{ + switch (value.type()) { + case QMetaType::QDateTime: + return value.value().date().toJulianDay() / 7; + default: + SinkError() << "Not knowing how to get the bucket of a" << value.typeName(); + return {}; + } +} + template <> void TypeIndex::addProperty(const QByteArray &property) { @@ -202,6 +223,41 @@ void TypeIndex::addPropertyWithSorting( addPropertyWithSorting(property, sortProperty); } +template <> +void TypeIndex::addSampledPeriodIndex( + const QByteArray &beginProperty, const QByteArray &endProperty) +{ + auto indexer = [=](bool add, const QByteArray &identifier, const QVariant &begin, + const QVariant &end, Sink::Storage::DataStore::Transaction &transaction) { + SinkTraceCtx(mLogCtx) << "Adding entity to sampled period index"; + const auto beginDate = begin.toDateTime(); + const auto endDate = end.toDateTime(); + + auto beginBucket = bucketOf(beginDate); + auto endBucket = bucketOf(endDate); + + if (beginBucket > endBucket) { + SinkError() << "End bucket greater than begin bucket"; + return; + } + + Index index(sampledPeriodIndexName(beginProperty, endProperty), transaction); + for (auto bucket = beginBucket; bucket <= endBucket; ++bucket) { + QByteArray bucketKey = padNumber(bucket); + if (add) { + SinkTraceCtx(mLogCtx) << "Adding entity to bucket:" << bucketKey; + index.add(bucketKey, identifier); + } else { + SinkTraceCtx(mLogCtx) << "Removing entity from bucket:" << bucketKey; + index.remove(bucketKey, identifier); + } + } + }; + + mSampledPeriodProperties.insert({ beginProperty, endProperty }); + mSampledPeriodIndexer.insert({ beginProperty, endProperty }, indexer); +} + void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) { for (const auto &property : mProperties) { @@ -209,6 +265,12 @@ void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink:: auto indexer = mIndexer.value(property); indexer(add, identifier, value, transaction); } + for (const auto &properties : mSampledPeriodProperties) { + const auto beginValue = entity.getProperty(properties.first); + const auto endValue = entity.getProperty(properties.second); + auto indexer = mSampledPeriodIndexer.value(properties); + indexer(add, identifier, beginValue, endValue, transaction); + } for (const auto &property : mSortedProperties) { const auto value = entity.getProperty(property); auto indexer = mSortIndexer.value(property); @@ -312,7 +374,38 @@ static QVector sortedIndexLookup(Index &index, QueryBase::Comparator return keys; } -QVector TypeIndex::query(const Sink::QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) +static QVector sampledIndexLookup(Index &index, QueryBase::Comparator filter) +{ + if (filter.comparator != Query::Comparator::Overlap) { + SinkWarning() << "Comparisons other than Overlap not supported on sampled period indexes"; + return {}; + } + + QVector keys; + + auto bounds = filter.value.value(); + + QByteArray lowerBound = toSortableByteArray(bounds[0]); + QByteArray upperBound = toSortableByteArray(bounds[1]); + + QByteArray lowerBucket = padNumber(bucketOf(bounds[0])); + QByteArray upperBucket = padNumber(bucketOf(bounds[1])); + + SinkTrace() << "Looking up from bucket:" << lowerBucket << "to:" << upperBucket; + + index.rangeLookup(lowerBucket, upperBucket, + [&](const QByteArray &value) { + keys << value.data(); + }, + [bounds](const Index::Error &error) { + SinkWarning() << "Lookup error in index:" << error.message + << "with bounds:" << bounds[0] << bounds[1]; + }); + + return keys; +} + +QVector TypeIndex::query(const Sink::QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) { const auto baseFilters = query.getBaseFilters(); for (auto it = baseFilters.constBegin(); it != baseFilters.constEnd(); it++) { @@ -325,11 +418,28 @@ QVector TypeIndex::query(const Sink::QueryBase &query, QSet TypeIndex::query(const Sink::QueryBase &query, QSet TypeIndex::query(const Sink::QueryBase &query, QSet + void addSampledPeriodIndex(const QByteArray &beginProperty, const QByteArray &endProperty); + + template + void addSampledPeriodIndex() + { + addSampledPeriodIndex(Begin::name, End::name); + } + void add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); void remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); - QVector query(const Sink::QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); + QVector query(const Sink::QueryBase &query, QSet &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); QVector lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction); template @@ -115,6 +124,7 @@ private: void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; QByteArray sortedIndexName(const QByteArray &property) const; + QByteArray sampledPeriodIndexName(const QByteArray &rangeBeginProperty, const QByteArray &rangeEndProperty) const; Sink::Log::Context mLogCtx; QByteArray mType; QByteArrayList mProperties; @@ -122,9 +132,11 @@ private: QMap mGroupedSortedProperties; // QMap mSecondaryProperties; + QSet> mSampledPeriodProperties; QList mCustomIndexer; Sink::Storage::DataStore::Transaction *mTransaction; QHash> mIndexer; QHash> mSortIndexer; QHash> mGroupedSortIndexer; + QHash, std::function> mSampledPeriodIndexer; }; 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: Sink::QueryBase::Filter filter; filter.ids << "id"; - filter.propertyFilter.insert("foo", QVariant::fromValue(QByteArray("bar"))); + filter.propertyFilter.insert({"foo"}, QVariant::fromValue(QByteArray("bar"))); Sink::Query query; query.setFilter(filter); @@ -1617,6 +1617,163 @@ private slots: QCOMPARE(model->rowCount(), 4); } } + + void eventsWithDates() + { + { + Event event("sink.dummy.instance1"); + event.setExtractedStartTime(QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate)); + event.setExtractedEndTime(QDateTime::fromString("2018-05-23T13:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event)); + } + { + Event event("sink.dummy.instance1"); + event.setExtractedStartTime(QDateTime::fromString("2018-05-23T13:00:00Z", Qt::ISODate)); + event.setExtractedEndTime(QDateTime::fromString("2018-05-23T14:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event)); + } + { + Event event("sink.dummy.instance1"); + event.setExtractedStartTime(QDateTime::fromString("2018-05-23T14:00:00Z", Qt::ISODate)); + event.setExtractedEndTime(QDateTime::fromString("2018-05-23T15:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event)); + } + { + Event event("sink.dummy.instance1"); + event.setExtractedStartTime(QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate)); + event.setExtractedEndTime(QDateTime::fromString("2018-05-23T14:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event)); + } + { + Event event("sink.dummy.instance1"); + event.setExtractedStartTime(QDateTime::fromString("2018-05-24T12:00:00Z", Qt::ISODate)); + event.setExtractedEndTime(QDateTime::fromString("2018-05-24T14:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event)); + } + { + Event event("sink.dummy.instance1"); + VERIFYEXEC(Sink::Store::create(event)); + } + + VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1")); + } + + void testOverlap() + { + eventsWithDates(); + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-22T12:00:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 5); + } + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-22T12:30:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-22T12:31:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 0); + } + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-24T10:00:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-24T11:00:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 0); + } + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-23T12:30:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-23T12:31:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 2); + } + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-22T12:30:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 2); + } + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-23T14:30:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-23T16:00:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 1); + } + + } + + void testOverlapLive() + { + eventsWithDates(); + + { + Sink::Query query; + query.resourceFilter("sink.dummy.instance1"); + query.setFlags(Query::LiveQuery); + query.filter(QueryBase::Comparator( + QVariantList{ QDateTime::fromString("2018-05-22T12:00:00Z", Qt::ISODate), + QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) }, + QueryBase::Comparator::Overlap)); + auto model = Sink::Store::loadModel(query); + QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); + QCOMPARE(model->rowCount(), 5); + + Event event = Event::createEntity("sink.dummy.instance1"); + event.setExtractedStartTime(QDateTime::fromString("2018-05-23T12:00:00Z", Qt::ISODate)); + event.setExtractedEndTime(QDateTime::fromString("2018-05-23T13:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event)); + + Event event2 = Event::createEntity("sink.dummy.instance1"); + event2.setExtractedStartTime(QDateTime::fromString("2018-05-33T12:00:00Z", Qt::ISODate)); + event2.setExtractedEndTime(QDateTime::fromString("2018-05-33T13:00:00Z", Qt::ISODate)); + VERIFYEXEC(Sink::Store::create(event2)); + + QTest::qWait(500); + QCOMPARE(model->rowCount(), 6); + + VERIFYEXEC(Sink::Store::remove(event)); + VERIFYEXEC(Sink::Store::remove(event2)); + + QTest::qWait(500); + QCOMPARE(model->rowCount(), 5); + } + + } + }; QTEST_MAIN(QueryTest) -- cgit v1.2.3