diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp --- a/common/datastorequery.cpp +++ b/common/datastorequery.cpp @@ -119,7 +119,7 @@ public: typedef QSharedPointer Ptr; - QHash propertyFilter; + QHash propertyFilter; Filter(FilterBase::Ptr source, DataStoreQuery *store) : FilterBase(source, store) @@ -158,7 +158,16 @@ 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 @@ })) {} mBloomed = true; - propertyFilter.insert(mBloomProperty, mBloomValue); + propertyFilter.insert({mBloomProperty}, mBloomValue); return foundValue; } else { //Filter on bloom value @@ -598,7 +607,7 @@ //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 --- 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.h b/common/query.h --- a/common/query.h +++ b/common/query.h @@ -37,6 +37,7 @@ Contains, In, Within, + Overlap, Fulltext }; @@ -53,7 +54,7 @@ class SINK_EXPORT Filter { public: QByteArrayList ids; - QHash propertyFilter; + QHash propertyFilter; bool operator==(const Filter &other) const; }; @@ -64,18 +65,29 @@ 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 Comparator getFilter() const { 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 @@ return mId; } - void setBaseFilters(const QHash &filter) + void setBaseFilters(const QHash &filter) { mBaseFilterStage.propertyFilter = filter; } @@ -104,7 +116,7 @@ mBaseFilterStage = filter; } - QHash getBaseFilters() const + QHash getBaseFilters() const { return mBaseFilterStage.propertyFilter; } @@ -131,7 +143,12 @@ 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 @@ 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 @@ 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 @@ 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/query.cpp b/common/query.cpp --- a/common/query.cpp +++ b/common/query.cpp @@ -179,6 +179,19 @@ 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/resourcefacade.cpp b/common/resourcefacade.cpp --- a/common/resourcefacade.cpp +++ b/common/resourcefacade.cpp @@ -80,13 +80,13 @@ return object; } -static bool matchesFilter(const QHash &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 @@ //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.h b/common/storage/entitystore.h --- a/common/storage/entitystore.h +++ b/common/storage/entitystore.h @@ -57,7 +57,7 @@ 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/storage/entitystore.cpp b/common/storage/entitystore.cpp --- a/common/storage/entitystore.cpp +++ b/common/storage/entitystore.cpp @@ -443,7 +443,7 @@ 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/store.cpp b/common/store.cpp --- a/common/store.cpp +++ b/common/store.cpp @@ -117,11 +117,13 @@ //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 @@ { 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.h b/common/typeindex.h --- a/common/typeindex.h +++ b/common/typeindex.h @@ -73,10 +73,19 @@ mCustomIndexer << CustomIndexer::Ptr::create(); } + template + 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,16 +124,19 @@ 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; QByteArrayList mSortedProperties; 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/common/typeindex.cpp b/common/typeindex.cpp --- a/common/typeindex.cpp +++ b/common/typeindex.cpp @@ -53,15 +53,20 @@ 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) { // Sort invalid last 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 @@ 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,13 +223,54 @@ 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) { const auto value = entity.getProperty(property); 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 @@ 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 @@ } } + for (auto it = baseFilters.constBegin(); it != baseFilters.constEnd(); it++) { + if (it.value().comparator == QueryBase::Comparator::Overlap) { + if (mSampledPeriodProperties.contains({it.key()[0], it.key()[1]})) { + Index index(sampledPeriodIndexName(it.key()[0], it.key()[1]), transaction); + const auto keys = sampledIndexLookup(index, query.getFilter(it.key())); + // The filter is not completely applied, we need post-filtering + // in the case the overlap period is not completely aligned + // with a week starting on monday + //appliedFilters << it.key(); + SinkTraceCtx(mLogCtx) << "Sampled period index lookup on" << it.key() << "found" << keys.size() << "keys."; + return keys; + } else { + SinkWarning() << "Overlap search without sampled period index"; + } + } + } + for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { Index index(indexName(it.key(), it.value()), transaction); const auto keys = indexLookup(index, query.getFilter(it.key())); - appliedFilters << it.key(); + appliedFilters.insert({it.key()}); appliedSorting = it.value(); SinkTraceCtx(mLogCtx) << "Grouped sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; return keys; @@ -340,7 +450,7 @@ if (query.hasFilter(property)) { Index index(sortedIndexName(property), transaction); const auto keys = sortedIndexLookup(index, query.getFilter(property)); - appliedFilters << property; + appliedFilters.insert({property}); SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << property << " found " << keys.size() << " keys."; return keys; } @@ -350,7 +460,7 @@ if (query.hasFilter(property)) { Index index(indexName(property), transaction); const auto keys = indexLookup(index, query.getFilter(property)); - appliedFilters << property; + appliedFilters.insert({property}); SinkTraceCtx(mLogCtx) << "Index lookup on " << property << " found " << keys.size() << " keys."; return keys; } diff --git a/tests/querytest.cpp b/tests/querytest.cpp --- a/tests/querytest.cpp +++ b/tests/querytest.cpp @@ -59,7 +59,7 @@ 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 @@ 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)