From 096d14f20f4dc39d20d35d605ca755b66bd48cf9 Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Sun, 1 May 2016 16:22:55 +0200 Subject: Account filter for resources and contains comparator in query --- common/CMakeLists.txt | 1 + common/query.cpp | 56 ++++++++++++++++++++++++++++++++++++++++ common/query.h | 43 ++++++++++++++++++++++++++++-- common/queryrunner.cpp | 10 +++---- common/resourcefacade.cpp | 7 ++--- common/store.cpp | 25 ++++++++++++++---- common/typeindex.cpp | 4 +-- tests/clientapitest.cpp | 6 ++--- tests/dummyresourcebenchmark.cpp | 2 +- tests/dummyresourcetest.cpp | 38 ++++++++++----------------- tests/maildirresourcetest.cpp | 2 +- tests/mailquerybenchmark.cpp | 2 +- tests/querytest.cpp | 6 ++--- 13 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 common/query.cpp diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 54d86f3..c269a85 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -65,6 +65,7 @@ set(command_SRCS domain/mail.cpp domain/folder.cpp test.cpp + query.cpp ${storage_SRCS}) add_library(${PROJECT_NAME} SHARED ${command_SRCS}) diff --git a/common/query.cpp b/common/query.cpp new file mode 100644 index 0000000..a80aecb --- /dev/null +++ b/common/query.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Christian Mollekopf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ +#include "query.h" + +using namespace Sink; + +QDebug operator<<(QDebug dbg, const Sink::Query::Comparator &c) +{ + if (c.comparator == Sink::Query::Comparator::Equals) { + dbg.nospace() << "== " << c.value; + } else if (c.comparator == Sink::Query::Comparator::Contains) { + dbg.nospace() << "contains " << c.value; + } else { + dbg.nospace() << "unknown comparator: " << c.value; + } + + return dbg.space(); +} + +Query::Comparator::Comparator() : comparator(Invalid) +{ +} + +Query::Comparator::Comparator(const QVariant &v) : value(v), comparator(Equals) +{ +} + +bool Query::Comparator::matches(const QVariant &v) const +{ + switch(comparator) { + case Equals: + return v == value; + case Contains: + return v.toList().contains(value); + default: + break; + } + return false; +} diff --git a/common/query.h b/common/query.h index 3a56c9f..ccac1e7 100644 --- a/common/query.h +++ b/common/query.h @@ -19,6 +19,7 @@ */ #pragma once +#include "sink_export.h" #include #include #include @@ -29,7 +30,7 @@ namespace Sink { /** * A query that matches a set of entities. */ -class Query +class SINK_EXPORT Query { public: enum Flag @@ -46,6 +47,13 @@ public: return query; } + static Query PropertyContainsFilter(const QByteArray &key, const QVariant &value) + { + Query query; + query.propertyFilter.insert(key, value); + return query; + } + static Query PropertyFilter(const QByteArray &key, const ApplicationDomain::Entity &entity) { return PropertyFilter(key, QVariant::fromValue(entity.identifier())); @@ -70,6 +78,18 @@ public: return ResourceFilter(entity.identifier()); } + static Query AccountFilter(const QByteArrayList &identifier) + { + Query query; + query.accounts = identifier; + return query; + } + + static Query AccountFilter(const ApplicationDomain::SinkAccount &entity) + { + return AccountFilter(entity.identifier()); + } + static Query IdentityFilter(const QByteArray &identifier) { Query query; @@ -110,6 +130,7 @@ public: Query &operator+=(const Query &rhs) { resources += rhs.resources; + accounts += rhs.accounts; ids += rhs.ids; for (auto it = rhs.propertyFilter.constBegin(); it != rhs.propertyFilter.constEnd(); it++) { propertyFilter.insert(it.key(), it.value()); @@ -128,9 +149,25 @@ public: return lhs; } + struct Comparator { + enum Comparators { + Invalid, + Equals, + Contains + }; + + Comparator(); + Comparator(const QVariant &v); + bool matches(const QVariant &v) const; + + QVariant value; + Comparators comparator; + }; + QByteArrayList resources; + QByteArrayList accounts; QByteArrayList ids; - QHash propertyFilter; + QHash propertyFilter; QByteArrayList requestedProperties; QByteArray parentProperty; QByteArray sortProperty; @@ -139,4 +176,6 @@ public: }; } +QDebug operator<<(QDebug dbg, const Sink::Query::Comparator &c); + Q_DECLARE_OPERATORS_FOR_FLAGS(Sink::Query::Flags) diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp index bbaae7b..38fc779 100644 --- a/common/queryrunner.cpp +++ b/common/queryrunner.cpp @@ -382,9 +382,9 @@ QueryWorker::getFilter(const QSet remainingFilters, cons for (const auto &filterProperty : remainingFilters) { const auto property = domainObject->getProperty(filterProperty); if (property.isValid()) { - // TODO implement other comparison operators than equality - if (property != query.propertyFilter.value(filterProperty)) { - Trace() << "Filtering entity due to property mismatch on filter: " << filterProperty << property << ":" << query.propertyFilter.value(filterProperty); + const auto comparator = query.propertyFilter.value(filterProperty); + if (!comparator.matches(property)) { + Trace() << "Filtering entity due to property mismatch on filter: " << filterProperty << property << ":" << comparator.value; return false; } } else { @@ -445,10 +445,10 @@ QPair QueryWorker::executeInitialQuery( if (!query.parentProperty.isEmpty()) { if (parent) { Trace() << "Running initial query for parent:" << parent->identifier(); - modifiedQuery.propertyFilter.insert(query.parentProperty, parent->identifier()); + modifiedQuery.propertyFilter.insert(query.parentProperty, Query::Comparator(parent->identifier())); } else { Trace() << "Running initial query for toplevel"; - modifiedQuery.propertyFilter.insert(query.parentProperty, QVariant()); + modifiedQuery.propertyFilter.insert(query.parentProperty, Query::Comparator(QVariant())); } } auto revisionAndReplayedEntities = load(modifiedQuery, [&](Sink::Storage::Transaction &transaction, QSet &remainingFilters, QByteArray &remainingSorting) -> ResultSet { diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp index e6d235f..96e2ac3 100644 --- a/common/resourcefacade.cpp +++ b/common/resourcefacade.cpp @@ -113,13 +113,13 @@ KAsync::Job LocalStorageFacade::remove(const DomainType &domai }); } -static bool matchesFilter(const QHash &filter, const QMap &properties) +static bool matchesFilter(const QHash &filter, const QMap &properties) { for (const auto &filterProperty : filter.keys()) { if (filterProperty == "type") { continue; } - if (filter.value(filterProperty).toByteArray() != properties.value(filterProperty).toByteArray()) { + if (filter.value(filterProperty).matches(properties.value(filterProperty))) { return false; } } @@ -138,7 +138,8 @@ QPair, typename Sink::ResultEmitter: const auto entries = mConfigStore.getEntries(); for (const auto &res : entries.keys()) { const auto type = entries.value(res); - if (query.propertyFilter.contains("type") && query.propertyFilter.value("type").toByteArray() != type) { + + if (query.propertyFilter.contains("type") && query.propertyFilter.value("type").value.toByteArray() != type) { Trace() << "Skipping due to type."; continue; } diff --git a/common/store.cpp b/common/store.cpp index b89e08c..0ac99be 100644 --- a/common/store.cpp +++ b/common/store.cpp @@ -54,8 +54,16 @@ QString Store::getTemporaryFilePath() /* * Returns a map of resource instance identifiers and resource type */ -static QMap getResources(const QList &resourceFilter, const QByteArray &type = QByteArray()) +static QMap getResources(const QList &resourceFilter, const QList &accountFilter,const QByteArray &type = QByteArray()) { + const auto filterResource = [&](const QByteArray &res) { + const auto configuration = ResourceConfig::getConfiguration(res); + if (!accountFilter.isEmpty() && !accountFilter.contains(configuration.value("account").toByteArray())) { + return true; + } + return false; + }; + QMap resources; // Return the global resource (signified by an empty name) for types that don't belong to a specific resource if (type == "sinkresource" || type == "sinkaccount" || type == "identity") { @@ -65,15 +73,22 @@ static QMap getResources(const QList &resour const auto configuredResources = ResourceConfig::getResources(); if (resourceFilter.isEmpty()) { for (const auto &res : configuredResources.keys()) { + const auto type = configuredResources.value(res); + if (filterResource(res)) { + continue; + } // TODO filter by entity type - resources.insert(res, configuredResources.value(res)); + resources.insert(res, type); } } else { for (const auto &res : resourceFilter) { if (configuredResources.contains(res)) { + if (filterResource(res)) { + continue; + } resources.insert(res, configuredResources.value(res)); } else { - qWarning() << "Resource is not existing: " << res; + Warning() << "Resource is not existing: " << res; } } } @@ -100,7 +115,7 @@ QSharedPointer Store::loadModel(Query query) //* The result provider needs to live for as long as results are provided (until the last thread exits). // Query all resources and aggregate results - auto resources = getResources(query.resources, ApplicationDomain::getTypeName()); + auto resources = getResources(query.resources, query.accounts, ApplicationDomain::getTypeName()); auto aggregatingEmitter = AggregatingResultEmitter::Ptr::create(); model->setEmitter(aggregatingEmitter); KAsync::iterate(resources.keys()) @@ -181,7 +196,7 @@ KAsync::Job Store::removeDataFromDisk(const QByteArray &identifier) KAsync::Job Store::synchronize(const Sink::Query &query) { Trace() << "synchronize" << query.resources; - auto resources = getResources(query.resources).keys(); + auto resources = getResources(query.resources, query.accounts).keys(); return KAsync::iterate(resources) .template each([query](const QByteArray &resource, KAsync::Future &future) { Trace() << "Synchronizing " << resource; diff --git a/common/typeindex.cpp b/common/typeindex.cpp index 1321469..fca083c 100644 --- a/common/typeindex.cpp +++ b/common/typeindex.cpp @@ -143,7 +143,7 @@ ResultSet TypeIndex::query(const Sink::Query &query, QSet &appliedFi for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { if (query.propertyFilter.contains(it.key()) && query.sortProperty == it.value()) { Index index(indexName(it.key(), it.value()), transaction); - const auto lookupKey = getByteArray(query.propertyFilter.value(it.key())); + const auto lookupKey = getByteArray(query.propertyFilter.value(it.key()).value); Trace() << "looking for " << lookupKey; index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, [it](const Index::Error &error) { Warning() << "Error in index: " << error.message << it.key() << it.value(); }, true); @@ -156,7 +156,7 @@ ResultSet TypeIndex::query(const Sink::Query &query, QSet &appliedFi for (const auto &property : mProperties) { if (query.propertyFilter.contains(property)) { Index index(indexName(property), transaction); - const auto lookupKey = getByteArray(query.propertyFilter.value(property)); + const auto lookupKey = getByteArray(query.propertyFilter.value(property).value); index.lookup( lookupKey, [&](const QByteArray &value) { keys << value; }, [property](const Index::Error &error) { Warning() << "Error in index: " << error.message << property; }); appliedFilters << property; diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp index 172232f..96982ca 100644 --- a/tests/clientapitest.cpp +++ b/tests/clientapitest.cpp @@ -58,7 +58,7 @@ public: } Trace() << "-------------------------."; for (const auto &res : results) { - qDebug() << "Parent filter " << query.propertyFilter.value("parent").toByteArray() << res->identifier() << res->getProperty("parent").toByteArray(); + qDebug() << "Parent filter " << query.propertyFilter.value("parent").value.toByteArray() << res->identifier() << res->getProperty("parent").toByteArray(); auto parentProperty = res->getProperty("parent").toByteArray(); if ((!parent && parentProperty.isEmpty()) || (parent && parentProperty == parent->identifier()) || query.parentProperty.isEmpty()) { qDebug() << "Found a hit" << res->identifier(); @@ -132,7 +132,7 @@ private slots: Sink::Store::create(res).exec().waitForFinished(); { Sink::Query query; - query.propertyFilter.insert("type", "dummyresource"); + query.propertyFilter.insert("type", Sink::Query::Comparator("dummyresource")); auto model = Sink::Store::loadModel(query); QTRY_COMPARE(model->rowCount(QModelIndex()), 1); } @@ -140,7 +140,7 @@ private slots: Sink::Store::remove(res).exec().waitForFinished(); { Sink::Query query; - query.propertyFilter.insert("type", "dummyresource"); + query.propertyFilter.insert("type", Sink::Query::Comparator("dummyresource")); auto model = Sink::Store::loadModel(query); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 0); diff --git a/tests/dummyresourcebenchmark.cpp b/tests/dummyresourcebenchmark.cpp index 1e71fc2..d5f98c3 100644 --- a/tests/dummyresourcebenchmark.cpp +++ b/tests/dummyresourcebenchmark.cpp @@ -157,7 +157,7 @@ private slots: Sink::Query query; query.resources << "org.kde.dummy.instance1"; - query.propertyFilter.insert("uid", "testuid"); + query.propertyFilter.insert("uid", Sink::Query::Comparator("testuid")); auto model = Sink::Store::loadModel(query); QTRY_COMPARE(model->rowCount(QModelIndex()), num); } diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp index 33304e1..58df2da 100644 --- a/tests/dummyresourcetest.cpp +++ b/tests/dummyresourcetest.cpp @@ -12,6 +12,8 @@ #include "pipeline.h" #include "log.h" +using namespace Sink; + /** * Test of complete system using the dummy resource. * @@ -62,14 +64,12 @@ private slots: event.setProperty("summary", "summaryValue"); Sink::Store::create(event).exec().waitForFinished(); - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; + const auto query = Query::ResourceFilter("org.kde.dummy.instance1") ; // Ensure all local data is processed Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); - query.propertyFilter.insert("uid", "testuid"); - auto model = Sink::Store::loadModel(query); + auto model = Sink::Store::loadModel(query + Query::PropertyFilter("uid", "testuid")); QTRY_COMPARE(model->rowCount(QModelIndex()), 1); auto value = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value(); QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); @@ -86,15 +86,12 @@ private slots: event.setProperty("uid", "testuid2"); Sink::Store::create(event).exec().waitForFinished(); - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; + const auto query = Query::ResourceFilter("org.kde.dummy.instance1") ; // Ensure all local data is processed Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); - query.propertyFilter.insert("uid", "testuid"); - - auto model = Sink::Store::loadModel(query); + auto model = Sink::Store::loadModel(query + Query::PropertyFilter("uid", "testuid")); QTRY_COMPARE(model->rowCount(QModelIndex()), 1); auto value = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value(); @@ -114,15 +111,12 @@ private slots: event.setProperty("summary", "summaryValue2"); Sink::Store::create(event).exec().waitForFinished(); - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; + const auto query = Query::ResourceFilter("org.kde.dummy.instance1") ; // Ensure all local data is processed Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); - query.propertyFilter.insert("summary", "summaryValue2"); - - auto model = Sink::Store::loadModel(query); + auto model = Sink::Store::loadModel(query + Query::PropertyFilter("summary", "summaryValue2")); QTRY_COMPARE(model->rowCount(QModelIndex()), 1); auto value = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value(); @@ -147,8 +141,7 @@ private slots: void testSyncAndFacade() { - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; + const auto query = Query::ResourceFilter("org.kde.dummy.instance1"); // Ensure all local data is processed Sink::Store::synchronize(query).exec().waitForFinished(); @@ -164,8 +157,7 @@ private slots: void testSyncAndFacadeMail() { - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; + const auto query = Query::ResourceFilter("org.kde.dummy.instance1"); // Ensure all local data is processed Sink::Store::synchronize(query).exec().waitForFinished(); @@ -187,9 +179,7 @@ private slots: event.setProperty("summary", "summaryValue"); Sink::Store::create(event).exec().waitForFinished(); - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; - query.propertyFilter.insert("uid", "testuid"); + const auto query = Query::ResourceFilter("org.kde.dummy.instance1") + Query::PropertyFilter("uid", "testuid"); // Ensure all local data is processed Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); @@ -238,11 +228,9 @@ private slots: void testWriteModifyDeleteLive() { - - Sink::Query query; - query.resources << "org.kde.dummy.instance1"; + auto query = Query::ResourceFilter("org.kde.dummy.instance1"); query.liveQuery = true; - query.propertyFilter.insert("uid", "testuid"); + query += Query::PropertyFilter("uid", "testuid"); auto model = Sink::Store::loadModel(query); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); diff --git a/tests/maildirresourcetest.cpp b/tests/maildirresourcetest.cpp index b5c1c3c..9b014e3 100644 --- a/tests/maildirresourcetest.cpp +++ b/tests/maildirresourcetest.cpp @@ -265,7 +265,7 @@ private slots: Sink::Query folderQuery; folderQuery.resources << "org.kde.maildir.instance1"; - folderQuery.propertyFilter.insert("name", "testCreateFolder"); + folderQuery += Sink::Query::PropertyFilter("name", "testCreateFolder"); auto model = Sink::Store::loadModel(folderQuery); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 1); diff --git a/tests/mailquerybenchmark.cpp b/tests/mailquerybenchmark.cpp index 6b93863..20ee63c 100644 --- a/tests/mailquerybenchmark.cpp +++ b/tests/mailquerybenchmark.cpp @@ -159,7 +159,7 @@ private slots: << "subject" << "date"; query.sortProperty = "date"; - query.propertyFilter.insert("folder", "folder1"); + query += Sink::Query::PropertyFilter("folder", "folder1"); query.limit = 1000; populateDatabase(50000); diff --git a/tests/querytest.cpp b/tests/querytest.cpp index 7b9129e..a654931 100644 --- a/tests/querytest.cpp +++ b/tests/querytest.cpp @@ -202,7 +202,7 @@ private slots: Sink::Query query; query.resources << "org.kde.dummy.instance1"; query.liveQuery = false; - query.propertyFilter.insert("uid", "test1"); + query += Sink::Query::PropertyFilter("uid", "test1"); // Ensure all local data is processed Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); @@ -243,7 +243,7 @@ private slots: // Test Sink::Query query; query.resources << "org.kde.dummy.instance1"; - query.propertyFilter.insert("folder", folderEntity->identifier()); + query += Sink::Query::PropertyFilter("folder", *folderEntity); // Ensure all local data is processed Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); @@ -302,7 +302,7 @@ private slots: // Test Sink::Query query; query.resources << "org.kde.dummy.instance1"; - query.propertyFilter.insert("folder", folderEntity->identifier()); + query += Sink::Query::PropertyFilter("folder", *folderEntity); query.sortProperty = "date"; query.limit = 1; query.liveQuery = false; -- cgit v1.2.3