Rex Dieter adf30a
From c24329bb570ee16c033228588e6d22b0f6000f95 Mon Sep 17 00:00:00 2001
Rex Dieter adf30a
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Rex Dieter adf30a
Date: Fri, 5 Dec 2014 18:23:33 +0100
Rex Dieter adf30a
Subject: [PATCH 22/30] Implement cache for CollectionStatistics to
Rex Dieter adf30a
 significantly reduce amount of SQL queries
Rex Dieter adf30a
Rex Dieter adf30a
Collection statistics are being requested extremely often (basically whenever
Rex Dieter adf30a
a PimItem is changed, or when a Collection itself is changed), and it's always
Rex Dieter adf30a
requested by at least 5 or so clients (including agents that listen to
Rex Dieter adf30a
everything).
Rex Dieter adf30a
Rex Dieter adf30a
To decrease the load on database we now cache the Collection statistics and
Rex Dieter adf30a
we only invalidate a cache entry when respective collection (or it's content)
Rex Dieter adf30a
is changed. The invalidation is invoked from NotificationCollector, which is
Rex Dieter adf30a
basically a hack, but performance-wise it's the best place to avoid additional
Rex Dieter adf30a
expensive queries.
Rex Dieter adf30a
Rex Dieter adf30a
This patch also optimizes the SQL query needed to get up-to-date statistics.
Rex Dieter adf30a
We now have only one query to get both full count and read items count, which
Rex Dieter adf30a
a bit is faster as the database only has to deal with one large JOIN.
Rex Dieter adf30a
Rex Dieter adf30a
Thanks to the cache the number of SQL queries for Collection statistics have
Rex Dieter adf30a
reduced by 70%-80%, and average query duration is now between 20 and 80ms
Rex Dieter adf30a
depending on average collection size and database used.
Rex Dieter adf30a
---
Rex Dieter adf30a
 server/CMakeLists.txt                        |   1 +
Rex Dieter adf30a
 server/src/handler/link.cpp                  |   2 +-
Rex Dieter adf30a
 server/src/handler/merge.cpp                 |   4 +-
Rex Dieter adf30a
 server/src/handler/select.cpp                |  14 ++--
Rex Dieter adf30a
 server/src/handler/status.cpp                |  20 ++---
Rex Dieter adf30a
 server/src/handlerhelper.cpp                 |  81 ++------------------
Rex Dieter adf30a
 server/src/handlerhelper.h                   |  22 ------
Rex Dieter adf30a
 server/src/storage/collectionstatistics.cpp  | 108 +++++++++++++++++++++++++++
Rex Dieter adf30a
 server/src/storage/collectionstatistics.h    |  70 +++++++++++++++++
Rex Dieter adf30a
 server/src/storage/datastore.cpp             |   8 +-
Rex Dieter adf30a
 server/src/storage/datastore.h               |   6 +-
Rex Dieter adf30a
 server/src/storage/notificationcollector.cpp |   8 ++
Rex Dieter adf30a
 server/tests/unittest/fakedatastore.cpp      |   8 +-
Rex Dieter adf30a
 server/tests/unittest/fakedatastore.h        |   2 +
Rex Dieter adf30a
 14 files changed, 224 insertions(+), 130 deletions(-)
Rex Dieter adf30a
 create mode 100644 server/src/storage/collectionstatistics.cpp
Rex Dieter adf30a
 create mode 100644 server/src/storage/collectionstatistics.h
Rex Dieter adf30a
Rex Dieter adf30a
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
Rex Dieter adf30a
index 275938d..f0e0093 100644
Rex Dieter adf30a
--- a/server/CMakeLists.txt
Rex Dieter adf30a
+++ b/server/CMakeLists.txt
Rex Dieter adf30a
@@ -161,6 +161,7 @@ set(libakonadiprivate_SRCS
Rex Dieter adf30a
   src/search/searchmanager.cpp
Rex Dieter adf30a
 
Rex Dieter adf30a
   src/storage/collectionqueryhelper.cpp
Rex Dieter adf30a
+  src/storage/collectionstatistics.cpp
Rex Dieter adf30a
   src/storage/entity.cpp
Rex Dieter adf30a
   ${CMAKE_CURRENT_BINARY_DIR}/entities.cpp
Rex Dieter adf30a
   ${CMAKE_CURRENT_BINARY_DIR}/akonadischema.cpp
Rex Dieter adf30a
diff --git a/server/src/handler/link.cpp b/server/src/handler/link.cpp
Rex Dieter adf30a
index ce18e47..227de11 100644
Rex Dieter adf30a
--- a/server/src/handler/link.cpp
Rex Dieter adf30a
+++ b/server/src/handler/link.cpp
Rex Dieter adf30a
@@ -25,10 +25,10 @@
Rex Dieter adf30a
 #include "storage/itemqueryhelper.h"
Rex Dieter adf30a
 #include "storage/transaction.h"
Rex Dieter adf30a
 #include "storage/selectquerybuilder.h"
Rex Dieter adf30a
+#include "storage/collectionqueryhelper.h"
Rex Dieter adf30a
 #include "entities.h"
Rex Dieter adf30a
 
Rex Dieter adf30a
 #include "imapstreamparser.h"
Rex Dieter adf30a
-#include <storage/collectionqueryhelper.h>
Rex Dieter adf30a
 
Rex Dieter adf30a
 using namespace Akonadi::Server;
Rex Dieter adf30a
 
Rex Dieter adf30a
diff --git a/server/src/handler/merge.cpp b/server/src/handler/merge.cpp
Rex Dieter adf30a
index c26917d..5149916 100644
Rex Dieter adf30a
--- a/server/src/handler/merge.cpp
Rex Dieter adf30a
+++ b/server/src/handler/merge.cpp
Rex Dieter adf30a
@@ -88,7 +88,7 @@ bool Merge::mergeItem( PimItem &newItem, PimItem &currentItem,
Rex Dieter adf30a
       if ( !itemFlags.removed.isEmpty() ) {
Rex Dieter adf30a
         const Flag::List removedFlags = HandlerHelper::resolveFlags( itemFlags.removed );
Rex Dieter adf30a
         DataStore::self()->removeItemsFlags( PimItem::List() << currentItem, removedFlags,
Rex Dieter adf30a
-                                             &flagsRemoved, true );
Rex Dieter adf30a
+                                             &flagsRemoved, col, true );
Rex Dieter adf30a
       }
Rex Dieter adf30a
 
Rex Dieter adf30a
       if ( flagsAdded || flagsRemoved ) {
Rex Dieter adf30a
@@ -98,7 +98,7 @@ bool Merge::mergeItem( PimItem &newItem, PimItem &currentItem,
Rex Dieter adf30a
       bool flagsChanged = false;
Rex Dieter adf30a
       const Flag::List flags = HandlerHelper::resolveFlags( itemFlags.added );
Rex Dieter adf30a
       DataStore::self()->setItemsFlags( PimItem::List() << currentItem, flags,
Rex Dieter adf30a
-                                        &flagsChanged, true );
Rex Dieter adf30a
+                                        &flagsChanged, col, true );
Rex Dieter adf30a
       if ( flagsChanged ) {
Rex Dieter adf30a
         mChangedParts << AKONADI_PARAM_FLAGS;
Rex Dieter adf30a
       }
Rex Dieter adf30a
diff --git a/server/src/handler/select.cpp b/server/src/handler/select.cpp
Rex Dieter adf30a
index 1c5dd8a..f1ecc44 100644
Rex Dieter adf30a
--- a/server/src/handler/select.cpp
Rex Dieter adf30a
+++ b/server/src/handler/select.cpp
Rex Dieter adf30a
@@ -27,6 +27,7 @@
Rex Dieter adf30a
 #include "handlerhelper.h"
Rex Dieter adf30a
 #include "imapstreamparser.h"
Rex Dieter adf30a
 #include "storage/selectquerybuilder.h"
Rex Dieter adf30a
+#include "storage/collectionstatistics.h"
Rex Dieter adf30a
 #include "commandcontext.h"
Rex Dieter adf30a
 
Rex Dieter adf30a
 #include "response.h"
Rex Dieter adf30a
@@ -96,19 +97,14 @@ bool Select::parseStream()
Rex Dieter adf30a
     response.setString( "FLAGS (" + Flag::joinByName( Flag::retrieveAll(), QLatin1String( " " ) ).toLatin1() + ")" );
Rex Dieter adf30a
     Q_EMIT responseAvailable( response );
Rex Dieter adf30a
 
Rex Dieter adf30a
-    const int itemCount = HandlerHelper::itemCount( col );
Rex Dieter adf30a
-    if ( itemCount < 0 ) {
Rex Dieter adf30a
+    const CollectionStatistics::Statistics stats = CollectionStatistics::instance()->statistics(col);
Rex Dieter adf30a
+    if ( stats.count == -1 ) {
Rex Dieter adf30a
       return failureResponse( "Unable to determine item count" );
Rex Dieter adf30a
     }
Rex Dieter adf30a
-    response.setString( QByteArray::number( itemCount ) + " EXISTS" );
Rex Dieter adf30a
+    response.setString( QByteArray::number( stats.count ) + " EXISTS" );
Rex Dieter adf30a
     Q_EMIT responseAvailable( response );
Rex Dieter adf30a
 
Rex Dieter adf30a
-    int readCount = HandlerHelper::itemWithFlagsCount( col, QStringList() << QLatin1String( AKONADI_FLAG_SEEN )
Rex Dieter adf30a
-                                                                          << QLatin1String( AKONADI_FLAG_IGNORED ) );
Rex Dieter adf30a
-    if ( readCount < 0 || itemCount < readCount ) {
Rex Dieter adf30a
-      return failureResponse( "Unable to retrieve unseen count" );
Rex Dieter adf30a
-    }
Rex Dieter adf30a
-    response.setString( "OK [UNSEEN " + QByteArray::number( itemCount - readCount ) + "] Message 0 is first unseen" );
Rex Dieter adf30a
+    response.setString( "OK [UNSEEN " + QByteArray::number( stats.count - stats.read ) + "] Message 0 is first unseen" );
Rex Dieter adf30a
     Q_EMIT responseAvailable( response );
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
diff --git a/server/src/handler/status.cpp b/server/src/handler/status.cpp
Rex Dieter adf30a
index 8c6823d..283532c 100644
Rex Dieter adf30a
--- a/server/src/handler/status.cpp
Rex Dieter adf30a
+++ b/server/src/handler/status.cpp
Rex Dieter adf30a
@@ -25,6 +25,7 @@
Rex Dieter adf30a
 #include "storage/datastore.h"
Rex Dieter adf30a
 #include "storage/entity.h"
Rex Dieter adf30a
 #include "storage/countquerybuilder.h"
Rex Dieter adf30a
+#include "storage/collectionstatistics.h"
Rex Dieter adf30a
 
Rex Dieter adf30a
 #include "response.h"
Rex Dieter adf30a
 #include "handlerhelper.h"
Rex Dieter adf30a
@@ -62,9 +63,9 @@ bool Status::parseStream()
Rex Dieter adf30a
     // Responses:
Rex Dieter adf30a
     // REQUIRED untagged responses: STATUS
Rex Dieter adf30a
 
Rex Dieter adf30a
-  qint64 itemCount, itemSize;
Rex Dieter adf30a
-  if ( !HandlerHelper::itemStatistics( col, itemCount, itemSize ) ) {
Rex Dieter adf30a
-    return failureResponse( "Failed to query statistics." );
Rex Dieter adf30a
+  const CollectionStatistics::Statistics &stats = CollectionStatistics::instance()->statistics(col);
Rex Dieter adf30a
+  if (stats.count == -1) {
Rex Dieter adf30a
+      return failureResponse( "Failed to query statistics." );
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
     // build STATUS response
Rex Dieter adf30a
@@ -72,7 +73,7 @@ bool Status::parseStream()
Rex Dieter adf30a
     // MESSAGES - The number of messages in the mailbox
Rex Dieter adf30a
   if ( attributeList.contains( AKONADI_ATTRIBUTE_MESSAGES ) ) {
Rex Dieter adf30a
     statusResponse += AKONADI_ATTRIBUTE_MESSAGES " ";
Rex Dieter adf30a
-    statusResponse += QByteArray::number( itemCount );
Rex Dieter adf30a
+    statusResponse += QByteArray::number( stats.count );
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
   if ( attributeList.contains( AKONADI_ATTRIBUTE_UNSEEN ) ) {
Rex Dieter adf30a
@@ -80,21 +81,14 @@ bool Status::parseStream()
Rex Dieter adf30a
       statusResponse += " ";
Rex Dieter adf30a
     }
Rex Dieter adf30a
     statusResponse += AKONADI_ATTRIBUTE_UNSEEN " ";
Rex Dieter adf30a
-
Rex Dieter adf30a
-    // itemWithFlagCount is twice as fast as itemWithoutFlagCount...
Rex Dieter adf30a
-    const int count = HandlerHelper::itemWithFlagsCount( col, QStringList() << QLatin1String( AKONADI_FLAG_SEEN )
Rex Dieter adf30a
-                                                                            << QLatin1String( AKONADI_FLAG_IGNORED ) );
Rex Dieter adf30a
-    if ( count < 0 ) {
Rex Dieter adf30a
-      return failureResponse( "Unable to retrieve unread count" );
Rex Dieter adf30a
-    }
Rex Dieter adf30a
-    statusResponse += QByteArray::number( itemCount - count );
Rex Dieter adf30a
+    statusResponse += QByteArray::number( stats.count - stats.read );
Rex Dieter adf30a
   }
Rex Dieter adf30a
   if ( attributeList.contains( AKONADI_PARAM_SIZE ) ) {
Rex Dieter adf30a
     if ( !statusResponse.isEmpty() ) {
Rex Dieter adf30a
       statusResponse += " ";
Rex Dieter adf30a
     }
Rex Dieter adf30a
     statusResponse += AKONADI_PARAM_SIZE " ";
Rex Dieter adf30a
-    statusResponse += QByteArray::number( itemSize );
Rex Dieter adf30a
+    statusResponse += QByteArray::number( stats.size );
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
   Response response;
Rex Dieter adf30a
diff --git a/server/src/handlerhelper.cpp b/server/src/handlerhelper.cpp
Rex Dieter adf30a
index 82347b4..39583ce 100644
Rex Dieter adf30a
--- a/server/src/handlerhelper.cpp
Rex Dieter adf30a
+++ b/server/src/handlerhelper.cpp
Rex Dieter adf30a
@@ -22,6 +22,7 @@
Rex Dieter adf30a
 #include "storage/countquerybuilder.h"
Rex Dieter adf30a
 #include "storage/datastore.h"
Rex Dieter adf30a
 #include "storage/selectquerybuilder.h"
Rex Dieter adf30a
+#include "storage/collectionstatistics.h"
Rex Dieter adf30a
 #include "storage/queryhelper.h"
Rex Dieter adf30a
 #include "libs/imapparser_p.h"
Rex Dieter adf30a
 #include "libs/protocol_p.h"
Rex Dieter adf30a
@@ -78,74 +79,6 @@ QString HandlerHelper::pathForCollection( const Collection &col )
Rex Dieter adf30a
   return parts.join( QLatin1String( "/" ) );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
-bool HandlerHelper::itemStatistics( const Collection &col, qint64 &count, qint64 &size )
Rex Dieter adf30a
-{
Rex Dieter adf30a
-  QueryBuilder qb( PimItem::tableName() );
Rex Dieter adf30a
-  qb.addAggregation( PimItem::idColumn(), QLatin1String( "count" ) );
Rex Dieter adf30a
-  qb.addAggregation( PimItem::sizeColumn(), QLatin1String( "sum" ) );
Rex Dieter adf30a
-
Rex Dieter adf30a
-  if ( col.isVirtual() ) {
Rex Dieter adf30a
-    qb.addJoin( QueryBuilder::InnerJoin, CollectionPimItemRelation::tableName(),
Rex Dieter adf30a
-                CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName() );
Rex Dieter adf30a
-    qb.addValueCondition( CollectionPimItemRelation::leftFullColumnName(), Query::Equals, col.id() );
Rex Dieter adf30a
-  } else {
Rex Dieter adf30a
-    qb.addValueCondition( PimItem::collectionIdColumn(), Query::Equals, col.id() );
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-
Rex Dieter adf30a
-  if ( !qb.exec() ) {
Rex Dieter adf30a
-    return false;
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-  if ( !qb.query().next() ) {
Rex Dieter adf30a
-    akError() << "Error during retrieving result of statistics query:" << qb.query().lastError().text();
Rex Dieter adf30a
-    return false;
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-  count = qb.query().value( 0 ).toLongLong();
Rex Dieter adf30a
-  size = qb.query().value( 1 ).toLongLong();
Rex Dieter adf30a
-  return true;
Rex Dieter adf30a
-}
Rex Dieter adf30a
-
Rex Dieter adf30a
-int HandlerHelper::itemWithFlagsCount( const Collection &col, const QStringList &flags )
Rex Dieter adf30a
-{
Rex Dieter adf30a
-  CountQueryBuilder qb( PimItem::tableName(), PimItem::idFullColumnName(), CountQueryBuilder::Distinct );
Rex Dieter adf30a
-  qb.addJoin( QueryBuilder::InnerJoin, PimItemFlagRelation::tableName(),
Rex Dieter adf30a
-              PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName() );
Rex Dieter adf30a
-  if ( col.isVirtual() ) {
Rex Dieter adf30a
-    qb.addJoin( QueryBuilder::InnerJoin, CollectionPimItemRelation::tableName(),
Rex Dieter adf30a
-                CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName() );
Rex Dieter adf30a
-    qb.addValueCondition( CollectionPimItemRelation::leftFullColumnName(), Query::Equals, col.id() );
Rex Dieter adf30a
-  } else {
Rex Dieter adf30a
-    qb.addValueCondition( PimItem::collectionIdFullColumnName(), Query::Equals, col.id() );
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-  Query::Condition cond( Query::Or );
Rex Dieter adf30a
-  // We use the below instead of an inner join in the query above because postgres seems
Rex Dieter adf30a
-  // to struggle to optimize the two inner joins, despite having indices that should
Rex Dieter adf30a
-  // facilitate that. This exploits the fact that the Flag::retrieveByName is fast because
Rex Dieter adf30a
-  // it hits an in-memory cache.
Rex Dieter adf30a
-  Q_FOREACH ( const QString &flag, flags ) {
Rex Dieter adf30a
-    const Flag f = Flag::retrieveByName( flag );
Rex Dieter adf30a
-    if (!f.isValid()) {
Rex Dieter adf30a
-      // since we OR this condition, we can skip invalid flags to speed up the query
Rex Dieter adf30a
-      continue;
Rex Dieter adf30a
-    }
Rex Dieter adf30a
-    cond.addValueCondition( PimItemFlagRelation::rightFullColumnName(), Query::Equals, f.id() );
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-  qb.addCondition( cond );
Rex Dieter adf30a
-  if ( !qb.exec() ) {
Rex Dieter adf30a
-    return -1;
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-  return qb.result();
Rex Dieter adf30a
-}
Rex Dieter adf30a
-
Rex Dieter adf30a
-int HandlerHelper::itemCount( const Collection &col )
Rex Dieter adf30a
-{
Rex Dieter adf30a
-  CountQueryBuilder qb( PimItem::tableName() );
Rex Dieter adf30a
-  qb.addValueCondition( PimItem::collectionIdColumn(), Query::Equals, col.id() );
Rex Dieter adf30a
-  if ( !qb.exec() ) {
Rex Dieter adf30a
-    return -1;
Rex Dieter adf30a
-  }
Rex Dieter adf30a
-  return qb.result();
Rex Dieter adf30a
-}
Rex Dieter adf30a
-
Rex Dieter adf30a
 int HandlerHelper::parseCachePolicy( const QByteArray &data, Collection &col, int start, bool *changed )
Rex Dieter adf30a
 {
Rex Dieter adf30a
   bool inheritChanged = false;
Rex Dieter adf30a
@@ -233,14 +166,12 @@ QByteArray HandlerHelper::collectionToByteArray( const Collection &col, bool hid
Rex Dieter adf30a
   b += " " AKONADI_PARAM_VIRTUAL " " + QByteArray::number( col.isVirtual() ) + ' ';
Rex Dieter adf30a
 
Rex Dieter adf30a
   if ( includeStatistics ) {
Rex Dieter adf30a
-    qint64 itemCount, itemSize;
Rex Dieter adf30a
-    if ( itemStatistics( col, itemCount, itemSize ) ) {
Rex Dieter adf30a
-      b += AKONADI_ATTRIBUTE_MESSAGES " " + QByteArray::number( itemCount ) + ' ';
Rex Dieter adf30a
-      // itemWithFlagCount is twice as fast as itemWithoutFlagCount, so emulated that...
Rex Dieter adf30a
+    const CollectionStatistics::Statistics &stats = CollectionStatistics::instance()->statistics(col);
Rex Dieter adf30a
+    if (stats.count > -1) {
Rex Dieter adf30a
+      b += AKONADI_ATTRIBUTE_MESSAGES " " + QByteArray::number( stats.count ) + ' ';
Rex Dieter adf30a
       b += AKONADI_ATTRIBUTE_UNSEEN " ";
Rex Dieter adf30a
-      b += QByteArray::number( itemCount - itemWithFlagsCount( col, QStringList() << QLatin1String( AKONADI_FLAG_SEEN )
Rex Dieter adf30a
-                                                                                  << QLatin1String( AKONADI_FLAG_IGNORED ) ) );
Rex Dieter adf30a
-      b += " " AKONADI_PARAM_SIZE " " + QByteArray::number( itemSize ) + ' ';
Rex Dieter adf30a
+      b += QByteArray::number( stats.count - stats.read) ;
Rex Dieter adf30a
+      b += " " AKONADI_PARAM_SIZE " " + QByteArray::number( stats.size ) + ' ';
Rex Dieter adf30a
     }
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
diff --git a/server/src/handlerhelper.h b/server/src/handlerhelper.h
Rex Dieter adf30a
index 22e6e1c..cf9ac22 100644
Rex Dieter adf30a
--- a/server/src/handlerhelper.h
Rex Dieter adf30a
+++ b/server/src/handlerhelper.h
Rex Dieter adf30a
@@ -52,28 +52,6 @@ class HandlerHelper
Rex Dieter adf30a
     static QString pathForCollection( const Collection &col );
Rex Dieter adf30a
 
Rex Dieter adf30a
     /**
Rex Dieter adf30a
-      Returns the amount of existing items in the given collection.
Rex Dieter adf30a
-      @return -1 on error
Rex Dieter adf30a
-    */
Rex Dieter adf30a
-    static int itemCount( const Collection &col );
Rex Dieter adf30a
-
Rex Dieter adf30a
-    /**
Rex Dieter adf30a
-     * Queries for collection statistics.
Rex Dieter adf30a
-     * @param col The collection to query.
Rex Dieter adf30a
-     * @param count The total amount of items in this collection.
Rex Dieter adf30a
-     * @param size The size of all items in this collection.
Rex Dieter adf30a
-     * @return @c false on a query error, @c true otherwise
Rex Dieter adf30a
-     */
Rex Dieter adf30a
-    static bool itemStatistics( const Collection &col, qint64 &count, qint64 &size );
Rex Dieter adf30a
-
Rex Dieter adf30a
-    /**
Rex Dieter adf30a
-      Returns the amount of existing items in the given collection
Rex Dieter adf30a
-      which have a given flag set.
Rex Dieter adf30a
-      @return -1 on error.
Rex Dieter adf30a
-    */
Rex Dieter adf30a
-    static int itemWithFlagsCount( const Collection &col, const QStringList &flags );
Rex Dieter adf30a
-
Rex Dieter adf30a
-    /**
Rex Dieter adf30a
       Parse cache policy and update the given Collection object accoordingly.
Rex Dieter adf30a
       @param changed Indicates whether or not the cache policy already available in @p col
Rex Dieter adf30a
       has actually changed
Rex Dieter adf30a
diff --git a/server/src/storage/collectionstatistics.cpp b/server/src/storage/collectionstatistics.cpp
Rex Dieter adf30a
new file mode 100644
Rex Dieter adf30a
index 0000000..85ee449
Rex Dieter adf30a
--- /dev/null
Rex Dieter adf30a
+++ b/server/src/storage/collectionstatistics.cpp
Rex Dieter adf30a
@@ -0,0 +1,108 @@
Rex Dieter adf30a
+/*
Rex Dieter adf30a
+ * Copyright (C) 2014  Daniel Vrátil <dvratil@redhat.com>
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * This library is free software; you can redistribute it and/or
Rex Dieter adf30a
+ * modify it under the terms of the GNU Lesser General Public
Rex Dieter adf30a
+ * License as published by the Free Software Foundation; either
Rex Dieter adf30a
+ * version 2.1 of the License, or (at your option) any later version.
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * This library is distributed in the hope that it will be useful,
Rex Dieter adf30a
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
Rex Dieter adf30a
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Rex Dieter adf30a
+ * Lesser General Public License for more details.
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * You should have received a copy of the GNU Lesser General Public
Rex Dieter adf30a
+ * License along with this library; if not, write to the Free Software
Rex Dieter adf30a
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ */
Rex Dieter adf30a
+
Rex Dieter adf30a
+#include "collectionstatistics.h"
Rex Dieter adf30a
+#include "querybuilder.h"
Rex Dieter adf30a
+#include "countquerybuilder.h"
Rex Dieter adf30a
+#include "akdebug.h"
Rex Dieter adf30a
+#include "entities.h"
Rex Dieter adf30a
+
Rex Dieter adf30a
+#include <libs/protocol_p.h>
Rex Dieter adf30a
+
Rex Dieter adf30a
+#include <QDateTime>
Rex Dieter adf30a
+
Rex Dieter adf30a
+using namespace Akonadi::Server;
Rex Dieter adf30a
+
Rex Dieter adf30a
+CollectionStatistics *CollectionStatistics::sInstance = 0;
Rex Dieter adf30a
+
Rex Dieter adf30a
+CollectionStatistics* CollectionStatistics::instance()
Rex Dieter adf30a
+{
Rex Dieter adf30a
+    static QMutex lock;
Rex Dieter adf30a
+    lock.lock();
Rex Dieter adf30a
+    if (sInstance == 0) {
Rex Dieter adf30a
+        sInstance = new CollectionStatistics();
Rex Dieter adf30a
+    }
Rex Dieter adf30a
+    lock.unlock();
Rex Dieter adf30a
+    return sInstance;
Rex Dieter adf30a
+}
Rex Dieter adf30a
+
Rex Dieter adf30a
+void CollectionStatistics::invalidateCollection(const Collection &col)
Rex Dieter adf30a
+{
Rex Dieter adf30a
+    QMutexLocker lock(&mCacheLock);
Rex Dieter adf30a
+    mCache.remove(col.id());
Rex Dieter adf30a
+}
Rex Dieter adf30a
+
Rex Dieter adf30a
+const CollectionStatistics::Statistics& CollectionStatistics::statistics(const Collection &col)
Rex Dieter adf30a
+{
Rex Dieter adf30a
+    QMutexLocker lock(&mCacheLock);
Rex Dieter adf30a
+    auto it = mCache.find(col.id());
Rex Dieter adf30a
+    if (it == mCache.constEnd()) {
Rex Dieter adf30a
+        it = mCache.insert(col.id(), getCollectionStatistics(col));
Rex Dieter adf30a
+    }
Rex Dieter adf30a
+    return it.value();
Rex Dieter adf30a
+}
Rex Dieter adf30a
+
Rex Dieter adf30a
+CollectionStatistics::Statistics CollectionStatistics::getCollectionStatistics(const Collection &col)
Rex Dieter adf30a
+{
Rex Dieter adf30a
+    QueryBuilder qb(PimItem::tableName());
Rex Dieter adf30a
+    // COUNT(DISTINCT PimItemTable.id)
Rex Dieter adf30a
+    qb.addAggregation(QString::fromLatin1("DISTINCT %1")
Rex Dieter adf30a
+                          .arg(PimItem::idFullColumnName()),
Rex Dieter adf30a
+                      QLatin1String("count"));
Rex Dieter adf30a
+    // SUM(PimItemTable.size)
Rex Dieter adf30a
+    qb.addAggregation(PimItem::sizeFullColumnName(), QLatin1String("sum"));
Rex Dieter adf30a
+    // SUM(CASE WHEN FlagTable.name IN ('\SEEN', '$IGNORED') THEN 1 ELSE 0 END)
Rex Dieter adf30a
+    // This allows us to get read messages count in a single query with the other
Rex Dieter adf30a
+    // statistics. It is much than doing two queries, because the database
Rex Dieter adf30a
+    // only has to calculate the JOINs once.
Rex Dieter adf30a
+    //
Rex Dieter adf30a
+    // Flag::retrieveByName() will hit the Entity cache, which allows us to avoid
Rex Dieter adf30a
+    // a second JOIN with FlagTable, which PostgreSQL seems to struggle to optimize.
Rex Dieter adf30a
+    Query::Condition cond(Query::Or);
Rex Dieter adf30a
+    cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(),
Rex Dieter adf30a
+                           Query::Equals,
Rex Dieter adf30a
+                           Flag::retrieveByName(QLatin1String(AKONADI_FLAG_SEEN)).id());
Rex Dieter adf30a
+    cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(),
Rex Dieter adf30a
+                           Query::Equals,
Rex Dieter adf30a
+                           Flag::retrieveByName(QLatin1String(AKONADI_FLAG_IGNORED)).id());
Rex Dieter adf30a
+    Query::Case caseStmt(cond, QLatin1String("1"), QLatin1String("0"));
Rex Dieter adf30a
+    qb.addAggregation(caseStmt, QLatin1String("sum"));
Rex Dieter adf30a
+
Rex Dieter adf30a
+    qb.addJoin(QueryBuilder::LeftJoin, PimItemFlagRelation::tableName(),
Rex Dieter adf30a
+               PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName());
Rex Dieter adf30a
+    if (col.isVirtual()) {
Rex Dieter adf30a
+        qb.addJoin(QueryBuilder::InnerJoin, CollectionPimItemRelation::tableName(),
Rex Dieter adf30a
+                   CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName());
Rex Dieter adf30a
+        qb.addValueCondition(CollectionPimItemRelation::leftFullColumnName(), Query::Equals, col.id());
Rex Dieter adf30a
+    } else {
Rex Dieter adf30a
+        qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, col.id());
Rex Dieter adf30a
+    }
Rex Dieter adf30a
+
Rex Dieter adf30a
+    if (!qb.exec()) {
Rex Dieter adf30a
+        return { -1, -1, -1 };
Rex Dieter adf30a
+    }
Rex Dieter adf30a
+    if (!qb.query().next()) {
Rex Dieter adf30a
+        akError() << "Error during retrieving result of statistics query:" << qb.query().lastError().text();
Rex Dieter adf30a
+        return { -1, -1, -1 };
Rex Dieter adf30a
+    }
Rex Dieter adf30a
+
Rex Dieter adf30a
+    return { qb.query().value(0).toLongLong(),
Rex Dieter adf30a
+             qb.query().value(1).toLongLong(),
Rex Dieter adf30a
+             qb.query().value(2).toLongLong() };
Rex Dieter adf30a
+}
Rex Dieter adf30a
diff --git a/server/src/storage/collectionstatistics.h b/server/src/storage/collectionstatistics.h
Rex Dieter adf30a
new file mode 100644
Rex Dieter adf30a
index 0000000..2c0af6a
Rex Dieter adf30a
--- /dev/null
Rex Dieter adf30a
+++ b/server/src/storage/collectionstatistics.h
Rex Dieter adf30a
@@ -0,0 +1,70 @@
Rex Dieter adf30a
+/*
Rex Dieter adf30a
+ * Copyright (C) 2014  Daniel Vrátil <dvratil@redhat.com>
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * This library is free software; you can redistribute it and/or
Rex Dieter adf30a
+ * modify it under the terms of the GNU Lesser General Public
Rex Dieter adf30a
+ * License as published by the Free Software Foundation; either
Rex Dieter adf30a
+ * version 2.1 of the License, or (at your option) any later version.
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * This library is distributed in the hope that it will be useful,
Rex Dieter adf30a
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
Rex Dieter adf30a
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Rex Dieter adf30a
+ * Lesser General Public License for more details.
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * You should have received a copy of the GNU Lesser General Public
Rex Dieter adf30a
+ * License along with this library; if not, write to the Free Software
Rex Dieter adf30a
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ */
Rex Dieter adf30a
+
Rex Dieter adf30a
+#ifndef AKONADI_SERVER_COLLECTIONSTATISTICS_H
Rex Dieter adf30a
+#define AKONADI_SERVER_COLLECTIONSTATISTICS_H
Rex Dieter adf30a
+
Rex Dieter adf30a
+class QMutex;
Rex Dieter adf30a
+
Rex Dieter adf30a
+#include <QHash>
Rex Dieter adf30a
+#include <QMutex>
Rex Dieter adf30a
+
Rex Dieter adf30a
+namespace Akonadi {
Rex Dieter adf30a
+namespace Server {
Rex Dieter adf30a
+
Rex Dieter adf30a
+class Collection;
Rex Dieter adf30a
+
Rex Dieter adf30a
+/**
Rex Dieter adf30a
+ * Provides cache for collection statistics
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * Collection statistics are requested very often, so to take some load from the
Rex Dieter adf30a
+ * database we cache the results until the statistics are invalidated (see
Rex Dieter adf30a
+ * NotificationCollector, which takes care for invalidating the statistics).
Rex Dieter adf30a
+ *
Rex Dieter adf30a
+ * The cache (together with optimization of the actual SQL query) seems to
Rex Dieter adf30a
+ * massively improve initial folder listing on system start (when IO and CPU loads
Rex Dieter adf30a
+ * are very high).
Rex Dieter adf30a
+ */
Rex Dieter adf30a
+class CollectionStatistics
Rex Dieter adf30a
+{
Rex Dieter adf30a
+public:
Rex Dieter adf30a
+    struct Statistics {
Rex Dieter adf30a
+        qint64 count;
Rex Dieter adf30a
+        qint64 size;
Rex Dieter adf30a
+        qint64 read;
Rex Dieter adf30a
+    };
Rex Dieter adf30a
+
Rex Dieter adf30a
+    static CollectionStatistics* instance();
Rex Dieter adf30a
+
Rex Dieter adf30a
+    const Statistics& statistics(const Collection &col);
Rex Dieter adf30a
+    void invalidateCollection(const Collection &col);
Rex Dieter adf30a
+
Rex Dieter adf30a
+private:
Rex Dieter adf30a
+    Statistics getCollectionStatistics(const Collection &col);
Rex Dieter adf30a
+
Rex Dieter adf30a
+    QMutex mCacheLock;
Rex Dieter adf30a
+    QHash<qint64, Statistics> mCache;
Rex Dieter adf30a
+
Rex Dieter adf30a
+    static CollectionStatistics *sInstance;
Rex Dieter adf30a
+};
Rex Dieter adf30a
+
Rex Dieter adf30a
+} // namespace Server
Rex Dieter adf30a
+} // namespace Akonadi
Rex Dieter adf30a
+
Rex Dieter adf30a
+#endif // AKONADI_SERVER_COLLECTIONSTATISTICS_H
Rex Dieter adf30a
diff --git a/server/src/storage/datastore.cpp b/server/src/storage/datastore.cpp
Rex Dieter adf30a
index 304f0e8..0983d84 100644
Rex Dieter adf30a
--- a/server/src/storage/datastore.cpp
Rex Dieter adf30a
+++ b/server/src/storage/datastore.cpp
Rex Dieter adf30a
@@ -209,7 +209,7 @@ DataStore *DataStore::self()
Rex Dieter adf30a
 /* --- ItemFlags ----------------------------------------------------- */
Rex Dieter adf30a
 
Rex Dieter adf30a
 bool DataStore::setItemsFlags( const PimItem::List &items, const QVector<Flag> &flags,
Rex Dieter adf30a
-                               bool *flagsChanged, bool silent )
Rex Dieter adf30a
+                               bool *flagsChanged, const Collection &col, bool silent )
Rex Dieter adf30a
 {
Rex Dieter adf30a
   QSet<QByteArray> removedFlags;
Rex Dieter adf30a
   QSet<QByteArray> addedFlags;
Rex Dieter adf30a
@@ -258,7 +258,7 @@ bool DataStore::setItemsFlags( const PimItem::List &items, const QVector<Flag> &
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
   if ( !silent && ( !addedFlags.isEmpty() || !removedFlags.isEmpty() ) ) {
Rex Dieter adf30a
-    mNotificationCollector->itemsFlagsChanged( items, addedFlags, removedFlags );
Rex Dieter adf30a
+    mNotificationCollector->itemsFlagsChanged( items, addedFlags, removedFlags, col );
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
   setBoolPtr( flagsChanged, ( addedFlags != removedFlags ) );
Rex Dieter adf30a
@@ -361,7 +361,7 @@ bool DataStore::appendItemsFlags( const PimItem::List &items, const QVector
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
 bool DataStore::removeItemsFlags( const PimItem::List &items, const QVector<Flag> &flags,
Rex Dieter adf30a
-                                  bool *flagsChanged, bool silent )
Rex Dieter adf30a
+                                  bool *flagsChanged, const Collection &col, bool silent )
Rex Dieter adf30a
 {
Rex Dieter adf30a
   QSet<QByteArray> removedFlags;
Rex Dieter adf30a
   QVariantList itemsIds;
Rex Dieter adf30a
@@ -393,7 +393,7 @@ bool DataStore::removeItemsFlags( const PimItem::List &items, const QVector
Rex Dieter adf30a
   if ( qb.query().numRowsAffected() != 0 ) {
Rex Dieter adf30a
     setBoolPtr( flagsChanged, true );
Rex Dieter adf30a
     if ( !silent ) {
Rex Dieter adf30a
-      mNotificationCollector->itemsFlagsChanged( items, QSet<QByteArray>(), removedFlags );
Rex Dieter adf30a
+      mNotificationCollector->itemsFlagsChanged( items, QSet<QByteArray>(), removedFlags, col );
Rex Dieter adf30a
     }
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
diff --git a/server/src/storage/datastore.h b/server/src/storage/datastore.h
Rex Dieter adf30a
index 395b227..a2d8a42 100644
Rex Dieter adf30a
--- a/server/src/storage/datastore.h
Rex Dieter adf30a
+++ b/server/src/storage/datastore.h
Rex Dieter adf30a
@@ -119,10 +119,12 @@ class DataStore : public QObject
Rex Dieter adf30a
     static DataStore *self();
Rex Dieter adf30a
 
Rex Dieter adf30a
     /* --- ItemFlags ----------------------------------------------------- */
Rex Dieter adf30a
-    virtual bool setItemsFlags( const PimItem::List &items, const QVector<Flag> &flags, bool *flagsChanged = 0, bool silent = false );
Rex Dieter adf30a
+    virtual bool setItemsFlags( const PimItem::List &items, const QVector<Flag> &flags,
Rex Dieter adf30a
+                                bool *flagsChanged = 0, const Collection &col = Collection(), bool silent = false );
Rex Dieter adf30a
     virtual bool appendItemsFlags( const PimItem::List &items, const QVector<Flag> &flags, bool *flagsChanged = 0,
Rex Dieter adf30a
                                    bool checkIfExists = true, const Collection &col = Collection(), bool silent = false );
Rex Dieter adf30a
-    virtual bool removeItemsFlags( const PimItem::List &items, const QVector<Flag> &flags, bool *tagsChanged = 0, bool silent = false );
Rex Dieter adf30a
+    virtual bool removeItemsFlags( const PimItem::List &items, const QVector<Flag> &flags, bool *tagsChanged = 0,
Rex Dieter adf30a
+                                   const Collection &collection = Collection(), bool silent = false );
Rex Dieter adf30a
 
Rex Dieter adf30a
     /* --- ItemTags ----------------------------------------------------- */
Rex Dieter adf30a
     virtual bool setItemsTags( const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = 0, bool silent = false );
Rex Dieter adf30a
diff --git a/server/src/storage/notificationcollector.cpp b/server/src/storage/notificationcollector.cpp
Rex Dieter adf30a
index 67f57d1..dbc7883 100644
Rex Dieter adf30a
--- a/server/src/storage/notificationcollector.cpp
Rex Dieter adf30a
+++ b/server/src/storage/notificationcollector.cpp
Rex Dieter adf30a
@@ -20,6 +20,7 @@
Rex Dieter adf30a
 #include "notificationcollector.h"
Rex Dieter adf30a
 #include "storage/datastore.h"
Rex Dieter adf30a
 #include "storage/entity.h"
Rex Dieter adf30a
+#include "storage/collectionstatistics.h"
Rex Dieter adf30a
 #include "handlerhelper.h"
Rex Dieter adf30a
 #include "cachecleaner.h"
Rex Dieter adf30a
 #include "intervalcheck.h"
Rex Dieter adf30a
@@ -133,6 +134,7 @@ void NotificationCollector::collectionChanged( const Collection &collection,
Rex Dieter adf30a
   if ( AkonadiServer::instance()->intervalChecker() ) {
Rex Dieter adf30a
     AkonadiServer::instance()->intervalChecker()->collectionAdded( collection.id() );
Rex Dieter adf30a
   }
Rex Dieter adf30a
+  CollectionStatistics::instance()->invalidateCollection(collection);
Rex Dieter adf30a
   collectionNotification( NotificationMessageV2::Modify, collection, collection.parentId(), -1, resource, changes.toSet() );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
@@ -159,6 +161,8 @@ void NotificationCollector::collectionRemoved( const Collection &collection,
Rex Dieter adf30a
   if ( AkonadiServer::instance()->intervalChecker() ) {
Rex Dieter adf30a
     AkonadiServer::instance()->intervalChecker()->collectionRemoved( collection.id() );
Rex Dieter adf30a
   }
Rex Dieter adf30a
+  CollectionStatistics::instance()->invalidateCollection(collection);
Rex Dieter adf30a
+
Rex Dieter adf30a
   collectionNotification( NotificationMessageV2::Remove, collection, collection.parentId(), -1, resource );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
@@ -183,6 +187,8 @@ void NotificationCollector::collectionUnsubscribed( const Collection &collection
Rex Dieter adf30a
   if ( AkonadiServer::instance()->intervalChecker() ) {
Rex Dieter adf30a
     AkonadiServer::instance()->intervalChecker()->collectionRemoved( collection.id() );
Rex Dieter adf30a
   }
Rex Dieter adf30a
+  CollectionStatistics::instance()->invalidateCollection(collection);
Rex Dieter adf30a
+
Rex Dieter adf30a
   collectionNotification( NotificationMessageV2::Unsubscribe, collection, collection.parentId(), -1, resource, QSet<QByteArray>() );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
@@ -282,6 +288,7 @@ void NotificationCollector::itemNotification( NotificationMessageV2::Operation o
Rex Dieter adf30a
     copy.setParentCollection( iter.key() );
Rex Dieter adf30a
     copy.setResource( resource );
Rex Dieter adf30a
 
Rex Dieter adf30a
+    CollectionStatistics::instance()->invalidateCollection(Collection::retrieveById(iter.key()));
Rex Dieter adf30a
     dispatchNotification( copy );
Rex Dieter adf30a
   }
Rex Dieter adf30a
 
Rex Dieter adf30a
@@ -304,6 +311,7 @@ void NotificationCollector::itemNotification( NotificationMessageV2::Operation o
Rex Dieter adf30a
   }
Rex Dieter adf30a
   msg.setResource( res );
Rex Dieter adf30a
 
Rex Dieter adf30a
+  CollectionStatistics::instance()->invalidateCollection(col);
Rex Dieter adf30a
   dispatchNotification( msg );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
diff --git a/server/tests/unittest/fakedatastore.cpp b/server/tests/unittest/fakedatastore.cpp
Rex Dieter adf30a
index 12214fa..43ef7e6 100644
Rex Dieter adf30a
--- a/server/tests/unittest/fakedatastore.cpp
Rex Dieter adf30a
+++ b/server/tests/unittest/fakedatastore.cpp
Rex Dieter adf30a
@@ -91,13 +91,15 @@ bool FakeDataStore::init()
Rex Dieter adf30a
 bool FakeDataStore::setItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
                                    const QVector<Flag> &flags,
Rex Dieter adf30a
                                    bool *flagsChanged,
Rex Dieter adf30a
+                                   const Collection &col,
Rex Dieter adf30a
                                    bool silent )
Rex Dieter adf30a
 {
Rex Dieter adf30a
   mChanges.insert( QLatin1String( "setItemsFlags" ),
Rex Dieter adf30a
                    QVariantList() << QVariant::fromValue( items )
Rex Dieter adf30a
                                   << QVariant::fromValue( flags )
Rex Dieter adf30a
+                                  << QVariant::fromValue( col )
Rex Dieter adf30a
                                   << silent );
Rex Dieter adf30a
-  return DataStore::setItemsFlags( items, flags, flagsChanged, silent );
Rex Dieter adf30a
+  return DataStore::setItemsFlags( items, flags, flagsChanged, col, silent );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
 bool FakeDataStore::appendItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
@@ -119,13 +121,15 @@ bool FakeDataStore::appendItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
 bool FakeDataStore::removeItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
                                       const QVector<Flag> &flags,
Rex Dieter adf30a
                                       bool *flagsChanged,
Rex Dieter adf30a
+                                      const Collection &col,
Rex Dieter adf30a
                                       bool silent )
Rex Dieter adf30a
 {
Rex Dieter adf30a
   mChanges.insert( QLatin1String( "removeItemsFlags" ),
Rex Dieter adf30a
                    QVariantList() << QVariant::fromValue( items )
Rex Dieter adf30a
                                   << QVariant::fromValue( flags )
Rex Dieter adf30a
+                                  << QVariant::fromValue( col )
Rex Dieter adf30a
                                   << silent );
Rex Dieter adf30a
-  return DataStore::removeItemsFlags( items, flags, flagsChanged, silent );
Rex Dieter adf30a
+  return DataStore::removeItemsFlags( items, flags, flagsChanged, col, silent );
Rex Dieter adf30a
 }
Rex Dieter adf30a
 
Rex Dieter adf30a
 
Rex Dieter adf30a
diff --git a/server/tests/unittest/fakedatastore.h b/server/tests/unittest/fakedatastore.h
Rex Dieter adf30a
index 62c5b75..cd9ab13 100644
Rex Dieter adf30a
--- a/server/tests/unittest/fakedatastore.h
Rex Dieter adf30a
+++ b/server/tests/unittest/fakedatastore.h
Rex Dieter adf30a
@@ -41,6 +41,7 @@ class FakeDataStore: public DataStore
Rex Dieter adf30a
     virtual bool setItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
                                 const QVector<Flag> &flags,
Rex Dieter adf30a
                                 bool *flagsChanged = 0,
Rex Dieter adf30a
+                                const Collection &col = Collection(),
Rex Dieter adf30a
                                 bool silent = false );
Rex Dieter adf30a
     virtual bool appendItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
                                    const QVector<Flag> &flags,
Rex Dieter adf30a
@@ -51,6 +52,7 @@ class FakeDataStore: public DataStore
Rex Dieter adf30a
     virtual bool removeItemsFlags( const PimItem::List &items,
Rex Dieter adf30a
                                    const QVector<Flag> &flags,
Rex Dieter adf30a
                                    bool *flagsChanged = 0,
Rex Dieter adf30a
+                                   const Collection &col = Collection(),
Rex Dieter adf30a
                                    bool silent = false );
Rex Dieter adf30a
 
Rex Dieter adf30a
     virtual bool setItemsTags( const PimItem::List &items,
Rex Dieter adf30a
-- 
Rex Dieter adf30a
2.1.0
Rex Dieter adf30a