Compare commits

...
Sign in to create a new pull request.

78 commits

Author SHA1 Message Date
Fedora Release Engineering
f93f842792 Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild 2026-01-16 03:32:46 +00:00
Fedora Release Engineering
04d7652051 Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild 2025-07-23 16:51:21 +00:00
Fedora Release Engineering
d5433cf6e8 Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild 2025-01-16 10:46:32 +00:00
Fedora Release Engineering
b94e5a0e93 Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild 2024-07-17 16:46:09 +00:00
Fedora Release Engineering
547cef98b9 Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild 2024-01-22 22:49:46 +00:00
Fedora Release Engineering
2bcdc9e18b Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild 2024-01-19 12:29:49 +00:00
Fedora Release Engineering
aefd5236d8 Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2023-07-19 13:03:56 +00:00
Than Ngo
8e0da1155d migrated to SPDX license 2023-06-12 12:12:13 +02:00
Fedora Release Engineering
3e10cdbabe Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2023-01-18 21:32:30 +00:00
Fedora Release Engineering
bad5a7962c Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2022-07-20 20:35:13 +00:00
Fedora Release Engineering
24bd5578bf - Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2022-01-19 21:04:02 +00:00
Fedora Release Engineering
e513ef09a9 - Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2021-07-21 17:23:36 +00:00
Fedora Release Engineering
1c611726ac - Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2021-07-21 12:28:20 +00:00
Fedora Release Engineering
1d79ef60cf - Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2021-01-25 23:56:12 +00:00
Fedora Release Engineering
e0c683030d - Second attempt - Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2020-07-31 23:53:20 +00:00
Fedora Release Engineering
b53992e7f4 - Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2020-07-27 11:52:34 +00:00
Neal Gompa
71184c15cb Update to new out-of-source build mechanism 2020-07-05 14:46:26 -04:00
Fedora Release Engineering
d4cb712128 - Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2020-01-28 11:21:56 +00:00
Fedora Release Engineering
5a7644bcb2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2019-07-24 17:42:27 +00:00
Fedora Release Engineering
5f516d08a4 - Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2019-01-31 13:07:51 +00:00
Jonathan Wakely
e63e99f1ef Rebuilt for Boost 1.69 2019-01-25 08:13:03 +00:00
Fedora Release Engineering
442c71f0ec - Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2018-07-12 20:05:38 +00:00
Rex Dieter
c183a6e345 BR: gcc-c++, use %ldconfig_scriptlets 2018-02-20 15:47:13 -06:00
Fedora Release Engineering
070820a23d - Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
2018-02-07 02:02:35 +00:00
Jonathan Wakely
e0386f5e78 Rebuilt for Boost 1.66 2018-01-23 12:01:43 +00:00
Björn Esser
99916b63be Rebuilt for AutoReq cmake-filesystem 2017-08-06 11:28:59 +02:00
Fedora Release Engineering
80b6168b8d - Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild 2017-08-02 17:28:23 +00:00
Fedora Release Engineering
b8f85d80a1 - Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild 2017-07-26 02:42:05 +00:00
Jonathan Wakely
13aaf3464f Rebuilt for Boost 1.64 2017-07-18 12:13:23 +01:00
Fedora Release Engineering
cba6ce5ec1 - Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild 2017-02-10 05:50:28 +00:00
Dennis Gilmore
2981736d02 - Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild 2016-02-03 16:04:06 +00:00
Rex Dieter
80bf6d98ef devel: re-enable dbus-1/interfaces, Conflicts: kf5-akonadi-server-devel 2015-12-17 13:45:30 -06:00
Rex Dieter
04ca9f646e for kf5 kdepim world, build libakonadi bits only (omit server and related files) 2015-12-11 08:38:19 -06:00
Rex Dieter
21ec943481 Recommends: akonadi-mysql 2015-11-12 10:29:52 -06:00
Jonathan Wakely
b97ca23711 Rebuilt for Boost 1.59 2015-08-30 17:39:19 +01:00
Rex Dieter
7e082edf72 Revert "15.08.0"
This reverts commit 36b47e46be.

moving to qt5 pim stack will be a lot more work, not ready till
at least sometime next week.
2015-08-30 11:30:12 -05:00
Than Ngo
36b47e46be 15.08.0 2015-08-20 22:28:40 +02:00
Jonathan Wakely
258eaca3ec Rebuilt for Boost 1.58 2015-08-05 19:35:04 +01:00
Rex Dieter
8b4426d5d3 pull in latest 1.13 branch fixes 2015-08-02 13:38:23 -05:00
Dennis Gilmore
6505b31d44 - Rebuilt for https://fedoraproject.org/wiki/Changes/F23Boost159 2015-07-29 11:57:10 -05:00
David Tardon
eb7406d932 rebuild for Boost 1.58 2015-07-22 18:03:59 +02:00
Dan Vrátil
62a3cba7d7 Pull upstream fix for KDE#341884 2015-06-29 23:03:33 +02:00
Dennis Gilmore
8942b654a7 - Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild 2015-06-16 23:44:25 +00:00
Rex Dieter
bc3af2d35c %build: explicitly set -DCMAKE_BUILD_TYPE="Release" (-DQT_NO_DEBUG was used already) 2015-05-07 13:47:35 -05:00
Dan Vrátil
3845bc69ad Rebuild for boost 2015-04-27 15:04:52 +02:00
Rex Dieter
3ceac1733c use Recommends for f21 too 2015-04-11 18:36:53 -05:00
Rex Dieter
c9783c242f 1.13.0-12
- explicit BuildRequires: mariadb-server
- -mysql: Recommends: mariadb-server (f22+, #1199797)
2015-03-08 15:00:57 -05:00
Rex Dieter
a4f5b0576f explicitly Requires: mariadb(-sesrver) only as needed (#1199797) 2015-03-08 14:17:39 -05:00
Rex Dieter
8b319ccb4c rebuild (gcc5) 2015-02-18 09:02:17 -06:00
Petr Machata
e63d049f49 Bump for rebuild. 2015-02-04 14:09:07 +01:00
Rex Dieter
adf30afcb0 latest 1.13 branch fixes 2015-01-31 22:05:52 -06:00
Petr Machata
ea4f267da4 Rebuild for boost 1.57.0 2015-01-27 18:02:57 +01:00
Rex Dieter
f8437d9b98 drop el6/cmake hacks 2015-01-08 08:58:11 -06:00
Rex Dieter
8acc3d6ce7 latest 1.13 branch fixes 2014-10-31 08:02:57 -05:00
Rex Dieter
002028f889 explicitly Requires: mariadb-server/mysql-server as appropriate 2014-09-27 16:50:06 -05:00
Rex Dieter
b80c244f30 explicitly s/mysql-server/mariadb-server/ 2014-09-27 16:46:27 -05:00
Rex Dieter
a87e2990ff pull in some upstream fixes 2014-09-15 21:23:49 -05:00
Rex Dieter
338a460da5 no soprano for f20 now too (where kde-4.13 landed recently) 2014-09-04 12:58:37 -05:00
Peter Robinson
5c1d74f712 - Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild 2014-08-15 20:14:12 +00:00
Rex Dieter
baf6f13ced 1.13.0 2014-08-14 10:47:09 -05:00
Rex Dieter
04b405e175 1.12.91 2014-08-04 18:39:05 -05:00
Rex Dieter
65d3207aae scriptlet polish 2014-07-08 13:10:40 -05:00
Rex Dieter
e9c8aa0fe7 safer version 2014-07-03 13:21:39 -05:00
Rex Dieter
12eb55c0c8 optimized mimeinfo scriptlet 2014-07-03 12:19:35 -05:00
Rex Dieter
78fb1bf7ea pull in latest 1.12 branch commits 2014-06-09 20:46:44 -05:00
Dennis Gilmore
6b7be2b3ab - Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild 2014-06-06 19:21:48 -05:00
Petr Machata
7170ac89c4 Rebuild for boost 1.55.0 2014-05-22 20:36:13 +02:00
Dan Vrátil
660e892d22 Backport 1.12 commit to fix upgrade from Akonadi<1.12 for users with invalid DB entries 2014-04-22 11:40:30 +02:00
Rex Dieter
fcd69dcca6 backport master/ branch commits to test sqlite backend concurrency support 2014-04-16 09:23:26 -05:00
Rex Dieter
fdc262e626 WITH_SOPRANO=OFF (kde-4.13,fc21+) 2014-04-16 06:55:05 -05:00
Rex Dieter
215b266c1a 1.12.1-2
- drop mysql-global-mobile.conf, it's too minimalistic
- drop Requires: qt4 >= 4.8.5-10 (workaround for psql driver bug had only a small window a long time ago)
2014-04-15 08:44:48 -05:00
Rex Dieter
9fdf2bb2a7 1.12.1 2014-04-08 20:59:14 -05:00
Rex Dieter
998c8cf861 1.12.0 2014-03-26 21:20:26 -05:00
Dan Vrátil
ddb5148507 1.11.90 2014-03-19 18:21:01 +01:00
Rex Dieter
00571966d4 1.11.80 2014-03-17 13:44:59 -05:00
Rex Dieter
8a132797b8 rebuild 2014-02-03 17:23:20 -06:00
Rex Dieter
6fef9757f6 1.11.0 2013-11-30 21:17:45 -06:00
Rex Dieter
5e14a94748 1.10.80 2013-11-15 10:57:10 -06:00
38 changed files with 4579 additions and 153 deletions

5
.gitignore vendored
View file

@ -1,4 +1 @@
/akonadi-1.9.2.tar.bz2
/akonadi-1.10.1.tar.bz2
/akonadi-1.10.2.tar.bz2
/akonadi-1.10.3-1.tar.bz2
/akonadi-1.13.0.tar.bz2

View file

@ -0,0 +1,38 @@
From b60702e0b7041c56a3cb52c209204d28406f3ce5 Mon Sep 17 00:00:00 2001
From: Raphael Kubo da Costa <rakuco@FreeBSD.org>
Date: Wed, 13 Aug 2014 14:43:04 +0300
Subject: [PATCH 01/30] FindSqlite: Use CMAKE_FLAGS the right way in
try_compile().
This fixes f90774f1 ("Check whether Sqlite is compiled with
SQLITE_ENABLE_UNLOCK_NOTIFY"), so that SQLITE_INCLUDE_DIR is really
passed to the try_compile() call. So far, it was just a NOP and the
compilation only worked if sqlite3.h was in a directory the compiler
uses automatically.
try_compile()'s syntax is a bit complicated, and CMAKE_FLAGS expects a
series of arguments as if they had been passed to the command line, so
instead of "CMAKE_FLAGS INCLUDE_DIRECTORIES /some/dir" one needs to use
"CMAKE_FLAGS -DINCLUDE_DIRECTORIES:PATH=/some/dir".
REVIEW: 119762
---
cmake/modules/FindSqlite.cmake | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmake/modules/FindSqlite.cmake b/cmake/modules/FindSqlite.cmake
index ad8cdb4..c43a7b5 100644
--- a/cmake/modules/FindSqlite.cmake
+++ b/cmake/modules/FindSqlite.cmake
@@ -94,7 +94,7 @@ if(EXISTS ${SQLITE_INCLUDE_DIR}/sqlite3.h)
${CMAKE_BINARY_DIR}/sqlite_check_unlock_notify
${CMAKE_BINARY_DIR}/sqlite_check_unlock_notify.cpp
LINK_LIBRARIES ${SQLITE_LIBRARIES}
- CMAKE_FLAGS INCLUDE_DIRECTORIES ${SQLITE_INCLUDE_DIR})
+ CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:PATH=${SQLITE_INCLUDE_DIR}")
if (NOT SQLITE_HAS_UNLOCK_NOTIFY)
message(STATUS "Sqlite ${SQLITE_VERSION} was found, but it is not compiled with -DSQLITE_ENABLE_UNLOCK_NOTIFY")
endif()
--
2.1.0

View file

@ -0,0 +1,82 @@
From 2146519108ec66300328b7b3979477c7789795d3 Mon Sep 17 00:00:00 2001
From: Raphael Kubo da Costa <rakuco@FreeBSD.org>
Date: Wed, 13 Aug 2014 23:22:11 +0300
Subject: [PATCH 02/30] Do not enter the test/ directories if
AKONADI_BUILD_TESTS is off.
enable_testing() only determines whether a "test" target and the related
CTest files will be created. And in Akonadi's case it is actually
invoked regardless of the value of the AKONADI_BUILD_TESTS option
because Akonadi includes the CTest module, which calls enable_testing()
based on the value of another variable, BUILD_TESTING.
In any case, whether the executables and libraries that compose
Akonadi's unit tests will be built has nothing to do with
enable_testing(). To make AKONADI_BUILD_TESTS really disable the build
of the unit tests we now avoid entering the tests/ directories at all
when it is off, so that neither tests nor targets they depend on get
built.
REVIEW: 119776
---
CMakeLists.txt | 6 +-----
libs/CMakeLists.txt | 4 +++-
server/CMakeLists.txt | 5 ++++-
3 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c52009..e081d23 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,7 +14,7 @@ include(FeatureSummary)
############### Build Options ###############
-include(CTest)
+include(CTest) # Calls enable_testing().
include(CTestConfig.cmake)
option(AKONADI_BUILD_TESTS "Build the Akonadi unit tests." TRUE)
option(AKONADI_BUILD_QSQLITE "Build the Sqlite backend." TRUE)
@@ -27,10 +27,6 @@ if(NOT DEFINED DATABASE_BACKEND)
set(DATABASE_BACKEND "MYSQL" CACHE STRING "The default database backend to use for Akonadi. Can be either MYSQL, POSTGRES or SQLITE")
endif()
-if(AKONADI_BUILD_TESTS)
- enable_testing()
-endif()
-
############### CMake Macros ###############
include(InstallSettings)
diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt
index de6ab0d..74de6b2 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -36,5 +36,7 @@ install(FILES
DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/private
)
-add_subdirectory(tests)
+if(AKONADI_BUILD_TESTS)
+ add_subdirectory(tests)
+endif()
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
index e4829f3..275938d 100644
--- a/server/CMakeLists.txt
+++ b/server/CMakeLists.txt
@@ -64,7 +64,10 @@ endmacro()
add_subdirectory(akonadictl)
add_subdirectory(control)
add_subdirectory(src)
-add_subdirectory(tests)
+
+if(AKONADI_BUILD_TESTS)
+ add_subdirectory(tests)
+endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_ENABLE_EXCEPTIONS}")
if(MYSQLD_EXECUTABLE)
--
2.1.0

View file

@ -0,0 +1,201 @@
From 9734074267bacd39aeb29c7a0d7df7cadb212d89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 11 Jul 2014 18:33:39 +0200
Subject: [PATCH 03/30] STORE: Allow modifying items tags via Tag RID or GID
Tags RID is of course allowed only to resources
(Cherry-picked from 1a619d4df010a4862621a03031176ad8759070d3)
Conflicts:
CMakeLists.txt
---
libs/protocol_p.h | 2 ++
server/src/handler/store.cpp | 76 ++++++++++++++++++++++++++++++--------------
server/src/handler/store.h | 8 +++--
server/src/handlerhelper.cpp | 4 +--
4 files changed, 62 insertions(+), 28 deletions(-)
diff --git a/libs/protocol_p.h b/libs/protocol_p.h
index 002abe4..2ec2a2e 100644
--- a/libs/protocol_p.h
+++ b/libs/protocol_p.h
@@ -110,6 +110,7 @@
#define AKONADI_PARAM_TAGS "TAGS"
#define AKONADI_PARAM_FULLPAYLOAD "FULLPAYLOAD"
#define AKONADI_PARAM_GID "GID"
+#define AKONADI_PARAM_GTAGS "GTAGS"
#define AKONADI_PARAM_IGNOREERRORS "IGNOREERRORS"
#define AKONADI_PARAM_INDEX "INDEX"
#define AKONADI_PARAM_INHERIT "INHERIT"
@@ -137,6 +138,7 @@
#define AKONADI_PARAM_REMOTEREVISION "REMOTEREVISION"
#define AKONADI_PARAM_RESOURCE "RESOURCE"
#define AKONADI_PARAM_REVISION "REV"
+#define AKONADI_PARAM_RTAGS "RTAGS"
#define AKONADI_PARAM_SILENT "SILENT"
#define AKONADI_PARAM_DOT_SILENT ".SILENT"
#define AKONADI_PARAM_CAPABILITY_SERVERSEARCH "SERVERSEARCH"
diff --git a/server/src/handler/store.cpp b/server/src/handler/store.cpp
index 6664a09..4a503a2 100644
--- a/server/src/handler/store.cpp
+++ b/server/src/handler/store.cpp
@@ -115,35 +115,56 @@ bool Store::deleteFlags( const PimItem::List &items, const QVector<QByteArray> &
return true;
}
-bool Store::replaceTags( const PimItem::List &item, const ImapSet &tags )
+bool Store::replaceTags( const PimItem::List &item, const Tag::List &tags )
{
- const Tag::List tagList = HandlerHelper::resolveTags( tags );
- if ( !connection()->storageBackend()->setItemsTags( item, tagList ) ) {
+ if ( !connection()->storageBackend()->setItemsTags( item, tags ) ) {
throw HandlerException( "Store::replaceTags: Unable to set new item tags" );
}
return true;
}
-bool Store::addTags( const PimItem::List &items, const ImapSet &tags, bool &tagsChanged )
+bool Store::addTags( const PimItem::List &items, const Tag::List &tags, bool &tagsChanged )
{
- const Tag::List tagList = HandlerHelper::resolveTags( tags );
- if ( !connection()->storageBackend()->appendItemsTags( items, tagList, &tagsChanged ) ) {
+ if ( !connection()->storageBackend()->appendItemsTags( items, tags, &tagsChanged ) ) {
akDebug() << "Store::addTags: Unable to add new item tags";
return false;
}
return true;
}
-bool Store::deleteTags( const PimItem::List &items, const ImapSet &tags )
+bool Store::deleteTags( const PimItem::List &items, const Tag::List &tags )
{
- const Tag::List tagList = HandlerHelper::resolveTags( tags );
- if ( !connection()->storageBackend()->removeItemsTags( items, tagList ) ) {
+ if ( !connection()->storageBackend()->removeItemsTags( items, tags ) ) {
akDebug() << "Store::deleteTags: Unable to remove item tags";
return false;
}
return true;
}
+bool Store::processTagsChange( Store::Operation op, const PimItem::List &items,
+ const Tag::List &tags, QSet<QByteArray> &changes )
+{
+ bool tagsChanged = true;
+ if ( op == Replace ) {
+ tagsChanged = replaceTags( items, tags );
+ } else if ( op == Add ) {
+ if ( !addTags( items, tags, tagsChanged ) ) {
+ return failureResponse( "Unable to add item tags." );
+ }
+ } else if ( op == Delete ) {
+ if ( !( tagsChanged = deleteTags( items, tags ) ) ) {
+ return failureResponse( "Unable to remove item tags." );
+ }
+ }
+
+ if ( tagsChanged && !changes.contains( AKONADI_PARAM_TAGS ) ) {
+ changes << AKONADI_PARAM_TAGS;
+ }
+
+ return true;
+}
+
+
bool Store::parseStream()
{
parseCommand();
@@ -234,22 +255,31 @@ bool Store::parseStream()
}
if ( command == AKONADI_PARAM_TAGS ) {
- bool tagsChanged = true;
- const ImapSet tags = m_streamParser->readSequenceSet();
- if ( op == Replace ) {
- tagsChanged = replaceTags( pimItems, tags );
- } else if ( op == Add ) {
- if ( !addTags( pimItems, tags, tagsChanged ) ) {
- return failureResponse( "Unable to add item tags." );
- }
- } else if ( op == Delete ) {
- if ( !( tagsChanged = deleteTags( pimItems, tags ) ) ) {
- return failureResponse( "Unable to remove item tags." );
- }
+ const ImapSet tagsIds = m_streamParser->readSequenceSet();
+ const Tag::List tags = HandlerHelper::resolveTags( tagsIds );
+ if (!processTagsChange( op, pimItems, tags, changes )) {
+ return false;
}
+ continue;
+ }
+
+ if ( command == AKONADI_PARAM_RTAGS ) {
+ if (!connection()->context()->resource().isValid()) {
+ throw HandlerException( "Only resources can use RTAGS" );
+ }
+ const QVector<QByteArray> tagsIds = m_streamParser->readParenthesizedList().toVector();
+ const Tag::List tags = HandlerHelper::resolveTagsByRID( tagsIds, connection()->context() );
+ if (!processTagsChange( op, pimItems, tags, changes )) {
+ return false;
+ }
+ continue;
+ }
- if ( tagsChanged && !changes.contains( AKONADI_PARAM_TAGS ) ) {
- changes << AKONADI_PARAM_TAGS;
+ if ( command == AKONADI_PARAM_GTAGS ) {
+ const QVector<QByteArray> tagsIds = m_streamParser->readParenthesizedList().toVector();
+ const Tag::List tags = HandlerHelper::resolveTagsByGID( tagsIds );
+ if (!processTagsChange( op, pimItems, tags, changes )) {
+ return false;
}
continue;
}
diff --git a/server/src/handler/store.h b/server/src/handler/store.h
index ad3a5a0..c618a53 100644
--- a/server/src/handler/store.h
+++ b/server/src/handler/store.h
@@ -115,12 +115,14 @@ class Store : public Handler
bool replaceFlags( const PimItem::List &items, const QVector<QByteArray> &flags );
bool addFlags( const PimItem::List &items, const QVector<QByteArray> &flags, bool &flagsChanged );
bool deleteFlags( const PimItem::List &items, const QVector<QByteArray> &flags );
- bool replaceTags( const PimItem::List &items, const ImapSet &tags );
- bool addTags( const PimItem::List &items, const ImapSet &tags, bool &tagsChanged );
- bool deleteTags( const PimItem::List &items, const ImapSet &tags );
+ bool replaceTags( const PimItem::List &items, const Tag::List &tags );
+ bool addTags( const PimItem::List &items, const Tag::List &tags, bool &tagsChanged );
+ bool deleteTags( const PimItem::List &items, const Tag::List &tags );
bool setGid( const PimItem &item, const QString &gid );
void sendPimItemResponse( const PimItem &pimItem );
+ bool processTagsChange(Store::Operation operation, const PimItem::List &items, const Tag::List &tags, QSet<QByteArray> &changes);
+
private:
Scope mScope;
int mPos;
diff --git a/server/src/handlerhelper.cpp b/server/src/handlerhelper.cpp
index 763ea30..634a26c 100644
--- a/server/src/handlerhelper.cpp
+++ b/server/src/handlerhelper.cpp
@@ -366,7 +366,7 @@ Tag::List HandlerHelper::resolveTagsByGID(const QVector<QByteArray> &tagsGIDs)
}
Q_FOREACH (const QByteArray &tagGID, tagsGIDs) {
- Tag::List tags = Tag::retrieveFiltered(Tag::gidColumn(), tagGID);
+ Tag::List tags = Tag::retrieveFiltered(Tag::gidColumn(), QString::fromLatin1(tagGID));
Tag tag;
if (tags.isEmpty()) {
tag.setGid(QString::fromUtf8(tagGID));
@@ -413,7 +413,7 @@ Tag::List HandlerHelper::resolveTagsByRID(const QVector< QByteArray >& tagsRIDs,
cond.addColumnCondition(Tag::idFullColumnName(), Query::Equals, TagRemoteIdResourceRelation::tagIdFullColumnName());
cond.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, context->resource().id());
qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), cond);
- qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Equals, tagRID);
+ qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Equals, QString::fromLatin1(tagRID));
if (!qb.exec()) {
throw HandlerException("Unable to resolve tags");
}
--
2.1.0

View file

@ -0,0 +1,27 @@
From e52f9be20e566e507e77421f1243f51aa2fe8e55 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 25 Aug 2014 14:35:14 +0200
Subject: [PATCH 04/30] Fix typo in if condition
BUG: 338483
FIXED-IN: 1.13.1
---
server/src/handler/akappend.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/src/handler/akappend.cpp b/server/src/handler/akappend.cpp
index 43f03ba..ad3682f 100644
--- a/server/src/handler/akappend.cpp
+++ b/server/src/handler/akappend.cpp
@@ -380,7 +380,7 @@ bool AkAppend::parseStream()
if ( itemFlags.incremental ) {
throw HandlerException( "Incremental flags changes are not allowed in AK-APPEND" );
}
- if ( itemTagsRID.incremental || itemTagsRID.incremental ) {
+ if ( itemTagsRID.incremental || itemTagsGID.incremental ) {
throw HandlerException( "Incremental tags changes are not allowed in AK-APPEND" );
}
--
2.1.0

View file

@ -0,0 +1,25 @@
From 01c86229f9e26d9e036f6f2ab405659ed836b5c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 8 Sep 2014 15:36:18 +0200
Subject: [PATCH 05/30] Fix buffer overflow in AKTEST_FAKESERVER_MAIN()
---
shared/aktest.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shared/aktest.h b/shared/aktest.h
index b1b9caa..3026304 100644
--- a/shared/aktest.h
+++ b/shared/aktest.h
@@ -57,7 +57,7 @@ int main(int argc, char **argv) \
} \
} \
TestObject tc; \
- char **fakeArgv = (char **) malloc(options.count()); \
+ char **fakeArgv = (char **) malloc(options.count() * sizeof(char**)); \
for (int i = 0; i < options.count(); ++i) { \
fakeArgv[i] = options[i]; \
} \
--
2.1.0

View file

@ -0,0 +1,29 @@
From ca59eb345cfef368242929ea33beca4bff837e9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Thu, 18 Sep 2014 16:54:26 +0200
Subject: [PATCH 06/30] Don't crash when setmntent returns NULL
setmntent can fail when there's no /etc/mtab file for instance and
passing NULL pointer to getmntent crashes, so we need to return when
this happens.
---
server/src/utils.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/server/src/utils.cpp b/server/src/utils.cpp
index b04a812..b51c330 100644
--- a/server/src/utils.cpp
+++ b/server/src/utils.cpp
@@ -179,6 +179,9 @@ QString Utils::getDirectoryFileSystem(const QString &directory)
QString bestMatchFS;
FILE *mtab = setmntent("/etc/mtab", "r");
+ if (!mtab) {
+ return QString();
+ }
while (mntent *mnt = getmntent(mtab)) {
if (qstrcmp(mnt->mnt_type, MNTTYPE_IGNORE) == 0) {
continue;
--
2.1.0

View file

@ -0,0 +1,37 @@
From c516ec5c28d603aea0df6165f66a3a5d0a0191c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 19 Sep 2014 10:50:23 +0200
Subject: [PATCH 07/30] Don't call insert() from Q_ASSERT - breaks unit-tests
in Release mode
---
server/tests/unittest/collectionreferencetest.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/server/tests/unittest/collectionreferencetest.cpp b/server/tests/unittest/collectionreferencetest.cpp
index 1700c75..1b10c55 100644
--- a/server/tests/unittest/collectionreferencetest.cpp
+++ b/server/tests/unittest/collectionreferencetest.cpp
@@ -45,7 +45,8 @@ public:
Resource res;
res.setId(1);
res.setName(QLatin1String(name));
- Q_ASSERT(res.insert());
+ const bool success = res.insert();
+ Q_ASSERT(success);
mResource = res;
return res;
}
@@ -57,7 +58,8 @@ public:
col.setName(QLatin1String(name));
col.setRemoteId(QLatin1String(name));
col.setResource(mResource);
- Q_ASSERT(col.insert());
+ const bool success = col.insert();
+ Q_ASSERT(success);
return col;
}
--
2.1.0

View file

@ -0,0 +1,32 @@
From b35fcb64c3ba3df95f62d0d129adb791ce2bad15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 19 Sep 2014 11:10:13 +0200
Subject: [PATCH 08/30] Suppress unused variable warnings in release mode
---
server/tests/unittest/collectionreferencetest.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/server/tests/unittest/collectionreferencetest.cpp b/server/tests/unittest/collectionreferencetest.cpp
index 1b10c55..9c98f28 100644
--- a/server/tests/unittest/collectionreferencetest.cpp
+++ b/server/tests/unittest/collectionreferencetest.cpp
@@ -47,6 +47,7 @@ public:
res.setName(QLatin1String(name));
const bool success = res.insert();
Q_ASSERT(success);
+ Q_UNUSED(success);
mResource = res;
return res;
}
@@ -60,6 +61,7 @@ public:
col.setResource(mResource);
const bool success = col.insert();
Q_ASSERT(success);
+ Q_UNUSED(success);
return col;
}
--
2.1.0

View file

@ -0,0 +1,45 @@
From e1c69c277ea6005cc358434679b83fa1cb752756 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Tue, 23 Sep 2014 18:00:34 +0200
Subject: [PATCH 09/30] Test whether compiler supports all required C++11
features at configure time
To prevent ugly compilation errors when someone tries to compile Akonadi
with a compiler that does not support all C++11 features we use, we run
a try_compile check in CMakeLists.txt.
---
CMakeLists.txt | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e081d23..2d790c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -216,6 +216,24 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER MATCHES "icc" OR (CMAKE_CXX_COMP
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-long-long -std=iso9899:1990 -Wundef -Wcast-align -Werror-implicit-function-declaration -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wwrite-strings -Wformat-security -Wmissing-format-attribute -fno-common")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wnon-virtual-dtor -Wundef -Wcast-align -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wformat-security -fno-common")
+ file(WRITE ${CMAKE_BINARY_DIR}/cxx11_check.cpp
+ "enum Enum { Value = 1 };
+ struct Class {
+ Class(int val) { (void)val; };
+ // Delegating constructor
+ Class(): Class(42) {};
+ // New-style enumerator
+ Class(Enum e = Enum::Value) { (void)e; };
+ };
+ int main() {}
+ ")
+ try_compile(CXX11_SUPPORTED
+ ${CMAKE_BINARY_DIR}/cxx11_check
+ ${CMAKE_BINARY_DIR}/cxx11_check.cpp)
+ if (NOT CXX11_SUPPORTED)
+ message(FATAL_ERROR "Compiler does not support all required C++11 features")
+ endif()
+
# debugfull target
set(CMAKE_CXX_FLAGS_DEBUGFULL "-g3 -fno-inline" CACHE STRING "Flags used by the C++ compiler during debugfull builds." FORCE)
set(CMAKE_C_FLAGS_DEBUGFULL "-g3 -fno-inline" CACHE STRING "Flags used by the C compiler during debugfull builds." FORCE)
--
2.1.0

View file

@ -0,0 +1,27 @@
From de9bd9043e8878fc472ced1669bc7d49b07c2062 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20J=2EV=2E=20Bertin?= <rjvbertin@gmail.com>
Date: Mon, 3 Nov 2014 16:56:56 +0100
Subject: [PATCH 10/30] prevent starting a QTimer with a negative interval
Review: 120800
---
server/src/collectionscheduler.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/server/src/collectionscheduler.cpp b/server/src/collectionscheduler.cpp
index 8d4cd5c..9ba632f 100644
--- a/server/src/collectionscheduler.cpp
+++ b/server/src/collectionscheduler.cpp
@@ -82,7 +82,8 @@ class PauseableTimer : public QTimer
return;
}
- start( interval() - ( mStarted.secsTo( mPaused ) * 1000 ) );
+ const int remainder = interval() - ( mStarted.secsTo( mPaused ) * 1000 );
+ start( qMax( 0, remainder ) );
mPaused = QDateTime();
// Update mStarted so that pause() can be called repeatedly
mStarted = QDateTime::currentDateTime();
--
2.1.0

View file

@ -0,0 +1,119 @@
From 1d79c645ffbd858517f07cee3143dc64fac7c3e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 10 Nov 2014 11:51:45 +0100
Subject: [PATCH 11/30] Convert some qDebugs to akDebugs
This should make Akonadi in release mode even less chatty.
---
server/src/handler/merge.cpp | 3 ++-
server/src/handler/modify.cpp | 6 +++---
server/src/handler/remove.cpp | 4 ++--
server/src/search/searchmanager.cpp | 12 ++++++------
4 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/server/src/handler/merge.cpp b/server/src/handler/merge.cpp
index fffe100..c26917d 100644
--- a/server/src/handler/merge.cpp
+++ b/server/src/handler/merge.cpp
@@ -328,8 +328,9 @@ bool Merge::parseStream()
}
} else {
+ akDebug() << "Multiple merge candidates:";
Q_FOREACH (const PimItem &item, result) {
- qDebug() << item.id() << item.remoteId() << item.gid();
+ akDebug() << "\t" << item.id() << item.remoteId() << item.gid();
}
// Nor GID or RID are guaranteed to be unique, so make sure we don't merge
// something we don't want
diff --git a/server/src/handler/modify.cpp b/server/src/handler/modify.cpp
index 9671fb9..ad329db 100644
--- a/server/src/handler/modify.cpp
+++ b/server/src/handler/modify.cpp
@@ -216,9 +216,9 @@ bool Modify::parseStream()
queryAttributes = attrs.join( QLatin1String( " " ) );
- qDebug() << collection.queryAttributes() << queryAttributes;
- qDebug() << collection.queryCollections() << queryCollections;
- qDebug() << collection.queryString() << queryString;
+ akDebug() << collection.queryAttributes() << queryAttributes;
+ akDebug() << collection.queryCollections() << queryCollections;
+ akDebug() << collection.queryString() << queryString;
if ( collection.queryAttributes() != queryAttributes
|| collection.queryCollections() != queryCollections
diff --git a/server/src/handler/remove.cpp b/server/src/handler/remove.cpp
index 090531f..daec5a0 100644
--- a/server/src/handler/remove.cpp
+++ b/server/src/handler/remove.cpp
@@ -40,8 +40,8 @@ bool Remove::parseStream()
{
mScope.parseScope( m_streamParser );
connection()->context()->parseContext( m_streamParser );
- qDebug() << "Tag context:" << connection()->context()->tagId();
- qDebug() << "Collection context: " << connection()->context()->collectionId();
+ akDebug() << "Tag context:" << connection()->context()->tagId();
+ akDebug() << "Collection context: " << connection()->context()->collectionId();
SelectQueryBuilder<PimItem> qb;
diff --git a/server/src/search/searchmanager.cpp b/server/src/search/searchmanager.cpp
index 35e76e1..c821aa3 100644
--- a/server/src/search/searchmanager.cpp
+++ b/server/src/search/searchmanager.cpp
@@ -159,7 +159,7 @@ void SearchManager::loadSearchPlugins()
Q_FOREACH ( const QString &pluginDir, dirs ) {
QDir dir( pluginDir + QLatin1String( "/akonadi" ) );
const QStringList desktopFiles = dir.entryList( QStringList() << QLatin1String( "*.desktop" ), QDir::Files );
- qDebug() << "SEARCH MANAGER: searching in " << pluginDir + QLatin1String( "/akonadi" ) << ":" << desktopFiles;
+ akDebug() << "SEARCH MANAGER: searching in " << pluginDir + QLatin1String( "/akonadi" ) << ":" << desktopFiles;
Q_FOREACH ( const QString &desktopFileName, desktopFiles ) {
QSettings desktop( pluginDir + QLatin1String( "/akonadi/" ) + desktopFileName, QSettings::IniFormat );
@@ -170,13 +170,13 @@ void SearchManager::loadSearchPlugins()
const QString libraryName = desktop.value( QLatin1String( "X-Akonadi-Library" ) ).toString();
if ( loadedPlugins.contains( libraryName ) ) {
- qDebug() << "Already loaded one version of this plugin, skipping: " << libraryName;
+ akDebug() << "Already loaded one version of this plugin, skipping: " << libraryName;
continue;
}
// When search plugin override is active, ignore all plugins except for the override
if ( !pluginOverride.isEmpty() ) {
if ( libraryName != pluginOverride ) {
- qDebug() << desktopFileName << "skipped because of AKONADI_OVERRIDE_SEARCHPLUGIN";
+ akDebug() << desktopFileName << "skipped because of AKONADI_OVERRIDE_SEARCHPLUGIN";
continue;
}
@@ -198,7 +198,7 @@ void SearchManager::loadSearchPlugins()
continue;
}
- qDebug() << "SearchManager: loaded search plugin" << libraryName;
+ akDebug() << "SearchManager: loaded search plugin" << libraryName;
mPlugins << plugin;
loadedPlugins << libraryName;
}
@@ -390,7 +390,7 @@ void SearchManager::searchUpdateResultsAvailable( const QSet<qint64> &results )
}
}
- qDebug() << "Got" << newMatches.count() << "results, out of which" << existingMatches.count() << "are already in the collection";
+ akDebug() << "Got" << newMatches.count() << "results, out of which" << existingMatches.count() << "are already in the collection";
newMatches = newMatches - existingMatches;
@@ -405,7 +405,7 @@ void SearchManager::searchUpdateResultsAvailable( const QSet<qint64> &results )
Collection::addPimItem( collection.id(), id );
}
- qDebug() << "Added" << newMatches.count();
+ akDebug() << "Added" << newMatches.count();
if ( !existingTransaction && !DataStore::self()->commitTransaction() ) {
akDebug() << "Failed to commit transaction";
--
2.1.0

View file

@ -0,0 +1,604 @@
From 63f49d233ca8a4fdd3e8937ea1c80d5e57a1cbdc Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Tue, 25 Nov 2014 20:16:41 +0100
Subject: [PATCH 12/30] Optimize: Reduce the amount of allocations required to
build a query.
The initial implementation of the QueryBuilder was quite naive, when
you look at the amount of string allocations it does to build the
final query we sent to the SQL server.
This was found with Linux perf (no, not even heaptrack!). It
showed a huge number of cycles spent in malloc/free, all called
eventually by the QueryBuilder.
This patch removes most of these allocations. It can further be
improved in the future, I bet. Also, the amount of queries we create
is pretty large. I guess using stored procedures or something similar
might also help the performance. At least, we should try to "remember"
some of our queries, and make it possible to reuse them in the
functions that run often.
The added benchmark shows that the cost is not as big as I'd initially
assumed. There are simply many more allocation occurrences in Akonadi
currently. Still, I think it's worth it, as it also decreases the
memory fragmentation and improves cache locality:
Before:
RESULT : QueryBuilderTest::benchQueryBuilder():
0.0115 msecs per iteration (total: 116, iterations: 10000)
113.10MB bytes allocated in total (ignoring deallocations)
over 1203089 calls to allocation functions.
peak heap memory consumption: 254.46KB
After:
RESULT : QueryBuilderTest::benchQueryBuilder():
0.0065 msecs per iteration (total: 66, iterations: 10000)
62.42MB bytes allocated in total (ignoring deallocations)
over 343089 calls to allocation functions.
peak heap memory consumption: 254.96KB
So before, we had approx. 60 allocations per query build in the
benchmark (note that Qt for some reason executes the loop twice,
so while the time is measured for 10k iterations, heaptrack will
see 20k). With this patch applied, we only need ~20 allocations
per query we build up.
The remaining allocations are the various append operations to
the QList/QVectors mostly, as well as QueryBuilder::addAggregation.
REVIEW: 121247
---
server/src/storage/querybuilder.cpp | 210 ++++++++++++++++-------------
server/src/storage/querybuilder.h | 14 +-
server/tests/unittest/querybuildertest.cpp | 58 ++++++--
server/tests/unittest/querybuildertest.h | 2 +
4 files changed, 173 insertions(+), 111 deletions(-)
diff --git a/server/src/storage/querybuilder.cpp b/server/src/storage/querybuilder.cpp
index c079059..3017867 100644
--- a/server/src/storage/querybuilder.cpp
+++ b/server/src/storage/querybuilder.cpp
@@ -31,7 +31,7 @@
using namespace Akonadi::Server;
-static QString compareOperatorToString( Query::CompareOperator op )
+static QLatin1String compareOperatorToString( Query::CompareOperator op )
{
switch ( op ) {
case Query::Equals:
@@ -58,10 +58,10 @@ static QString compareOperatorToString( Query::CompareOperator op )
return QLatin1String( " LIKE " );
}
Q_ASSERT_X( false, "QueryBuilder::compareOperatorToString()", "Unknown compare operator." );
- return QString();
+ return QLatin1String("");
}
-static QString logicOperatorToString( Query::LogicOperator op )
+static QLatin1String logicOperatorToString( Query::LogicOperator op )
{
switch ( op ) {
case Query::And:
@@ -70,10 +70,10 @@ static QString logicOperatorToString( Query::LogicOperator op )
return QLatin1String( " OR " );
}
Q_ASSERT_X( false, "QueryBuilder::logicOperatorToString()", "Unknown logic operator." );
- return QString();
+ return QLatin1String("");
}
-static QString sortOrderToString( Query::SortOrder order )
+static QLatin1String sortOrderToString( Query::SortOrder order )
{
switch ( order ) {
case Query::Ascending:
@@ -82,7 +82,17 @@ static QString sortOrderToString( Query::SortOrder order )
return QLatin1String( " DESC" );
}
Q_ASSERT_X( false, "QueryBuilder::sortOrderToString()", "Unknown sort order." );
- return QString();
+ return QLatin1String("");
+}
+
+static void appendJoined( QString *statement, const QStringList &strings, const QLatin1String &glue = QLatin1String( ", " ) )
+{
+ for (int i = 0, c = strings.size(); i < c; ++i) {
+ *statement += strings.at( i );
+ if (i + 1 < c) {
+ *statement += glue;
+ }
+ }
}
QueryBuilder::QueryBuilder( const QString &table, QueryBuilder::QueryType type )
@@ -94,10 +104,12 @@ QueryBuilder::QueryBuilder( const QString &table, QueryBuilder::QueryType type )
, mDatabaseType( DbType::Unknown )
#endif
, mType( type )
- , mIdentificationColumn( QLatin1String( "id" ) )
+ , mIdentificationColumn( )
, mLimit( -1 )
, mDistinct( false )
{
+ static const QString defaultIdColumn = QLatin1String( "id" );
+ mIdentificationColumn = defaultIdColumn;
}
void QueryBuilder::setDatabaseType( DbType::Type type )
@@ -175,60 +187,65 @@ void QueryBuilder::sqliteAdaptUpdateJoin( Query::Condition &condition )
qb.addCondition( joinCondition.second );
// Convert the subquery to string
- condition.mColumn = QLatin1String( "( " ) + qb.buildQuery() + QLatin1String( " )" );
+ condition.mColumn.reserve(1024);
+ condition.mColumn.resize(0);
+ condition.mColumn += QLatin1String( "( " );
+ qb.buildQuery(&condition.mColumn);
+ condition.mColumn += QLatin1String( " )" );
}
-
-QString QueryBuilder::buildQuery()
+void QueryBuilder::buildQuery(QString *statement)
{
- QString statement;
-
// we add the ON conditions of Inner Joins in a Update query here
// but don't want to change the mRootCondition on each exec().
Query::Condition whereCondition = mRootCondition[WhereCondition];
switch ( mType ) {
case Select:
- statement += QLatin1String( "SELECT " );
+ *statement += QLatin1String( "SELECT " );
if ( mDistinct ) {
- statement += QLatin1String( "DISTINCT " );
+ *statement += QLatin1String( "DISTINCT " );
}
Q_ASSERT_X( mColumns.count() > 0, "QueryBuilder::exec()", "No columns specified" );
- statement += mColumns.join( QLatin1String( ", " ) );
- statement += QLatin1String( " FROM " );
- statement += mTable;
+ appendJoined( statement, mColumns );
+ *statement += QLatin1String( " FROM " );
+ *statement += mTable;
Q_FOREACH ( const QString &joinedTable, mJoinedTables ) {
const QPair<JoinType, Query::Condition> &join = mJoins.value( joinedTable );
switch ( join.first ) {
case LeftJoin:
- statement += QLatin1String( " LEFT JOIN " );
+ *statement += QLatin1String( " LEFT JOIN " );
break;
case InnerJoin:
- statement += QLatin1String( " INNER JOIN " );
+ *statement += QLatin1String( " INNER JOIN " );
break;
}
- statement += joinedTable;
- statement += QLatin1String( " ON " );
- statement += buildWhereCondition( join.second );
+ *statement += joinedTable;
+ *statement += QLatin1String( " ON " );
+ buildWhereCondition( statement, join.second );
}
break;
case Insert:
{
- statement += QLatin1String( "INSERT INTO " );
- statement += mTable;
- statement += QLatin1String( " (" );
- typedef QPair<QString,QVariant> StringVariantPair;
- QStringList cols, vals;
- Q_FOREACH ( const StringVariantPair &p, mColumnValues ) {
- cols.append( p.first );
- vals.append( bindValue( p.second ) );
+ *statement += QLatin1String( "INSERT INTO " );
+ *statement += mTable;
+ *statement += QLatin1String( " (" );
+ for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
+ *statement += mColumnValues.at(i).first;
+ if (i + 1 < c) {
+ *statement += QLatin1String( ", " );
+ }
+ }
+ *statement += QLatin1String( ") VALUES (" );
+ for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
+ bindValue( statement, mColumnValues.at(i).second );
+ if (i + 1 < c) {
+ *statement += QLatin1String( ", " );
+ }
}
- statement += cols.join( QLatin1String( ", " ) );
- statement += QLatin1String( ") VALUES (" );
- statement += vals.join( QLatin1String( ", " ) );
- statement += QLatin1Char( ')' );
+ *statement += QLatin1Char( ')' );
if ( mDatabaseType == DbType::PostgreSQL && !mIdentificationColumn.isEmpty() ) {
- statement += QLatin1String( " RETURNING " ) + mIdentificationColumn;
+ *statement += QLatin1String( " RETURNING " ) + mIdentificationColumn;
}
break;
}
@@ -246,78 +263,75 @@ QString QueryBuilder::buildQuery()
sqliteAdaptUpdateJoin( whereCondition );
}
- statement += QLatin1String( "UPDATE " );
- statement += mTable;
+ *statement += QLatin1String( "UPDATE " );
+ *statement += mTable;
if ( mDatabaseType == DbType::MySQL && !mJoinedTables.isEmpty() ) {
// for mysql we list all tables directly
- statement += QLatin1String( ", " );
- statement += mJoinedTables.join( QLatin1String( ", " ) );
+ *statement += QLatin1String( ", " );
+ appendJoined( statement, mJoinedTables );
}
- statement += QLatin1String( " SET " );
+ *statement += QLatin1String( " SET " );
Q_ASSERT_X( mColumnValues.count() >= 1, "QueryBuilder::exec()", "At least one column needs to be changed" );
- typedef QPair<QString,QVariant> StringVariantPair;
- QStringList updStmts;
- Q_FOREACH ( const StringVariantPair &p, mColumnValues ) {
- QString updStmt = p.first;
- updStmt += QLatin1String( " = " );
- updStmt += bindValue( p.second );
- updStmts << updStmt;
+ for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
+ const QPair<QString, QVariant>& p = mColumnValues.at( i );
+ *statement += p.first;
+ *statement += QLatin1String( " = " );
+ bindValue( statement, p.second );
+ if (i + 1 < c) {
+ *statement += QLatin1String( ", " );
+ }
}
- statement += updStmts.join( QLatin1String( ", " ) );
if ( mDatabaseType == DbType::PostgreSQL && !mJoinedTables.isEmpty() ) {
// PSQL have this syntax
// FROM t1 JOIN t2 JOIN ...
- statement += QLatin1String( " FROM " );
- statement += mJoinedTables.join( QLatin1String( " JOIN " ) );
+ *statement += QLatin1String( " FROM " );
+ appendJoined( statement, mJoinedTables, QLatin1String( " JOIN " ) );
}
break;
}
case Delete:
- statement += QLatin1String( "DELETE FROM " );
- statement += mTable;
+ *statement += QLatin1String( "DELETE FROM " );
+ *statement += mTable;
break;
default:
Q_ASSERT_X( false, "QueryBuilder::exec()", "Unknown enum value" );
}
if ( !whereCondition.isEmpty() ) {
- statement += QLatin1String( " WHERE " );
- statement += buildWhereCondition( whereCondition );
+ *statement += QLatin1String( " WHERE " );
+ buildWhereCondition( statement, whereCondition );
}
if ( !mGroupColumns.isEmpty() ) {
- statement += QLatin1String( " GROUP BY " );
- statement += mGroupColumns.join( QLatin1String( ", " ) );
+ *statement += QLatin1String( " GROUP BY " );
+ appendJoined( statement, mGroupColumns );
}
if ( !mRootCondition[HavingCondition].isEmpty() ) {
- statement += QLatin1String( " HAVING " );
- statement += buildWhereCondition( mRootCondition[HavingCondition] );
+ *statement += QLatin1String( " HAVING " );
+ buildWhereCondition( statement, mRootCondition[HavingCondition] );
}
if ( !mSortColumns.isEmpty() ) {
Q_ASSERT_X( mType == Select, "QueryBuilder::exec()", "Order statements are only valid for SELECT queries" );
- QStringList orderStmts;
- typedef QPair<QString, Query::SortOrder> SortColumnInfo;
- Q_FOREACH ( const SortColumnInfo &order, mSortColumns ) {
- QString orderStmt;
- orderStmt += order.first;
- orderStmt += sortOrderToString( order.second );
- orderStmts << orderStmt;
+ *statement += QLatin1String( " ORDER BY " );
+ for (int i = 0, c = mSortColumns.size(); i < c; ++i) {
+ const QPair<QString, Query::SortOrder>& order = mSortColumns.at( i );
+ *statement += order.first;
+ *statement += sortOrderToString( order.second );
+ if (i + 1 < c) {
+ *statement += QLatin1String( ", " );
+ }
}
- statement += QLatin1String( " ORDER BY " );
- statement += orderStmts.join( QLatin1String( ", " ) );
}
if ( mLimit > 0 ) {
- statement += QLatin1Literal( " LIMIT " ) + QString::number( mLimit );
+ *statement += QLatin1Literal( " LIMIT " ) + QString::number( mLimit );
}
-
- return statement;
}
bool QueryBuilder::retryLastTransaction( bool rollback )
@@ -334,7 +348,9 @@ bool QueryBuilder::retryLastTransaction( bool rollback )
bool QueryBuilder::exec()
{
- const QString statement = buildQuery();
+ QString statement;
+ statement.reserve(1024);
+ buildQuery(&statement);
#ifndef QUERYBUILDER_UNITTEST
if ( QueryCache::contains( statement ) ) {
@@ -443,52 +459,54 @@ void QueryBuilder::addColumn( const QString &col )
void QueryBuilder::addAggregation( const QString &col, const QString &aggregate )
{
- QString s( aggregate );
- s += QLatin1Char( '(' );
- s += col;
- s += QLatin1Char( ')' );
- mColumns.append( s );
+ mColumns.append( aggregate + QLatin1Char( '(' ) + col + QLatin1Char( ')' ) );
}
-QString QueryBuilder::bindValue( const QVariant &value )
+void QueryBuilder::bindValue( QString *query, const QVariant &value )
{
mBindValues << value;
- return QLatin1Char( ':' ) + QString::number( mBindValues.count() - 1 );
+ *query += QLatin1Char( ':' ) + QString::number( mBindValues.count() - 1 );
}
-QString QueryBuilder::buildWhereCondition( const Query::Condition &cond )
+void QueryBuilder::buildWhereCondition( QString *query, const Query::Condition &cond )
{
if ( !cond.isEmpty() ) {
- QStringList conds;
- Q_FOREACH ( const Query::Condition &c, cond.subConditions() ) {
- conds << buildWhereCondition( c );
+ *query += QLatin1String( "( " );
+ const QLatin1String glue = logicOperatorToString( cond.mCombineOp );
+ const Query::Condition::List& subConditions = cond.subConditions();
+ for (int i = 0, c = subConditions.size(); i < c; ++i) {
+ buildWhereCondition(query, subConditions.at(i));
+ if (i + 1 < c) {
+ *query += glue;
+ }
}
- return QLatin1String( "( " ) + conds.join( logicOperatorToString( cond.mCombineOp ) ) + QLatin1String( " )" );
+ *query += QLatin1String( " )" );
} else {
- QString stmt = cond.mColumn;
- stmt += compareOperatorToString( cond.mCompareOp );
+ *query += cond.mColumn;
+ *query += compareOperatorToString( cond.mCompareOp );
if ( cond.mComparedColumn.isEmpty() ) {
if ( cond.mComparedValue.isValid() ) {
if ( cond.mComparedValue.canConvert( QVariant::List ) ) {
- stmt += QLatin1String( "( " );
- QStringList entries;
- Q_ASSERT_X( !cond.mComparedValue.toList().isEmpty(),
+ *query += QLatin1String( "( " );
+ const QVariantList& entries = cond.mComparedValue.toList();
+ Q_ASSERT_X( !entries.isEmpty(),
"QueryBuilder::buildWhereCondition()", "No values given for IN condition." );
- Q_FOREACH ( const QVariant &entry, cond.mComparedValue.toList() ) {
- entries << bindValue( entry );
+ for (int i = 0, c = entries.size(); i < c; ++i) {
+ bindValue( query, entries.at(i) );
+ if (i + 1 < c) {
+ *query += QLatin1String( ", " );
+ }
}
- stmt += entries.join( QLatin1String( ", " ) );
- stmt += QLatin1String( " )" );
+ *query += QLatin1String( " )" );
} else {
- stmt += bindValue( cond.mComparedValue );
+ bindValue( query, cond.mComparedValue );
}
} else {
- stmt += QLatin1String( "NULL" );
+ *query += QLatin1String( "NULL" );
}
} else {
- stmt += cond.mComparedColumn;
+ *query += cond.mComparedColumn;
}
- return stmt;
}
}
diff --git a/server/src/storage/querybuilder.h b/server/src/storage/querybuilder.h
index b380f93..df7c362 100644
--- a/server/src/storage/querybuilder.h
+++ b/server/src/storage/querybuilder.h
@@ -70,7 +70,9 @@ class QueryBuilder
WhereCondition,
/// add condition to HAVING part of the query
/// NOTE: only supported for SELECT queries
- HavingCondition
+ HavingCondition,
+
+ NUM_CONDITIONS
};
/**
@@ -234,9 +236,9 @@ class QueryBuilder
qint64 insertId();
private:
- QString buildQuery();
- QString bindValue( const QVariant &value );
- QString buildWhereCondition( const Query::Condition &cond );
+ void buildQuery( QString *query );
+ void bindValue( QString *query, const QVariant &value );
+ void buildWhereCondition( QString *query, const Query::Condition &cond );
/**
* SQLite does not support JOINs with UPDATE, so we have to convert it into
@@ -249,11 +251,11 @@ class QueryBuilder
private:
QString mTable;
DbType::Type mDatabaseType;
- QHash<ConditionType, Query::Condition> mRootCondition;
+ Query::Condition mRootCondition[NUM_CONDITIONS];
QSqlQuery mQuery;
QueryType mType;
QStringList mColumns;
- QList<QVariant> mBindValues;
+ QVector<QVariant> mBindValues;
QVector<QPair<QString, Query::SortOrder> > mSortColumns;
QStringList mGroupColumns;
QVector<QPair<QString, QVariant> > mColumnValues;
diff --git a/server/tests/unittest/querybuildertest.cpp b/server/tests/unittest/querybuildertest.cpp
index 0aba8a1..92df2a2 100644
--- a/server/tests/unittest/querybuildertest.cpp
+++ b/server/tests/unittest/querybuildertest.cpp
@@ -29,26 +29,29 @@
QTEST_MAIN( QueryBuilderTest )
+Q_DECLARE_METATYPE(QVector<QVariant>)
+
using namespace Akonadi::Server;
void QueryBuilderTest::testQueryBuilder_data()
{
+ qRegisterMetaType<QVector<QVariant> >();
mBuilders.clear();
QTest::addColumn<int>( "qbId" );
QTest::addColumn<QString>( "sql" );
- QTest::addColumn<QList<QVariant> >( "bindValues" );
+ QTest::addColumn<QVector<QVariant> >( "bindValues" );
QueryBuilder qb( "table", QueryBuilder::Select );
qb.addColumn( "col1" );
mBuilders << qb;
- QTest::newRow( "simple select" ) << mBuilders.count() << QString( "SELECT col1 FROM table" ) << QList<QVariant>();
+ QTest::newRow( "simple select" ) << mBuilders.count() << QString( "SELECT col1 FROM table" ) << QVector<QVariant>();
qb.addColumn( "col2" );
mBuilders << qb;
- QTest::newRow( "simple select 2" ) << mBuilders.count() << QString( "SELECT col1, col2 FROM table" ) << QList<QVariant>();
+ QTest::newRow( "simple select 2" ) << mBuilders.count() << QString( "SELECT col1, col2 FROM table" ) << QVector<QVariant>();
qb.addValueCondition( "col1", Query::Equals, QVariant( 5 ) );
- QList<QVariant> bindVals;
+ QVector<QVariant> bindVals;
bindVals << QVariant( 5 );
mBuilders << qb;
QTest::newRow( "single where" ) << mBuilders.count() << QString( "SELECT col1, col2 FROM table WHERE ( col1 = :0 )" ) << bindVals;
@@ -71,17 +74,17 @@ void QueryBuilderTest::testQueryBuilder_data()
qb = QueryBuilder( "table" );
qb.addAggregation( "col1", "count" );
mBuilders << qb;
- QTest::newRow( "single aggregation" ) << mBuilders.count() << QString( "SELECT count(col1) FROM table" ) << QList<QVariant>();
+ QTest::newRow( "single aggregation" ) << mBuilders.count() << QString( "SELECT count(col1) FROM table" ) << QVector<QVariant>();
qb = QueryBuilder( "table" );
qb.addColumn( "col1" );
qb.addSortColumn( "col1" );
mBuilders << qb;
- QTest::newRow( "single order by" ) << mBuilders.count() << QString( "SELECT col1 FROM table ORDER BY col1 ASC" ) << QList<QVariant>();
+ QTest::newRow( "single order by" ) << mBuilders.count() << QString( "SELECT col1 FROM table ORDER BY col1 ASC" ) << QVector<QVariant>();
qb.addSortColumn( "col2", Query::Descending );
mBuilders << qb;
- QTest::newRow( "multiple order by" ) << mBuilders.count() << QString( "SELECT col1 FROM table ORDER BY col1 ASC, col2 DESC" ) << QList<QVariant>();
+ QTest::newRow( "multiple order by" ) << mBuilders.count() << QString( "SELECT col1 FROM table ORDER BY col1 ASC, col2 DESC" ) << QVector<QVariant>();
qb = QueryBuilder( "table" );
qb.addColumn( "col1" );
@@ -98,7 +101,7 @@ void QueryBuilderTest::testQueryBuilder_data()
qb.addColumn( "col1" );
qb.setLimit( 1 );
mBuilders << qb;
- QTest::newRow( "SELECT with LIMIT" ) << mBuilders.count() << QString( "SELECT col1 FROM table LIMIT 1" ) << QList<QVariant>();
+ QTest::newRow( "SELECT with LIMIT" ) << mBuilders.count() << QString( "SELECT col1 FROM table LIMIT 1" ) << QVector<QVariant>();
qb = QueryBuilder( "table", QueryBuilder::Update );
qb.setColumnValue( "col1", QString( "bla" ) );
@@ -263,7 +266,7 @@ void QueryBuilderTest::testQueryBuilder()
{
QFETCH( int, qbId );
QFETCH( QString, sql );
- QFETCH( QList<QVariant>, bindValues );
+ QFETCH( QVector<QVariant>, bindValues );
--qbId;
@@ -271,3 +274,40 @@ void QueryBuilderTest::testQueryBuilder()
QCOMPARE( mBuilders[qbId].mStatement, sql );
QCOMPARE( mBuilders[qbId].mBindValues, bindValues );
}
+
+void QueryBuilderTest::benchQueryBuilder()
+{
+ const QString table1 = QLatin1String("Table1");
+ const QString table2 = QLatin1String("Table2");
+ const QString table3 = QLatin1String("Table3");
+ const QString table1_id = QLatin1String("Table1.id");
+ const QString table2_id = QLatin1String("Table2.id");
+ const QString table3_id = QLatin1String("Table3.id");
+ const QString aggregate = QLatin1String("COUNT");
+ const QVariant value = QVariant::fromValue(QString("asdf"));
+
+ const QStringList columns = QStringList()
+ << QLatin1String("Table1.id")
+ << QLatin1String("Table1.fooAsdf")
+ << QLatin1String("Table2.barLala")
+ << QLatin1String("Table3.xyzFsd");
+
+ bool executed = true;
+
+ QBENCHMARK {
+ QueryBuilder builder( table1, QueryBuilder::Select );
+ builder.setDatabaseType( DbType::MySQL );
+ builder.addColumns( columns );
+ builder.addJoin( QueryBuilder::InnerJoin, table2, table2_id, table1_id );
+ builder.addJoin( QueryBuilder::LeftJoin, table3, table1_id, table3_id );
+ builder.addAggregation( columns.first(), aggregate );
+ builder.addColumnCondition( columns.at(1), Query::LessOrEqual, columns.last() );
+ builder.addValueCondition( columns.at(3), Query::Equals, value );
+ builder.addSortColumn( columns.at(2) );
+ builder.setLimit( 10 );
+ builder.addGroupColumn( columns.at(3) );
+ executed = executed && builder.exec();
+ }
+
+ QVERIFY(executed);
+}
\ No newline at end of file
diff --git a/server/tests/unittest/querybuildertest.h b/server/tests/unittest/querybuildertest.h
index 3bb6b22..1bca2cc 100644
--- a/server/tests/unittest/querybuildertest.h
+++ b/server/tests/unittest/querybuildertest.h
@@ -37,6 +37,8 @@ class QueryBuilderTest : public QObject
void testQueryBuilder_data();
void testQueryBuilder();
+ void benchQueryBuilder();
+
private:
QList< Akonadi::Server::QueryBuilder > mBuilders;
};
--
2.1.0

View file

@ -0,0 +1,176 @@
From a04809a44c235bed854adc3bd49ca75b9673bf1f Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Wed, 26 Nov 2014 13:20:05 +0100
Subject: [PATCH 13/30] Intern entity strings for table and column names.
This should drastically cut down on the amount of allocations done
by the AkonadiServer. Currently, the getters will do the conversion
from QLatin1String to QString on every call. By reusing the data
via a function-local static const QString object, we can eliminate
all of these allocations and increase the cache locality as well.
REVIEW: 121255
---
server/src/storage/entities-source.xsl | 56 +++++++++++++++++++++-------------
server/src/storage/entities.xsl | 4 +--
2 files changed, 36 insertions(+), 24 deletions(-)
diff --git a/server/src/storage/entities-source.xsl b/server/src/storage/entities-source.xsl
index 174cf4f..7090c31 100644
--- a/server/src/storage/entities-source.xsl
+++ b/server/src/storage/entities-source.xsl
@@ -214,36 +214,41 @@ void <xsl:value-of select="$className"/>::<xsl:call-template name="setter-signat
// SQL table information
<xsl:text>QString </xsl:text><xsl:value-of select="$className"/>::tableName()
{
- return QLatin1String( "<xsl:value-of select="$tableName"/>" );
+ static const QString tableName = QLatin1String( "<xsl:value-of select="$tableName"/>" );
+ return tableName;
}
QStringList <xsl:value-of select="$className"/>::columnNames()
{
- QStringList rv;
+ static const QStringList columns = QStringList()
<xsl:for-each select="column">
- rv.append( QLatin1String( "<xsl:value-of select="@name"/>" ) );
+ &lt;&lt; <xsl:value-of select="@name"/>Column()
</xsl:for-each>
- return rv;
+ ;
+ return columns;
}
QStringList <xsl:value-of select="$className"/>::fullColumnNames()
{
- QStringList rv;
+ static const QStringList columns = QStringList()
<xsl:for-each select="column">
- rv.append( QLatin1String( "<xsl:value-of select="$tableName"/>.<xsl:value-of select="@name"/>" ) );
+ &lt;&lt; <xsl:value-of select="@name"/>FullColumnName()
</xsl:for-each>
- return rv;
+ ;
+ return columns;
}
<xsl:for-each select="column">
QString <xsl:value-of select="$className"/>::<xsl:value-of select="@name"/>Column()
{
- return QLatin1String( "<xsl:value-of select="@name"/>" );
+ static const QString column = QLatin1String( "<xsl:value-of select="@name"/>" );
+ return column;
}
QString <xsl:value-of select="$className"/>::<xsl:value-of select="@name"/>FullColumnName()
{
- return tableName() + QLatin1String( ".<xsl:value-of select="@name"/>" );
+ static const QString column = QLatin1String( "<xsl:value-of select="$tableName"/>.<xsl:value-of select="@name"/>" );
+ return column;
}
</xsl:for-each>
@@ -399,7 +404,6 @@ QVector&lt;<xsl:value-of select="@table"/>&gt; <xsl:value-of select="$className"
<xsl:variable name="relationName"><xsl:value-of select="@table1"/><xsl:value-of select="@table2"/>Relation</xsl:variable>
<xsl:variable name="rightSideClass"><xsl:value-of select="@table2"/></xsl:variable>
<xsl:variable name="rightSideEntity"><xsl:value-of select="@table2"/></xsl:variable>
-<xsl:variable name="rightSideTable"><xsl:value-of select="@table2"/>Table</xsl:variable>
// data retrieval for n:m relations
QVector&lt;<xsl:value-of select="$rightSideClass"/>&gt; <xsl:value-of select="$className"/>::<xsl:value-of select="concat(translate(substring(@table2,1,1),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'), substring(@table2,2))"/>s() const
@@ -408,14 +412,17 @@ QVector&lt;<xsl:value-of select="$rightSideClass"/>&gt; <xsl:value-of select="$c
if ( !db.isOpen() )
return QVector&lt;<xsl:value-of select="$rightSideClass"/>&gt;();
- QueryBuilder qb( QLatin1String("<xsl:value-of select="$rightSideTable"/>"), QueryBuilder::Select );
+ QueryBuilder qb( <xsl:value-of select="$rightSideClass"/>::tableName(), QueryBuilder::Select );
+ static const QStringList columns = QStringList()
<xsl:for-each select="/database/table[@name = $rightSideEntity]/column">
- qb.addColumn( QLatin1String("<xsl:value-of select="$rightSideTable"/>.<xsl:value-of select="@name"/>" ) );
+ &lt;&lt; <xsl:value-of select="$rightSideClass"/>::<xsl:value-of select="@name"/>FullColumnName()
</xsl:for-each>
- qb.addJoin( QueryBuilder::InnerJoin, QLatin1String("<xsl:value-of select="$relationName"/>"),
- QLatin1String("<xsl:value-of select="$relationName"/>.<xsl:value-of select="@table2"/>_<xsl:value-of select="@column2"/>"),
- QLatin1String("<xsl:value-of select="$rightSideTable"/>.<xsl:value-of select="@column2"/>") );
- qb.addValueCondition( QLatin1String("<xsl:value-of select="$relationName"/>.<xsl:value-of select="@table1"/>_<xsl:value-of select="@column1"/>"), Query::Equals, id() );
+ ;
+ qb.addColumns(columns);
+ qb.addJoin( QueryBuilder::InnerJoin, <xsl:value-of select="$relationName"/>::tableName(),
+ <xsl:value-of select="$relationName"/>::rightFullColumnName(),
+ <xsl:value-of select="$rightSideClass"/>::<xsl:value-of select="@column2"/>FullColumnName() );
+ qb.addValueCondition( <xsl:value-of select="$relationName"/>::leftFullColumnName(), Query::Equals, id() );
if ( !qb.exec() ) {
akDebug() &lt;&lt; "Error during selection of records from table <xsl:value-of select="@table1"/><xsl:value-of select="@table2"/>Relation"
@@ -546,7 +553,7 @@ bool <xsl:value-of select="$className"/>::update()
</xsl:for-each>
<xsl:if test="column[@name = 'id']">
- qb.addValueCondition( QLatin1String("id"), Query::Equals, id() );
+ qb.addValueCondition( idColumn(), Query::Equals, id() );
</xsl:if>
if ( !qb.exec() ) {
@@ -622,27 +629,32 @@ void <xsl:value-of select="$className"/>::enableCache( bool enable )
// SQL table information
QString <xsl:value-of select="$className"/>::tableName()
{
- return QLatin1String( "<xsl:value-of select="$tableName"/>" );
+ static const QString table = QLatin1String( "<xsl:value-of select="$tableName"/>" );
+ return table;
}
QString <xsl:value-of select="$className"/>::leftColumn()
{
- return QLatin1String( "<xsl:value-of select="@table1"/>_<xsl:value-of select="@column1"/>" );
+ static const QString column = QLatin1String( "<xsl:value-of select="@table1"/>_<xsl:value-of select="@column1"/>" );
+ return column;
}
QString <xsl:value-of select="$className"/>::leftFullColumnName()
{
- return tableName() + QLatin1String( "." ) + leftColumn();
+ static const QString column = QLatin1String( "<xsl:value-of select="$tableName"/>.<xsl:value-of select="@table1"/>_<xsl:value-of select="@column1"/>" );
+ return column;
}
QString <xsl:value-of select="$className"/>::rightColumn()
{
- return QLatin1String( "<xsl:value-of select="@table2"/>_<xsl:value-of select="@column2"/>" );
+ static const QString column = QLatin1String( "<xsl:value-of select="@table2"/>_<xsl:value-of select="@column2"/>" );
+ return column;
}
QString <xsl:value-of select="$className"/>::rightFullColumnName()
{
- return tableName() + QLatin1String( "." ) + rightColumn();
+ static const QString column = QLatin1String( "<xsl:value-of select="$tableName"/>.<xsl:value-of select="@table2"/>_<xsl:value-of select="@column2"/>" );
+ return column;
}
</xsl:template>
diff --git a/server/src/storage/entities.xsl b/server/src/storage/entities.xsl
index 033e292..8b0ed03 100644
--- a/server/src/storage/entities.xsl
+++ b/server/src/storage/entities.xsl
@@ -114,7 +114,7 @@ using namespace Akonadi::Server;
QVector&lt;QString&gt; Akonadi::Server::allDatabaseTables()
{
- static QVector&lt;QString&gt; allTables = QVector&lt;QString&gt;()
+ static const QVector&lt;QString&gt; allTables = QVector&lt;QString&gt;()
<xsl:for-each select="database/table">
&lt;&lt; QLatin1String( "<xsl:value-of select="@name"/>Table" )
</xsl:for-each>
@@ -182,7 +182,7 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
QueryBuilder qb( tableName(), QueryBuilder::Select );
qb.addColumns( columnNames() );
- qb.addValueCondition( QLatin1String("<xsl:value-of select="$key"/>"), Query::Equals, <xsl:value-of select="$key"/> );
+ qb.addValueCondition( <xsl:value-of select="$key"/>Column(), Query::Equals, <xsl:value-of select="$key"/> );
if ( !qb.exec() ) {
akDebug() &lt;&lt; "Error during selection of record with <xsl:value-of select="$key"/>"
&lt;&lt; <xsl:value-of select="$key"/> &lt;&lt; "from table" &lt;&lt; tableName()
--
2.1.0

View file

@ -0,0 +1,38 @@
From f49b99f5a49da1a78b0ced930b6438bb53b49fdd Mon Sep 17 00:00:00 2001
From: Volker Krause <vkrause@kde.org>
Date: Sun, 30 Nov 2014 11:25:56 +0100
Subject: [PATCH 14/30] No semicolon after Q_DECLARE_METATYPE.
---
server/src/storage/entity.h | 2 +-
server/tests/unittest/akappendhandlertest.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/src/storage/entity.h b/server/src/storage/entity.h
index eb180e4..acebb0b 100644
--- a/server/src/storage/entity.h
+++ b/server/src/storage/entity.h
@@ -192,6 +192,6 @@ namespace _detail {
} // namespace Server
} // namespace Akonadi
-Q_DECLARE_METATYPE(Akonadi::Server::Tristate);
+Q_DECLARE_METATYPE(Akonadi::Server::Tristate)
#endif
diff --git a/server/tests/unittest/akappendhandlertest.cpp b/server/tests/unittest/akappendhandlertest.cpp
index c221a3a..d7f57f9 100644
--- a/server/tests/unittest/akappendhandlertest.cpp
+++ b/server/tests/unittest/akappendhandlertest.cpp
@@ -41,7 +41,7 @@
using namespace Akonadi;
using namespace Akonadi::Server;
-Q_DECLARE_METATYPE(PimItem);
+Q_DECLARE_METATYPE(PimItem)
Q_DECLARE_METATYPE(QVector<Flag>)
Q_DECLARE_METATYPE(QVector<FakePart>)
Q_DECLARE_METATYPE(QVector<FakeTag>)
--
2.1.0

View file

@ -0,0 +1,112 @@
From f5a0e3f1f4787b6a48880e42463ae38dce336a8f Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Mon, 1 Dec 2014 11:36:31 +0100
Subject: [PATCH 15/30] Use QMutexLocker instead of manual lock/unlock calls.
Just a minor cleanup patch, no change of behavior.
---
server/src/storage/entities-source.xsl | 17 +++++------------
server/src/storage/entities.xsl | 4 +---
2 files changed, 6 insertions(+), 15 deletions(-)
diff --git a/server/src/storage/entities-source.xsl b/server/src/storage/entities-source.xsl
index 7090c31..05a8cb1 100644
--- a/server/src/storage/entities-source.xsl
+++ b/server/src/storage/entities-source.xsl
@@ -125,14 +125,13 @@ void <xsl:value-of select="$className"/>::Private::addToCache( const <xsl:value-
{
Q_ASSERT( cacheEnabled );
Q_UNUSED( entry ); <!-- in case the table has neither an id nor name column -->
- cacheMutex.lock();
+ QMutexLocker lock(&amp;cacheMutex);
<xsl:if test="column[@name = 'id']">
idCache.insert( entry.id(), entry );
</xsl:if>
<xsl:if test="column[@name = 'name']">
nameCache.insert( entry.name(), entry );
</xsl:if>
- cacheMutex.unlock();
}
@@ -264,12 +263,10 @@ int <xsl:value-of select="$className"/>::count( const QString &amp;column, const
bool <xsl:value-of select="$className"/>::exists( qint64 id )
{
if ( Private::cacheEnabled ) {
- Private::cacheMutex.lock();
+ QMutexLocker lock(&amp;Private::cacheMutex);
if ( Private::idCache.contains( id ) ) {
- Private::cacheMutex.unlock();
return true;
}
- Private::cacheMutex.unlock();
}
return count( idColumn(), id ) > 0;
}
@@ -278,12 +275,10 @@ bool <xsl:value-of select="$className"/>::exists( qint64 id )
bool <xsl:value-of select="$className"/>::exists( const <xsl:value-of select="column[@name = 'name']/@type"/> &amp;name )
{
if ( Private::cacheEnabled ) {
- Private::cacheMutex.lock();
+ QMutexLocker lock(&amp;Private::cacheMutex);
if ( Private::nameCache.contains( name ) ) {
- Private::cacheMutex.unlock();
return true;
}
- Private::cacheMutex.unlock();
}
return count( nameColumn(), name ) > 0;
}
@@ -588,28 +583,26 @@ bool <xsl:value-of select="$className"/>::remove( qint64 id )
void <xsl:value-of select="$className"/>::invalidateCache() const
{
if ( Private::cacheEnabled ) {
- Private::cacheMutex.lock();
+ QMutexLocker lock(&amp;Private::cacheMutex);
<xsl:if test="column[@name = 'id']">
Private::idCache.remove( id() );
</xsl:if>
<xsl:if test="column[@name = 'name']">
Private::nameCache.remove( name() );
</xsl:if>
- Private::cacheMutex.unlock();
}
}
void <xsl:value-of select="$className"/>::invalidateCompleteCache()
{
if ( Private::cacheEnabled ) {
- Private::cacheMutex.lock();
+ QMutexLocker lock(&amp;Private::cacheMutex);
<xsl:if test="column[@name = 'id']">
Private::idCache.clear();
</xsl:if>
<xsl:if test="column[@name = 'name']">
Private::nameCache.clear();
</xsl:if>
- Private::cacheMutex.unlock();
}
}
diff --git a/server/src/storage/entities.xsl b/server/src/storage/entities.xsl
index 8b0ed03..a397544 100644
--- a/server/src/storage/entities.xsl
+++ b/server/src/storage/entities.xsl
@@ -167,13 +167,11 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
<xsl:variable name="className"><xsl:value-of select="@name"/></xsl:variable>
<xsl:if test="$cache != ''">
if ( Private::cacheEnabled ) {
- Private::cacheMutex.lock();
+ QMutexLocker lock(&amp;Private::cacheMutex);
if ( Private::<xsl:value-of select="$cache"/>.contains( <xsl:value-of select="$key"/> ) ) {
const <xsl:value-of select="$className"/> tmp = Private::<xsl:value-of select="$cache"/>.value( <xsl:value-of select="$key"/> );
- Private::cacheMutex.unlock();
return tmp;
}
- Private::cacheMutex.unlock();
}
</xsl:if>
QSqlDatabase db = DataStore::self()->database();
--
2.1.0

View file

@ -0,0 +1,37 @@
From 8a113985cda1693c8158916065bd54e57d028cda Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Mon, 1 Dec 2014 11:39:33 +0100
Subject: [PATCH 16/30] Use an QAtomicInt instead of a plain bool for
Entity::cacheEnabled.
A plain bool is not thread safe and leads to undefined behavior.
So better be safe than sorry and use a thread safe QAtomicInt.
---
server/src/storage/entities-source.xsl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/src/storage/entities-source.xsl b/server/src/storage/entities-source.xsl
index 05a8cb1..e398da5 100644
--- a/server/src/storage/entities-source.xsl
+++ b/server/src/storage/entities-source.xsl
@@ -99,7 +99,7 @@ class <xsl:value-of select="$className"/>::Private : public QSharedData
static void addToCache( const <xsl:value-of select="$className"/> &amp; entry );
// cache
- static bool cacheEnabled;
+ static QAtomicInt cacheEnabled;
static QMutex cacheMutex;
<xsl:if test="column[@name = 'id']">
static QHash&lt;qint64, <xsl:value-of select="$className"/> &gt; idCache;
@@ -111,7 +111,7 @@ class <xsl:value-of select="$className"/>::Private : public QSharedData
// static members
-bool <xsl:value-of select="$className"/>::Private::cacheEnabled = false;
+QAtomicInt <xsl:value-of select="$className"/>::Private::cacheEnabled(0);
QMutex <xsl:value-of select="$className"/>::Private::cacheMutex;
<xsl:if test="column[@name = 'id']">
QHash&lt;qint64, <xsl:value-of select="$className"/> &gt; <xsl:value-of select="$className"/>::Private::idCache;
--
2.1.0

View file

@ -0,0 +1,32 @@
From 202ffa522668087cc133026febf21a7de8963218 Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Mon, 1 Dec 2014 11:51:04 +0100
Subject: [PATCH 17/30] Optimize: Only do one hash lookup to retrieve value
from cache.
Compilers do not merge the call to contains() and the successive
value() lookup. Using iterators thus saves us one QHash lookup.
---
server/src/storage/entities.xsl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/server/src/storage/entities.xsl b/server/src/storage/entities.xsl
index a397544..9471293 100644
--- a/server/src/storage/entities.xsl
+++ b/server/src/storage/entities.xsl
@@ -168,9 +168,9 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
<xsl:if test="$cache != ''">
if ( Private::cacheEnabled ) {
QMutexLocker lock(&amp;Private::cacheMutex);
- if ( Private::<xsl:value-of select="$cache"/>.contains( <xsl:value-of select="$key"/> ) ) {
- const <xsl:value-of select="$className"/> tmp = Private::<xsl:value-of select="$cache"/>.value( <xsl:value-of select="$key"/> );
- return tmp;
+ QHash&lt;<xsl:value-of select="column[@name = $key]/@type"/>, <xsl:value-of select="$className"/>&gt;::const_iterator it = Private::<xsl:value-of select="$cache"/>.constFind(<xsl:value-of select="$key"/>);
+ if ( it != Private::<xsl:value-of select="$cache"/>.constEnd() ) {
+ return it.value();
}
}
</xsl:if>
--
2.1.0

View file

@ -0,0 +1,38 @@
From 7cbff48f5782d1f7f844678e6b785aeb419b0c47 Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Mon, 1 Dec 2014 11:59:12 +0100
Subject: [PATCH 18/30] Optimize: Skip value condition on invalid flags.
HandlerHelper::itemWithFlagsCount gets called quite often apparently
and I noticed that it was relatively slow from the Query Debugger
in Akonadi Console. EXPLAIN'ing the query showed that it was using
a slow-path for the WHERE FOO AND (BAR OR ASDF) condition. Here,
ASDF was always id = -1, the id of the $IGNORED flag, which
I apparently don't have. Getting rid of that condition simplifies
the query to WHERE FOO AND BAR, which is apparently much better
optimizable. Before, the query often showed a runtime of ~15ms.
Now it is down to ~9ms.
REVIEW: 121306
---
server/src/handlerhelper.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/server/src/handlerhelper.cpp b/server/src/handlerhelper.cpp
index 634a26c..82347b4 100644
--- a/server/src/handlerhelper.cpp
+++ b/server/src/handlerhelper.cpp
@@ -123,6 +123,10 @@ int HandlerHelper::itemWithFlagsCount( const Collection &col, const QStringList
// it hits an in-memory cache.
Q_FOREACH ( const QString &flag, flags ) {
const Flag f = Flag::retrieveByName( flag );
+ if (!f.isValid()) {
+ // since we OR this condition, we can skip invalid flags to speed up the query
+ continue;
+ }
cond.addValueCondition( PimItemFlagRelation::rightFullColumnName(), Query::Equals, f.id() );
}
qb.addCondition( cond );
--
2.1.0

View file

@ -0,0 +1,107 @@
From e52b57b7a9f0303c0c710e60870d0ec265d32541 Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Mon, 1 Dec 2014 14:11:19 +0100
Subject: [PATCH 19/30] Optimize queries: Do not retrieve known key used in the
condition.
There is no point in doing a select like:
SELECT foo, bar FROM table WHERE foo = needle;
That can be rewritten to say
SELECT bar FROM table WHERE foo = needle;
This reduces the data traffic with the mysql server. Additionally, it
work-arounds some issues in Qt SQL, which lead to bad performance:
QSqlResult::value incurs multiple temporary allocations, and string
conversions, even to read a simple integer ID for example. Finally,
by reusing an externally provided QString name e.g., we can leverage
Qt's implicit sharing, instead of duplicating the string in a separate
QString instance, with the contents read from SQL server.
REVIEW: 121310
---
server/src/storage/entities.xsl | 50 +++++++++++++++++++++++++++++------------
1 file changed, 36 insertions(+), 14 deletions(-)
diff --git a/server/src/storage/entities.xsl b/server/src/storage/entities.xsl
index 9471293..c8fb1fd 100644
--- a/server/src/storage/entities.xsl
+++ b/server/src/storage/entities.xsl
@@ -104,6 +104,12 @@ Q_DECLARE_TYPEINFO( Akonadi::Server::<xsl:value-of select="@name"/>, Q_MOVABLE_T
using namespace Akonadi::Server;
+static QStringList removeEntry(QStringList list, const QString&amp; entry)
+{
+ list.removeOne(entry);
+ return list;
+}
+
<xsl:for-each select="database/table">
<xsl:call-template name="table-source"/>
</xsl:for-each>
@@ -179,7 +185,8 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
return <xsl:value-of select="$className"/>();
QueryBuilder qb( tableName(), QueryBuilder::Select );
- qb.addColumns( columnNames() );
+ static const QStringList columns = removeEntry(columnNames(), <xsl:value-of select="$key"/>Column());
+ qb.addColumns( columns );
qb.addValueCondition( <xsl:value-of select="$key"/>Column(), Query::Equals, <xsl:value-of select="$key"/> );
if ( !qb.exec() ) {
akDebug() &lt;&lt; "Error during selection of record with <xsl:value-of select="$key"/>"
@@ -191,21 +198,36 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
return <xsl:value-of select="$className"/>();
}
+ <!-- this indirection is required to prevent off-by-one access now that we skip the key column -->
+ int valueIndex = 0;
+ <xsl:for-each select="column">
+ const <xsl:value-of select="@type"/> value<xsl:value-of select="position()"/> =
+ <xsl:choose>
+ <xsl:when test="@name=$key">
+ <xsl:value-of select="$key"/>;
+ </xsl:when>
+ <xsl:otherwise>
+ (qb.query().isNull(valueIndex)) ?
+ <xsl:value-of select="@type"/>() :
+ <xsl:choose>
+ <xsl:when test="starts-with(@type,'QString')">
+ Utils::variantToString( qb.query().value( valueIndex ) )
+ </xsl:when>
+ <xsl:when test="starts-with(@type, 'Tristate')">
+ static_cast&lt;Tristate&gt;(qb.query().value( valueIndex ).value&lt;int&gt;())
+ </xsl:when>
+ <xsl:otherwise>
+ qb.query().value( valueIndex ).value&lt;<xsl:value-of select="@type"/>&gt;()
+ </xsl:otherwise>
+ </xsl:choose>
+ ; ++valueIndex;
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
<xsl:value-of select="$className"/> rv(
<xsl:for-each select="column">
- (qb.query().isNull(<xsl:value-of select="position() - 1"/>)) ?
- <xsl:value-of select="@type"/>() :
- <xsl:choose>
- <xsl:when test="starts-with(@type,'QString')">
- Utils::variantToString( qb.query().value( <xsl:value-of select="position() - 1"/> ) )
- </xsl:when>
- <xsl:when test="starts-with(@type, 'Tristate')">
- static_cast&lt;Tristate&gt;(qb.query().value( <xsl:value-of select="position() - 1"/> ).value&lt;int&gt;())
- </xsl:when>
- <xsl:otherwise>
- qb.query().value( <xsl:value-of select="position() - 1"/> ).value&lt;<xsl:value-of select="@type"/>&gt;()
- </xsl:otherwise>
- </xsl:choose>
+ value<xsl:value-of select="position()"/>
<xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
);
--
2.1.0

View file

@ -0,0 +1,262 @@
From 215b188d891d5236fe94131d176d7ddc3ae02d5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 5 Dec 2014 17:12:28 +0100
Subject: [PATCH 20/30] Avoid ridiculous amount of SQL queries by caching
PartTypes
PartTypes are identified by their FQ name, which is in form NAMESPACE:NAME,
where namespace and name are stored in individual columns. For this reason
the standard ::retrieveByName() and name cache generated from entities.xslt
does not work. This patch adds special handling for PartType table, so that
a special PartType::retrieveByFQName() method as well as PartType name cache
handling are generated during the XSL Transformation, allowing us to cache
all the PartTypes.
This reduces the amount of SQL queries by at least two for each single AKAPPEND,
MERGE, STORE and FETCH command, providing a nice performance boost during
sync.
---
server/src/handler/append.cpp | 4 ++--
server/src/storage/datastore.cpp | 4 +++-
server/src/storage/entities-header.xsl | 7 ++++++-
server/src/storage/entities-source.xsl | 31 ++++++++++++++++++++++++++++++-
server/src/storage/entities.xsl | 7 ++++++-
server/src/storage/parttypehelper.cpp | 29 +----------------------------
server/src/storage/parttypehelper.h | 13 -------------
7 files changed, 48 insertions(+), 47 deletions(-)
diff --git a/server/src/handler/append.cpp b/server/src/handler/append.cpp
index c503216..b594e27 100644
--- a/server/src/handler/append.cpp
+++ b/server/src/handler/append.cpp
@@ -134,7 +134,7 @@ bool Append::commit()
// wrap data into a part
Part part;
- part.setPartType( PartTypeHelper::fromName( "PLD", "RFC822" ) );
+ part.setPartType( PartType::retrieveByFQName( QLatin1String("PLD"), QLatin1String("RFC822") ) );
part.setData( m_data );
part.setPimItemId( item.id() );
part.setDatasize( dataSize );
@@ -148,7 +148,7 @@ bool Append::commit()
//akDebug() << "Append handler: doPreprocessing is" << doPreprocessing;
if ( doPreprocessing ) {
Part hiddenAttribute;
- hiddenAttribute.setPartType( PartTypeHelper::fromName( "ATR", "HIDDEN" ) );
+ hiddenAttribute.setPartType( PartType::retrieveByFQName( QLatin1String("ATR"), QLatin1String("HIDDEN") ) );
hiddenAttribute.setData( QByteArray() );
hiddenAttribute.setPimItemId( item.id() );
hiddenAttribute.setDatasize( 0 );
diff --git a/server/src/storage/datastore.cpp b/server/src/storage/datastore.cpp
index ae78bab..304f0e8 100644
--- a/server/src/storage/datastore.cpp
+++ b/server/src/storage/datastore.cpp
@@ -183,6 +183,7 @@ bool DataStore::init()
Flag::enableCache( true );
Resource::enableCache( true );
Collection::enableCache( true );
+ PartType::enableCache( true );
return true;
}
@@ -1025,7 +1026,8 @@ bool DataStore::unhideAllPimItems()
akDebug() << "DataStore::unhideAllPimItems()";
try {
- return PartHelper::remove( Part::partTypeIdFullColumnName(), PartTypeHelper::fromName( "ATR", "HIDDEN" ).id() );
+ return PartHelper::remove( Part::partTypeIdFullColumnName(),
+ PartType::retrieveByFQName( QLatin1String("ATR"), QLatin1String("HIDDEN") ).id() );
} catch ( ... ) {} // we can live with this failing
return false;
diff --git a/server/src/storage/entities-header.xsl b/server/src/storage/entities-header.xsl
index 4966966..d515fd3 100644
--- a/server/src/storage/entities-header.xsl
+++ b/server/src/storage/entities-header.xsl
@@ -133,11 +133,16 @@ class <xsl:value-of select="$className"/> : private Entity
<xsl:text>static </xsl:text><xsl:value-of select="$className"/> retrieveById( qint64 id );
</xsl:if>
- <xsl:if test="column[@name = 'name']">
+ <xsl:if test="column[@name = 'name'] and $className != 'PartType'">
/** Returns the record with name @p name. */
<xsl:text>static </xsl:text><xsl:value-of select="$className"/> retrieveByName( const <xsl:value-of select="column[@name = 'name']/@type"/> &amp;name );
</xsl:if>
+ <xsl:if test="column[@name = 'name'] and $className = 'PartType'">
+ <!-- Special case for PartTypes, which are identified by "NS:NAME" -->
+ <xsl:text>static PartType retrieveByFQName( const QString &amp;ns, const QString &amp;name );</xsl:text>
+ </xsl:if>
+
/** Retrieve all records from this table. */
static <xsl:value-of select="$className"/>::List retrieveAll();
/** Retrieve all records with value @p value in column @p key. */
diff --git a/server/src/storage/entities-source.xsl b/server/src/storage/entities-source.xsl
index e398da5..46ef3a6 100644
--- a/server/src/storage/entities-source.xsl
+++ b/server/src/storage/entities-source.xsl
@@ -130,7 +130,15 @@ void <xsl:value-of select="$className"/>::Private::addToCache( const <xsl:value-
idCache.insert( entry.id(), entry );
</xsl:if>
<xsl:if test="column[@name = 'name']">
+ <xsl:choose>
+ <xsl:when test="$className = 'PartType'">
+ <!-- special case for PartType, which is identified as "NS:NAME" -->
+ nameCache.insert( entry.ns() + QLatin1Char(':') + entry.name(), entry );
+ </xsl:when>
+ <xsl:otherwise>
nameCache.insert( entry.name(), entry );
+ </xsl:otherwise>
+ </xsl:choose>
</xsl:if>
}
@@ -323,7 +331,7 @@ QVector&lt; <xsl:value-of select="$className"/> &gt; <xsl:value-of select="$clas
}
</xsl:if>
-<xsl:if test="column[@name = 'name']">
+<xsl:if test="column[@name = 'name'] and $className != 'PartType'">
<xsl:value-of select="$className"/><xsl:text> </xsl:text><xsl:value-of select="$className"/>::retrieveByName( const <xsl:value-of select="column[@name = 'name']/@type"/> &amp;name )
{
<xsl:call-template name="data-retrieval">
@@ -333,6 +341,19 @@ QVector&lt; <xsl:value-of select="$className"/> &gt; <xsl:value-of select="$clas
}
</xsl:if>
+<xsl:if test="column[@name = 'name'] and $className = 'PartType'">
+<xsl:text>PartType PartType::retrieveByFQName( const QString &amp; ns, const QString &amp; name )</xsl:text>
+{
+ const QString fqname = ns + QLatin1Char(':') + name;
+ <xsl:call-template name="data-retrieval">
+ <xsl:with-param name="key">ns</xsl:with-param>
+ <xsl:with-param name="key2">name</xsl:with-param>
+ <xsl:with-param name="lookupKey">fqname</xsl:with-param>
+ <xsl:with-param name="cache">nameCache</xsl:with-param>
+ </xsl:call-template>
+}
+</xsl:if>
+
QVector&lt;<xsl:value-of select="$className"/>&gt; <xsl:value-of select="$className"/>::retrieveAll()
{
QSqlDatabase db = DataStore::self()->database();
@@ -588,7 +609,15 @@ void <xsl:value-of select="$className"/>::invalidateCache() const
Private::idCache.remove( id() );
</xsl:if>
<xsl:if test="column[@name = 'name']">
+ <xsl:choose>
+ <xsl:when test="$className = 'PartType'">
+ <!-- Special handling for PartType, which is identified as "NS:NAME" -->
+ Private::nameCache.remove( ns() + QLatin1Char(':') + name() );
+ </xsl:when>
+ <xsl:otherwise>
Private::nameCache.remove( name() );
+ </xsl:otherwise>
+ </xsl:choose>
</xsl:if>
}
}
diff --git a/server/src/storage/entities.xsl b/server/src/storage/entities.xsl
index c8fb1fd..2cf96c4 100644
--- a/server/src/storage/entities.xsl
+++ b/server/src/storage/entities.xsl
@@ -169,12 +169,14 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
<!-- data retrieval for a given key field -->
<xsl:template name="data-retrieval">
<xsl:param name="key"/>
+<xsl:param name="key2"/>
+<xsl:param name="lookupKey" select="$key"/>
<xsl:param name="cache"/>
<xsl:variable name="className"><xsl:value-of select="@name"/></xsl:variable>
<xsl:if test="$cache != ''">
if ( Private::cacheEnabled ) {
QMutexLocker lock(&amp;Private::cacheMutex);
- QHash&lt;<xsl:value-of select="column[@name = $key]/@type"/>, <xsl:value-of select="$className"/>&gt;::const_iterator it = Private::<xsl:value-of select="$cache"/>.constFind(<xsl:value-of select="$key"/>);
+ QHash&lt;<xsl:value-of select="column[@name = $key]/@type"/>, <xsl:value-of select="$className"/>&gt;::const_iterator it = Private::<xsl:value-of select="$cache"/>.constFind(<xsl:value-of select="$lookupKey"/>);
if ( it != Private::<xsl:value-of select="$cache"/>.constEnd() ) {
return it.value();
}
@@ -188,6 +190,9 @@ set<xsl:value-of select="$methodName"/>( <xsl:call-template name="argument"/> )
static const QStringList columns = removeEntry(columnNames(), <xsl:value-of select="$key"/>Column());
qb.addColumns( columns );
qb.addValueCondition( <xsl:value-of select="$key"/>Column(), Query::Equals, <xsl:value-of select="$key"/> );
+ <xsl:if test="$key2 != ''">
+ qb.addValueCondition( <xsl:value-of select="$key2"/>Column(), Query::Equals, <xsl:value-of select="$key2"/> );
+ </xsl:if>
if ( !qb.exec() ) {
akDebug() &lt;&lt; "Error during selection of record with <xsl:value-of select="$key"/>"
&lt;&lt; <xsl:value-of select="$key"/> &lt;&lt; "from table" &lt;&lt; tableName()
diff --git a/server/src/storage/parttypehelper.cpp b/server/src/storage/parttypehelper.cpp
index b73dcd5..7654108 100644
--- a/server/src/storage/parttypehelper.cpp
+++ b/server/src/storage/parttypehelper.cpp
@@ -37,7 +37,7 @@ QPair< QString, QString > PartTypeHelper::parseFqName(const QString& fqName)
PartType PartTypeHelper::fromFqName(const QString& fqName)
{
const QPair<QString, QString> p = parseFqName( fqName );
- return fromName( p.first, p.second );
+ return PartType::retrieveByFQName(p.first, p.second);
}
PartType PartTypeHelper::fromFqName(const QByteArray& fqName)
@@ -45,33 +45,6 @@ PartType PartTypeHelper::fromFqName(const QByteArray& fqName)
return fromFqName( QLatin1String(fqName) );
}
-PartType PartTypeHelper::fromName(const QString& ns, const QString& typeName)
-{
- SelectQueryBuilder<PartType> qb;
- qb.addValueCondition( PartType::nsColumn(), Query::Equals, ns );
- qb.addValueCondition( PartType::nameColumn(), Query::Equals, typeName );
- if ( !qb.exec() )
- throw PartTypeException( "Unable to query part type table." );
- const PartType::List result = qb.result();
- if ( result.size() == 1 )
- return result.first();
- if ( result.size() > 1 )
- throw PartTypeException( "Part type uniqueness constraint violation." );
-
- // doesn't exist yet, so let's create a new one
- PartType type;
- type.setName( typeName );
- type.setNs( ns );
- if ( !type.insert() )
- throw PartTypeException( "Creating a new part type failed." );
- return type;
-}
-
-PartType PartTypeHelper::fromName(const char* ns, const char* typeName)
-{
- return fromName( QLatin1String(ns), QLatin1String(typeName) );
-}
-
Query::Condition PartTypeHelper::conditionFromFqName(const QString& fqName)
{
const QPair<QString, QString> p = parseFqName( fqName );
diff --git a/server/src/storage/parttypehelper.h b/server/src/storage/parttypehelper.h
index 38cb858..4c4f42f 100644
--- a/server/src/storage/parttypehelper.h
+++ b/server/src/storage/parttypehelper.h
@@ -48,19 +48,6 @@ namespace PartTypeHelper
PartType fromFqName( const QByteArray &fqName );
/**
- * Retrieve (or create) PartType for the given namespace and type name.
- * @param ns Namespace
- * @param typeName Part type name.
- * @throws PartTypeException
- */
- PartType fromName( const QString &ns, const QString &typeName );
-
- /**
- * Convenience overload of the above.
- */
- PartType fromName( const char *ns, const char *typeName );
-
- /**
* Returns a query condition that matches the given part.
* @param fqName fully-qualified part type name
* @throws PartTypeException
--
2.1.0

View file

@ -0,0 +1,241 @@
From 9698d589e4c2b489f406fe1a823d4bb42c322f71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 5 Dec 2014 18:21:18 +0100
Subject: [PATCH 21/30] Implement support for CASE...WHEN...THEN SQL statements
SELECT columns
CASE...WHEN...THEN is a useful construct especially for aggregation
queries.
---
server/src/storage/query.cpp | 38 ++++++++++++++++++++++++++++++
server/src/storage/query.h | 19 +++++++++++++++
server/src/storage/querybuilder.cpp | 30 +++++++++++++++++++++++
server/src/storage/querybuilder.h | 14 +++++++++++
server/tests/unittest/querybuildertest.cpp | 38 +++++++++++++++++++++++++++++-
5 files changed, 138 insertions(+), 1 deletion(-)
diff --git a/server/src/storage/query.cpp b/server/src/storage/query.cpp
index 6fb6c6e..c938ade 100644
--- a/server/src/storage/query.cpp
+++ b/server/src/storage/query.cpp
@@ -68,3 +68,41 @@ void Query::Condition::addCondition( const Condition &condition )
{
mSubConditions << condition;
}
+
+
+Case::Case(const Condition &when, const QString &then, const QString &elseBranch)
+{
+ addCondition(when, then);
+ setElse(elseBranch);
+}
+
+Case::Case(const QString &column, CompareOperator op, const QVariant &value, const QString &when, const QString &elseBranch)
+{
+ addValueCondition(column, op, value, when);
+ setElse(elseBranch);
+}
+
+void Case::addCondition(const Condition &when, const QString &then)
+{
+ mWhenThen.append(qMakePair(when, then));
+}
+
+void Case::addValueCondition(const QString &column, CompareOperator op, const QVariant &value, const QString &then)
+{
+ Condition when;
+ when.addValueCondition(column, op, value);
+ addCondition(when, then);
+}
+
+void Case::addColumnCondition(const QString &column, CompareOperator op, const QString &column2, const QString &then)
+{
+ Condition when;
+ when.addColumnCondition(column, op, column2);
+ addCondition(when, then);
+}
+
+void Case::setElse(const QString &elseBranch)
+{
+ mElse = elseBranch;
+}
+
diff --git a/server/src/storage/query.h b/server/src/storage/query.h
index f4f1ac0..c8f35a7 100644
--- a/server/src/storage/query.h
+++ b/server/src/storage/query.h
@@ -130,6 +130,25 @@ class Condition
}; // class Condition
+
+class Case
+{
+ friend class Akonadi::Server::QueryBuilder;
+ public:
+ Case(const Condition &when, const QString &then, const QString &elseBranch = QString());
+ Case(const QString &column, Query::CompareOperator op, const QVariant &value, const QString &when, const QString &elseBranch = QString());
+
+ void addCondition(const Condition &when, const QString &then);
+ void addValueCondition(const QString &column, Query::CompareOperator op, const QVariant &value, const QString &then);
+ void addColumnCondition(const QString &column, Query::CompareOperator op, const QString &column2, const QString &then);
+
+ void setElse(const QString &elseBranch);
+
+ private:
+ QVector<QPair<Condition, QString> > mWhenThen;
+ QString mElse;
+};
+
} // namespace Query
} // namespace Server
} // namespace Akonadi
diff --git a/server/src/storage/querybuilder.cpp b/server/src/storage/querybuilder.cpp
index 3017867..74ed2da 100644
--- a/server/src/storage/querybuilder.cpp
+++ b/server/src/storage/querybuilder.cpp
@@ -457,11 +457,27 @@ void QueryBuilder::addColumn( const QString &col )
mColumns << col;
}
+void QueryBuilder::addColumn( const Query::Case &caseStmt )
+{
+ QString query;
+ buildCaseStatement(&query, caseStmt);
+ mColumns.append(query);
+}
+
void QueryBuilder::addAggregation( const QString &col, const QString &aggregate )
{
mColumns.append( aggregate + QLatin1Char( '(' ) + col + QLatin1Char( ')' ) );
}
+void QueryBuilder::addAggregation(const Query::Case &caseStmt, const QString &aggregate)
+{
+ QString query(aggregate + QLatin1Char('('));
+ buildCaseStatement(&query, caseStmt);
+ query += QLatin1Char(')');
+
+ mColumns.append(query);
+}
+
void QueryBuilder::bindValue( QString *query, const QVariant &value )
{
mBindValues << value;
@@ -510,6 +526,20 @@ void QueryBuilder::buildWhereCondition( QString *query, const Query::Condition &
}
}
+void QueryBuilder::buildCaseStatement(QString *query, const Query::Case &caseStmt)
+{
+ *query += QLatin1String("CASE ");
+ for (const auto whenThen : caseStmt.mWhenThen) {
+ *query += QLatin1String("WHEN ");
+ buildWhereCondition(query, whenThen.first); // When
+ *query += QLatin1String(" THEN ") + whenThen.second; // then
+ }
+ if (!caseStmt.mElse.isEmpty()) {
+ *query += QLatin1String(" ELSE ") + caseStmt.mElse;
+ }
+ *query += QLatin1String(" END");
+}
+
void QueryBuilder::setSubQueryMode( Query::LogicOperator op, ConditionType type )
{
Q_ASSERT( type == WhereCondition || ( type == HavingCondition && mType == Select ) );
diff --git a/server/src/storage/querybuilder.h b/server/src/storage/querybuilder.h
index df7c362..0304108 100644
--- a/server/src/storage/querybuilder.h
+++ b/server/src/storage/querybuilder.h
@@ -123,6 +123,12 @@ class QueryBuilder
void addColumn( const QString &col );
/**
+ * Adds the given case statement to a select query.
+ * @param caseStmt The case statement to add.
+ */
+ void addColumn( const Query::Case &caseStmt );
+
+ /**
* Adds an aggregation statement.
* @param col The column to aggregate on
* @param aggregate The aggregation function.
@@ -130,6 +136,13 @@ class QueryBuilder
void addAggregation( const QString &col, const QString &aggregate );
/**
+ * Adds and aggregation statement with CASE
+ * @param caseStmt The case statement to aggregate on
+ * @param aggregate The aggregation function.
+ */
+ void addAggregation( const Query::Case &caseStmt, const QString &aggregate );
+
+ /**
Add a WHERE or HAVING condition which compares a column with a given value.
@param column The column that should be compared.
@param op The operator used for comparison
@@ -239,6 +252,7 @@ class QueryBuilder
void buildQuery( QString *query );
void bindValue( QString *query, const QVariant &value );
void buildWhereCondition( QString *query, const Query::Condition &cond );
+ void buildCaseStatement( QString *query, const Query::Case &caseStmt );
/**
* SQLite does not support JOINs with UPDATE, so we have to convert it into
diff --git a/server/tests/unittest/querybuildertest.cpp b/server/tests/unittest/querybuildertest.cpp
index 92df2a2..848829d 100644
--- a/server/tests/unittest/querybuildertest.cpp
+++ b/server/tests/unittest/querybuildertest.cpp
@@ -217,6 +217,42 @@ void QueryBuilderTest::testQueryBuilder_data()
}
{
+ /// SELECT with CASE
+ QueryBuilder qbTpl = QueryBuilder("table1", QueryBuilder::Select );
+ qbTpl.setDatabaseType( DbType::MySQL );
+
+ QueryBuilder qb = qbTpl;
+ qb.addColumn( "col" );
+ qb.addColumn( Query::Case( "col1", Query::Greater, 42, "1", "0" ) );
+ bindVals.clear();
+ bindVals << 42;
+ mBuilders << qb;
+ QTest::newRow( "select case simple") << mBuilders.count()
+ << QString( "SELECT col, CASE WHEN ( col1 > :0 ) THEN 1 ELSE 0 END FROM table1" ) << bindVals;
+
+
+ qb = qbTpl;
+ qb.addAggregation( "table1.col1", "sum" );
+ qb.addAggregation( "table1.col2", "count" );
+ Query::Condition cond( Query::Or );
+ cond.addValueCondition( "table3.col2", Query::Equals, "value1" );
+ cond.addValueCondition( "table3.col2", Query::Equals, "value2" );\
+ Query::Case caseStmt( cond, "1", "0" );
+ qb.addAggregation( caseStmt, "sum" );
+ qb.addJoin( QueryBuilder::LeftJoin, "table2", "table1.col3", "table2.col1" );
+ qb.addJoin( QueryBuilder::LeftJoin, "table3", "table2.col2", "table3.col1" );
+ bindVals.clear();
+ bindVals << QString("value1") << QString("value2");
+ mBuilders <<qb;
+ QTest::newRow( "select case, aggregation and joins" ) << mBuilders.count()
+ << QString( "SELECT sum(table1.col1), count(table1.col2), sum(CASE WHEN ( table3.col2 = :0 OR table3.col2 = :1 ) THEN 1 ELSE 0 END) "
+ "FROM table1 "
+ "LEFT JOIN table2 ON ( table1.col3 = table2.col1 ) "
+ "LEFT JOIN table3 ON ( table2.col2 = table3.col1 )")
+ << bindVals;
+ }
+
+ {
/// UPDATE with INNER JOIN
QueryBuilder qbTpl = QueryBuilder( "table1", QueryBuilder::Update );
qbTpl.setColumnValue( "col", 42 );
@@ -310,4 +346,4 @@ void QueryBuilderTest::benchQueryBuilder()
}
QVERIFY(executed);
-}
\ No newline at end of file
+}
--
2.1.0

View file

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

View file

@ -0,0 +1,105 @@
From 1ce732668b2b3e4d735665bd60e1a18f139b1de2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 5 Dec 2014 18:49:15 +0100
Subject: [PATCH 23/30] Always create a new PartType when it does not exist
Fixes a regression introduced in previous commit that caused Part operations
to fail when specified PartType did not exist in Akonadi storage yet.
---
server/src/handler/append.cpp | 4 ++--
server/src/storage/datastore.cpp | 2 +-
server/src/storage/parttypehelper.cpp | 16 +++++++++++++++-
server/src/storage/parttypehelper.h | 8 ++++++++
4 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/server/src/handler/append.cpp b/server/src/handler/append.cpp
index b594e27..15fb9ea 100644
--- a/server/src/handler/append.cpp
+++ b/server/src/handler/append.cpp
@@ -134,7 +134,7 @@ bool Append::commit()
// wrap data into a part
Part part;
- part.setPartType( PartType::retrieveByFQName( QLatin1String("PLD"), QLatin1String("RFC822") ) );
+ part.setPartType( PartTypeHelper::fromFqName( QLatin1String("PLD"), QLatin1String("RFC822") ) );
part.setData( m_data );
part.setPimItemId( item.id() );
part.setDatasize( dataSize );
@@ -148,7 +148,7 @@ bool Append::commit()
//akDebug() << "Append handler: doPreprocessing is" << doPreprocessing;
if ( doPreprocessing ) {
Part hiddenAttribute;
- hiddenAttribute.setPartType( PartType::retrieveByFQName( QLatin1String("ATR"), QLatin1String("HIDDEN") ) );
+ hiddenAttribute.setPartType( PartTypeHelper::fromFqName( QLatin1String("ATR"), QLatin1String("HIDDEN") ) );
hiddenAttribute.setData( QByteArray() );
hiddenAttribute.setPimItemId( item.id() );
hiddenAttribute.setDatasize( 0 );
diff --git a/server/src/storage/datastore.cpp b/server/src/storage/datastore.cpp
index 0983d84..c9fa0c3 100644
--- a/server/src/storage/datastore.cpp
+++ b/server/src/storage/datastore.cpp
@@ -1027,7 +1027,7 @@ bool DataStore::unhideAllPimItems()
try {
return PartHelper::remove( Part::partTypeIdFullColumnName(),
- PartType::retrieveByFQName( QLatin1String("ATR"), QLatin1String("HIDDEN") ).id() );
+ PartTypeHelper::fromFqName( QLatin1String("ATR"), QLatin1String("HIDDEN") ).id() );
} catch ( ... ) {} // we can live with this failing
return false;
diff --git a/server/src/storage/parttypehelper.cpp b/server/src/storage/parttypehelper.cpp
index 7654108..bcff9c6 100644
--- a/server/src/storage/parttypehelper.cpp
+++ b/server/src/storage/parttypehelper.cpp
@@ -37,7 +37,8 @@ QPair< QString, QString > PartTypeHelper::parseFqName(const QString& fqName)
PartType PartTypeHelper::fromFqName(const QString& fqName)
{
const QPair<QString, QString> p = parseFqName( fqName );
- return PartType::retrieveByFQName(p.first, p.second);
+ return fromFqName(p.first, p.second);
+
}
PartType PartTypeHelper::fromFqName(const QByteArray& fqName)
@@ -45,6 +46,19 @@ PartType PartTypeHelper::fromFqName(const QByteArray& fqName)
return fromFqName( QLatin1String(fqName) );
}
+PartType PartTypeHelper::fromFqName(const QString& ns, const QString& name)
+{
+ PartType partType = PartType::retrieveByFQName(ns, name);
+ if (!partType.isValid()) {
+ PartType pt(name, ns);
+ if (!pt.insert()) {
+ throw PartTypeException( "Failed to append part type" );
+ }
+ partType = pt;
+ }
+ return partType;
+}
+
Query::Condition PartTypeHelper::conditionFromFqName(const QString& fqName)
{
const QPair<QString, QString> p = parseFqName( fqName );
diff --git a/server/src/storage/parttypehelper.h b/server/src/storage/parttypehelper.h
index 4c4f42f..6d3cf74 100644
--- a/server/src/storage/parttypehelper.h
+++ b/server/src/storage/parttypehelper.h
@@ -48,6 +48,14 @@ namespace PartTypeHelper
PartType fromFqName( const QByteArray &fqName );
/**
+ * Retrieve (or create) PartType for the given namespace and name
+ * @param ns Namespace
+ * @param name Name
+ * @throws PartTypeException
+ */
+ PartType fromFqName( const QString &ns, const QString &name );
+
+ /**
* Returns a query condition that matches the given part.
* @param fqName fully-qualified part type name
* @throws PartTypeException
--
2.1.0

View file

@ -0,0 +1,25 @@
From fcae659e9be22b00b0efe52f19a89b8fce26a925 Mon Sep 17 00:00:00 2001
From: David Faure <faure@kde.org>
Date: Sat, 6 Dec 2014 11:35:16 +0100
Subject: [PATCH 24/30] Fix compilation with strict iterators
---
server/src/storage/collectionstatistics.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/src/storage/collectionstatistics.cpp b/server/src/storage/collectionstatistics.cpp
index 85ee449..b2c6915 100644
--- a/server/src/storage/collectionstatistics.cpp
+++ b/server/src/storage/collectionstatistics.cpp
@@ -52,7 +52,7 @@ const CollectionStatistics::Statistics& CollectionStatistics::statistics(const C
{
QMutexLocker lock(&mCacheLock);
auto it = mCache.find(col.id());
- if (it == mCache.constEnd()) {
+ if (it == mCache.end()) {
it = mCache.insert(col.id(), getCollectionStatistics(col));
}
return it.value();
--
2.1.0

View file

@ -0,0 +1,58 @@
From 55dc6d141a20e2438308214ab60c18e282dd7b43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 8 Dec 2014 10:33:51 +0100
Subject: [PATCH 25/30] Avoid repeated calls to PimItem::flags() and
PimItem::tags()
The queries results are not cached, so each call to those methods runs an SQL
query. At least in case of flags, this reduced the number of queries to one
query per changed item.
---
server/src/storage/datastore.cpp | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/server/src/storage/datastore.cpp b/server/src/storage/datastore.cpp
index c9fa0c3..035395e 100644
--- a/server/src/storage/datastore.cpp
+++ b/server/src/storage/datastore.cpp
@@ -220,7 +220,8 @@ bool DataStore::setItemsFlags( const PimItem::List &items, const QVector<Flag> &
setBoolPtr( flagsChanged, false );
Q_FOREACH ( const PimItem &item, items ) {
- Q_FOREACH ( const Flag &flag, item.flags() ) {
+ const Flag::List itemFlags = item.flags();
+ Q_FOREACH ( const Flag &flag, itemFlags ) {
if ( !flags.contains( flag ) ) {
removedFlags << flag.name().toLatin1();
Query::Condition cond;
@@ -231,7 +232,7 @@ bool DataStore::setItemsFlags( const PimItem::List &items, const QVector<Flag> &
}
Q_FOREACH ( const Flag &flag, flags ) {
- if ( !item.flags().contains( flag ) ) {
+ if ( !itemFlags.contains( flag ) ) {
addedFlags << flag.name().toLatin1();
insIds << item.id();
insFlags << flag.id();
@@ -414,7 +415,8 @@ bool DataStore::setItemsTags( const PimItem::List &items, const Tag::List &tags,
setBoolPtr( tagsChanged, false );
Q_FOREACH ( const PimItem &item, items ) {
- Q_FOREACH ( const Tag &tag, item.tags() ) {
+ const Tag::List itemTags = item.tags();
+ Q_FOREACH ( const Tag &tag, itemTags ) {
if ( !tags.contains( tag ) ) {
// Remove tags from items that had it set
removedTags << tag.id();
@@ -426,7 +428,7 @@ bool DataStore::setItemsTags( const PimItem::List &items, const Tag::List &tags,
}
Q_FOREACH ( const Tag &tag, tags ) {
- if ( !item.tags().contains( tag ) ) {
+ if ( !itemTags.contains( tag ) ) {
// Add tags to items that did not have the tag
addedTags << tag.id();
insIds << item.id();
--
2.1.0

View file

@ -0,0 +1,374 @@
From 059d52845cbbc10e882764f64245c5995af4e741 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 8 Dec 2014 13:49:27 +0100
Subject: [PATCH 26/30] Avoid recursive collection listing in SearchHelper
The recursive listing generates one SQL query per collection, and since search
is invoked rather often (basically whenever you open an email in KMail), we get
lots of unnecessary queries. This new algorithm does one query to fetch all
folders with matching mime types, and only falls back to query the parent chain
when the requested ancestor is not 0 (root), or when the result collection is
not a direct descendant of the requested ancestor.
---
server/src/handler/search.cpp | 4 +-
server/src/handler/searchhelper.cpp | 111 ++++++++++++++----------
server/src/handler/searchhelper.h | 2 +-
server/src/search/searchmanager.cpp | 2 +-
server/tests/unittest/CMakeLists.txt | 2 +
server/tests/unittest/searchtest.cpp | 158 +++++++++++++++++++++++++++++++++++
6 files changed, 230 insertions(+), 49 deletions(-)
create mode 100644 server/tests/unittest/searchtest.cpp
diff --git a/server/src/handler/search.cpp b/server/src/handler/search.cpp
index 06d172f..00484ff 100644
--- a/server/src/handler/search.cpp
+++ b/server/src/handler/search.cpp
@@ -95,9 +95,7 @@ bool Search::parseStream()
}
if ( recursive ) {
- Q_FOREACH ( qint64 collection, collectionIds ) {
- collections << SearchHelper::listCollectionsRecursive( QVector<qint64>() << collection, mimeTypes );
- }
+ collections << SearchHelper::matchSubcollectionsByMimeType( collectionIds, mimeTypes );
} else {
collections = collectionIds;
}
diff --git a/server/src/handler/searchhelper.cpp b/server/src/handler/searchhelper.cpp
index aa6694d..1a06c0e 100644
--- a/server/src/handler/searchhelper.cpp
+++ b/server/src/handler/searchhelper.cpp
@@ -20,6 +20,7 @@
#include "searchhelper.h"
#include "storage/countquerybuilder.h"
+#include <storage/queryhelper.h>
#include "entities.h"
#include <libs/protocol_p.h>
@@ -89,55 +90,77 @@ QString SearchHelper::extractMimetype( const QList<QByteArray> &junks, int start
}
-QVector<qint64> SearchHelper::listCollectionsRecursive( const QVector<qint64> &ancestors, const QStringList &mimeTypes )
+static qint64 parentCollectionId(qint64 collectionId)
{
- QVector<qint64> recursiveChildren;
- Q_FOREACH ( qint64 ancestor, ancestors ) {
- QVector<qint64> searchChildren;
-
- { // Free the query before entering recursion to prevent too many opened connections
-
- Query::Condition mimeTypeCondition;
- mimeTypeCondition.addColumnCondition( CollectionMimeTypeRelation::rightFullColumnName(), Query::Equals, MimeType::idFullColumnName() );
- // Exclude top-level collections and collections that cannot have items!
- mimeTypeCondition.addValueCondition( MimeType::nameFullColumnName(), Query::NotEquals, QLatin1String( "inode/directory" ) );
- if ( !mimeTypes.isEmpty() ) {
- mimeTypeCondition.addValueCondition( MimeType::nameFullColumnName(), Query::In, mimeTypes );
- }
+ QueryBuilder qb(Collection::tableName(), QueryBuilder::Select);
+ qb.addColumn(Collection::parentIdColumn());
+ qb.addValueCondition(Collection::idColumn(), Query::Equals, collectionId);
+ if (!qb.exec()) {
+ return -1;
+ }
+ if (!qb.query().next()) {
+ return -1;
+ }
+ return qb.query().value(0).toLongLong();
+}
- CountQueryBuilder qb( Collection::tableName(), MimeType::nameFullColumnName(), CountQueryBuilder::All );
- qb.addColumn( Collection::idFullColumnName() );
- qb.addJoin( QueryBuilder::LeftJoin, CollectionMimeTypeRelation::tableName(), CollectionMimeTypeRelation::leftFullColumnName(), Collection::idFullColumnName() );
- qb.addJoin( QueryBuilder::LeftJoin, MimeType::tableName(), mimeTypeCondition );
- if ( ancestor == 0 ) {
- qb.addValueCondition( Collection::parentIdFullColumnName(), Query::Is, QVariant() );
- } else {
- // Also include current ancestor's result, so that we know whether we should search in the ancestor too
- Query::Condition idCond( Query::Or );
- idCond.addValueCondition( Collection::parentIdFullColumnName(), Query::Equals, ancestor );
- idCond.addValueCondition( Collection::idFullColumnName(), Query::Equals, ancestor );
- qb.addCondition( idCond );
- }
- qb.addValueCondition( Collection::isVirtualFullColumnName(), Query::Equals, false );
- qb.addGroupColumn( Collection::idFullColumnName() );
- qb.exec();
-
- QSqlQuery query = qb.query();
- while ( query.next() ) {
- const qint64 id = query.value( 1 ).toLongLong();
- // Don't add ancestor into search children, we are resolving it right now
- if ( id != ancestor ) {
- searchChildren << id;
+
+QVector<qint64> SearchHelper::matchSubcollectionsByMimeType(const QVector<qint64> &ancestors, const QStringList &mimeTypes)
+{
+ // Get all collections with given mime types
+ QueryBuilder qb(Collection::tableName(), QueryBuilder::Select);
+ qb.setDistinct(true);
+ qb.addColumn(Collection::idFullColumnName());
+ qb.addColumn(Collection::parentIdFullColumnName());
+ qb.addJoin(QueryBuilder::LeftJoin, CollectionMimeTypeRelation::tableName(),
+ CollectionMimeTypeRelation::leftFullColumnName(), Collection::idFullColumnName());
+ qb.addJoin(QueryBuilder::LeftJoin, MimeType::tableName(),
+ CollectionMimeTypeRelation::rightFullColumnName(), MimeType::idFullColumnName());
+ Query::Condition cond(Query::Or);
+ Q_FOREACH (const QString &mt, mimeTypes) {
+ cond.addValueCondition(MimeType::nameFullColumnName(), Query::Equals, mt);
+ }
+ qb.addCondition(cond);
+
+ if (!qb.exec()) {
+ qWarning() << "Failed to query search collections";
+ return QVector<qint64>();
+ }
+
+ QMap<qint64 /* parentId */, QVector<qint64> /* collectionIds */> candidateCollections;
+ while (qb.query().next()) {
+ candidateCollections[qb.query().value(1).toLongLong()].append(qb.query().value(0).toLongLong());
+ }
+
+ // If the ancestors list contains root, then return what we got, since everything
+ // is sub collection of root
+ QVector<qint64> results;
+ if (ancestors.contains(0)) {
+ Q_FOREACH (const QVector<qint64> &res, candidateCollections.values()) {
+ results += res;
}
- if ( query.value( 0 ).toInt() > 0 ) { // count( mimeTypeTable.name ) > 0
- recursiveChildren << id;
+ return results;
+ }
+
+ // Try to resolve direct descendants
+ Q_FOREACH (qint64 ancestor, ancestors) {
+ const QVector<qint64> cols = candidateCollections.take(ancestor);
+ if (!cols.isEmpty()) {
+ results += cols;
}
- }
}
- if ( !searchChildren.isEmpty() ) {
- recursiveChildren << listCollectionsRecursive( searchChildren, mimeTypes );
+
+ for (auto iter = candidateCollections.begin(); iter != candidateCollections.end(); ++iter) {
+ // Traverse the collection chain up to root
+ qint64 parentId = iter.key();
+ while (!ancestors.contains(parentId) && parentId > 0) {
+ parentId = parentCollectionId(parentId);
+ }
+ // Ok, we found a requested ancestor in the parent chain
+ if (parentId > 0) {
+ results += iter.value();
+ }
}
- }
- return recursiveChildren;
+ return results;
}
diff --git a/server/src/handler/searchhelper.h b/server/src/handler/searchhelper.h
index a64bb61..1595501 100644
--- a/server/src/handler/searchhelper.h
+++ b/server/src/handler/searchhelper.h
@@ -33,7 +33,7 @@ class SearchHelper
public:
static QList<QByteArray> splitLine( const QByteArray &line );
static QString extractMimetype( const QList<QByteArray> &junks, int start );
- static QVector<qint64> listCollectionsRecursive( const QVector<qint64> &ancestors, const QStringList &mimeTypes );
+ static QVector<qint64> matchSubcollectionsByMimeType( const QVector<qint64> &ancestors, const QStringList &mimeTypes );
};
} // namespace Server
diff --git a/server/src/search/searchmanager.cpp b/server/src/search/searchmanager.cpp
index c821aa3..b940fcc 100644
--- a/server/src/search/searchmanager.cpp
+++ b/server/src/search/searchmanager.cpp
@@ -296,7 +296,7 @@ void SearchManager::updateSearchImpl( const Collection &collection, QWaitConditi
}
if ( recursive ) {
- queryCollections = SearchHelper::listCollectionsRecursive( queryAncestors, queryMimeTypes );
+ queryCollections = SearchHelper::matchSubcollectionsByMimeType( queryAncestors, queryMimeTypes );
} else {
queryCollections = queryAncestors;
}
diff --git a/server/tests/unittest/CMakeLists.txt b/server/tests/unittest/CMakeLists.txt
index b9744d9..acdc180 100644
--- a/server/tests/unittest/CMakeLists.txt
+++ b/server/tests/unittest/CMakeLists.txt
@@ -77,3 +77,5 @@ add_server_test(listhandlertest.cpp akonadiprivate)
add_server_test(modifyhandlertest.cpp akonadiprivate)
add_server_test(createhandlertest.cpp akonadiprivate)
add_server_test(collectionreferencetest.cpp akonadiprivate)
+
+add_server_test(searchtest.cpp akonadiprivate)
\ No newline at end of file
diff --git a/server/tests/unittest/searchtest.cpp b/server/tests/unittest/searchtest.cpp
new file mode 100644
index 0000000..f523b09
--- /dev/null
+++ b/server/tests/unittest/searchtest.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * 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) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "fakeakonadiserver.h"
+#include "searchhelper.h"
+#include "akdebug.h"
+#include "aktest.h"
+
+#include <entities.h>
+
+#include <QTest>
+
+using namespace Akonadi::Server;
+
+Q_DECLARE_METATYPE(QList<qint64>)
+Q_DECLARE_METATYPE(QList<QString>)
+
+
+class SearchTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ SearchTest()
+ : QObject()
+ {
+ try {
+ FakeAkonadiServer::instance()->setPopulateDb(false);
+ FakeAkonadiServer::instance()->init();
+ } catch (const FakeAkonadiServerException &e) {
+ akError() << "Server exception: " << e.what();
+ akFatal() << "Fake Akonadi Server failed to start up, aborting test";
+ }
+ }
+
+ ~SearchTest()
+ {
+ FakeAkonadiServer::instance()->quit();
+ }
+
+ Collection createCollection(const Resource &res, const QString &name, const Collection &parent, const QStringList &mimetypes)
+ {
+ Collection col;
+ col.setName(name);
+ col.setResource(res);
+ col.setParentId(parent.isValid() ? parent.id() : 0);
+ col.insert();
+ Q_FOREACH (const QString &mimeType, mimetypes) {
+ MimeType mt = MimeType::retrieveByName(mimeType);
+ if (!mt.isValid()) {
+ mt = MimeType(mimeType);
+ mt.insert();
+ }
+ col.addMimeType(mt);
+ }
+ return col;
+ }
+
+private Q_SLOTS:
+ void testSearchHelperCollectionListing_data()
+ {
+ /*
+ Fake Resource
+ |- Col 1 (inode/directory)
+ | |- Col 2 (inode/direcotry, application/octet-stream)
+ | | |- Col 3(application/octet-stream)
+ | |- Col 4 (text/plain)
+ |- Col 5 (inode/directory, text/plain)
+ |- Col 6 (inode/directory, application/octet-stream)
+ |- Col 7 (inode/directory, text/plain)
+ |- Col 8 (inode/directory, application/octet-stream)
+ |- Col 9 (unique/mime-type)
+ */
+
+ Resource res(QLatin1String("Test Resource"), false);
+ res.insert();
+
+ Collection col1 = createCollection(res, QLatin1String("Col 1"), Collection(),
+ QStringList() << QLatin1String("inode/directory"));
+ Collection col2 = createCollection(res, QLatin1String("Col 2"), col1,
+ QStringList() << QLatin1String("inode/directory")
+ << QLatin1String("application/octet-stream"));
+ Collection col3 = createCollection(res, QLatin1String("Col 3"), col2,
+ QStringList() << QLatin1String("application/octet-stream"));
+ Collection col4 = createCollection(res, QLatin1String("Col 4"), col2,
+ QStringList() << QLatin1String("text/plain"));
+ Collection col5 = createCollection(res, QLatin1String("Col 5"), Collection(),
+ QStringList() << QLatin1String("inode/directory")
+ << QLatin1String("text/plain"));
+ Collection col6 = createCollection(res, QLatin1String("Col 6"), col5,
+ QStringList() << QLatin1String("inode/directory")
+ << QLatin1String("application/octet-stream"));
+ Collection col7 = createCollection(res, QLatin1String("Col 7"), col5,
+ QStringList() << QLatin1String("inode/directory")
+ << QLatin1String("text/plain"));
+ Collection col8 = createCollection(res, QLatin1String("Col 8"), col7,
+ QStringList() << QLatin1String("text/directory")
+ << QLatin1String("application/octet-stream"));
+ Collection col9 = createCollection(res, QLatin1String("Col 9"), col8,
+ QStringList() << QLatin1String("unique/mime-type"));
+
+ QTest::addColumn<QVector<qint64>>("ancestors");
+ QTest::addColumn<QStringList>("mimetypes");
+ QTest::addColumn<QVector<qint64>>("expectedResults");
+
+ QTest::newRow("") << QVector<qint64>({ 0 })
+ << QStringList({ QLatin1String("text/plain") })
+ << QVector<qint64>({ col4.id(), col5.id(), col7.id() });
+ QTest::newRow("") << QVector<qint64>({ 0 })
+ << QStringList({ QLatin1String("application/octet-stream") })
+ << QVector<qint64>({ col2.id(), col3.id(), col6.id(), col8.id() });
+ QTest::newRow("") << QVector<qint64>({ col1.id() })
+ << QStringList({ QLatin1String("text/plain") })
+ << QVector<qint64>({ col4.id() });
+ QTest::newRow("") << QVector<qint64>({ col1.id() })
+ << QStringList({ QLatin1String("unique/mime-type") })
+ << QVector<qint64>();
+ QTest::newRow("") << QVector<qint64>({ col2.id(), col7.id() })
+ << QStringList({ QLatin1String("application/octet-stream") })
+ << QVector<qint64>({ col3.id(), col8.id() });
+ }
+
+ void testSearchHelperCollectionListing()
+ {
+ QFETCH(QVector<qint64>, ancestors);
+ QFETCH(QStringList, mimetypes);
+ QFETCH(QVector<qint64>, expectedResults);
+
+ QVector<qint64> results = SearchHelper::matchSubcollectionsByMimeType(ancestors, mimetypes);
+
+ qSort(expectedResults);
+ qSort(results);
+
+ QCOMPARE(results.size(), expectedResults.size());
+ QCOMPARE(results, expectedResults);
+ }
+
+};
+
+AKTEST_FAKESERVER_MAIN(SearchTest)
+
+#include "searchtest.moc"
--
2.1.0

View file

@ -0,0 +1,185 @@
From ac118e12fca25826340b6c8561939be19c4b7170 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 8 Dec 2014 13:55:58 +0100
Subject: [PATCH 27/30] Minor improvements in StatisticsCache as suggested by
Millian
* rename instance() to self()
* first call self() from main thread to avoid having mutex
* use CountQueryBuilder
---
server/src/akonadi.cpp | 4 +++-
server/src/handler/select.cpp | 2 +-
server/src/handler/status.cpp | 2 +-
server/src/handlerhelper.cpp | 2 +-
server/src/storage/collectionstatistics.cpp | 10 ++--------
server/src/storage/collectionstatistics.h | 2 +-
server/src/storage/notificationcollector.cpp | 10 +++++-----
7 files changed, 14 insertions(+), 18 deletions(-)
diff --git a/server/src/akonadi.cpp b/server/src/akonadi.cpp
index 5369320..faef3a5 100644
--- a/server/src/akonadi.cpp
+++ b/server/src/akonadi.cpp
@@ -35,6 +35,7 @@
#include "utils.h"
#include "debuginterface.h"
#include "storage/itemretrievalthread.h"
+#include "storage/collectionstatistics.h"
#include "preprocessormanager.h"
#include "search/searchmanager.h"
#include "search/searchtaskmanagerthread.h"
@@ -169,6 +170,8 @@ bool AkonadiServer::init()
new DebugInterface( this );
ResourceManager::self();
+ CollectionStatistics::self();
+
// Initialize the preprocessor manager
PreprocessorManager::init();
@@ -194,7 +197,6 @@ bool AkonadiServer::init()
mAgentSearchManagerThread = new SearchTaskManagerThread( this );
mAgentSearchManagerThread->start();
-
const QStringList searchManagers = settings.value( QLatin1String( "Search/Manager" ),
QStringList() << QLatin1String( "Nepomuk" )
<< QLatin1String( "Agent" ) ).toStringList();
diff --git a/server/src/handler/select.cpp b/server/src/handler/select.cpp
index f1ecc44..a94d971 100644
--- a/server/src/handler/select.cpp
+++ b/server/src/handler/select.cpp
@@ -97,7 +97,7 @@ bool Select::parseStream()
response.setString( "FLAGS (" + Flag::joinByName( Flag::retrieveAll(), QLatin1String( " " ) ).toLatin1() + ")" );
Q_EMIT responseAvailable( response );
- const CollectionStatistics::Statistics stats = CollectionStatistics::instance()->statistics(col);
+ const CollectionStatistics::Statistics stats = CollectionStatistics::self()->statistics(col);
if ( stats.count == -1 ) {
return failureResponse( "Unable to determine item count" );
}
diff --git a/server/src/handler/status.cpp b/server/src/handler/status.cpp
index 283532c..5fc9bb1 100644
--- a/server/src/handler/status.cpp
+++ b/server/src/handler/status.cpp
@@ -63,7 +63,7 @@ bool Status::parseStream()
// Responses:
// REQUIRED untagged responses: STATUS
- const CollectionStatistics::Statistics &stats = CollectionStatistics::instance()->statistics(col);
+ const CollectionStatistics::Statistics &stats = CollectionStatistics::self()->statistics(col);
if (stats.count == -1) {
return failureResponse( "Failed to query statistics." );
}
diff --git a/server/src/handlerhelper.cpp b/server/src/handlerhelper.cpp
index 39583ce..a88bc6e 100644
--- a/server/src/handlerhelper.cpp
+++ b/server/src/handlerhelper.cpp
@@ -166,7 +166,7 @@ QByteArray HandlerHelper::collectionToByteArray( const Collection &col, bool hid
b += " " AKONADI_PARAM_VIRTUAL " " + QByteArray::number( col.isVirtual() ) + ' ';
if ( includeStatistics ) {
- const CollectionStatistics::Statistics &stats = CollectionStatistics::instance()->statistics(col);
+ const CollectionStatistics::Statistics &stats = CollectionStatistics::self()->statistics(col);
if (stats.count > -1) {
b += AKONADI_ATTRIBUTE_MESSAGES " " + QByteArray::number( stats.count ) + ' ';
b += AKONADI_ATTRIBUTE_UNSEEN " ";
diff --git a/server/src/storage/collectionstatistics.cpp b/server/src/storage/collectionstatistics.cpp
index b2c6915..7307956 100644
--- a/server/src/storage/collectionstatistics.cpp
+++ b/server/src/storage/collectionstatistics.cpp
@@ -31,14 +31,11 @@ using namespace Akonadi::Server;
CollectionStatistics *CollectionStatistics::sInstance = 0;
-CollectionStatistics* CollectionStatistics::instance()
+CollectionStatistics* CollectionStatistics::self()
{
- static QMutex lock;
- lock.lock();
if (sInstance == 0) {
sInstance = new CollectionStatistics();
}
- lock.unlock();
return sInstance;
}
@@ -60,11 +57,8 @@ const CollectionStatistics::Statistics& CollectionStatistics::statistics(const C
CollectionStatistics::Statistics CollectionStatistics::getCollectionStatistics(const Collection &col)
{
- QueryBuilder qb(PimItem::tableName());
// COUNT(DISTINCT PimItemTable.id)
- qb.addAggregation(QString::fromLatin1("DISTINCT %1")
- .arg(PimItem::idFullColumnName()),
- QLatin1String("count"));
+ CountQueryBuilder qb(PimItem::tableName(), PimItem::idFullColumnName(), CountQueryBuilder::Distinct);
// SUM(PimItemTable.size)
qb.addAggregation(PimItem::sizeFullColumnName(), QLatin1String("sum"));
// SUM(CASE WHEN FlagTable.name IN ('\SEEN', '$IGNORED') THEN 1 ELSE 0 END)
diff --git a/server/src/storage/collectionstatistics.h b/server/src/storage/collectionstatistics.h
index 2c0af6a..a0b9f24 100644
--- a/server/src/storage/collectionstatistics.h
+++ b/server/src/storage/collectionstatistics.h
@@ -50,7 +50,7 @@ public:
qint64 read;
};
- static CollectionStatistics* instance();
+ static CollectionStatistics* self();
const Statistics& statistics(const Collection &col);
void invalidateCollection(const Collection &col);
diff --git a/server/src/storage/notificationcollector.cpp b/server/src/storage/notificationcollector.cpp
index dbc7883..7ed255c 100644
--- a/server/src/storage/notificationcollector.cpp
+++ b/server/src/storage/notificationcollector.cpp
@@ -134,7 +134,7 @@ void NotificationCollector::collectionChanged( const Collection &collection,
if ( AkonadiServer::instance()->intervalChecker() ) {
AkonadiServer::instance()->intervalChecker()->collectionAdded( collection.id() );
}
- CollectionStatistics::instance()->invalidateCollection(collection);
+ CollectionStatistics::self()->invalidateCollection(collection);
collectionNotification( NotificationMessageV2::Modify, collection, collection.parentId(), -1, resource, changes.toSet() );
}
@@ -161,7 +161,7 @@ void NotificationCollector::collectionRemoved( const Collection &collection,
if ( AkonadiServer::instance()->intervalChecker() ) {
AkonadiServer::instance()->intervalChecker()->collectionRemoved( collection.id() );
}
- CollectionStatistics::instance()->invalidateCollection(collection);
+ CollectionStatistics::self()->invalidateCollection(collection);
collectionNotification( NotificationMessageV2::Remove, collection, collection.parentId(), -1, resource );
}
@@ -187,7 +187,7 @@ void NotificationCollector::collectionUnsubscribed( const Collection &collection
if ( AkonadiServer::instance()->intervalChecker() ) {
AkonadiServer::instance()->intervalChecker()->collectionRemoved( collection.id() );
}
- CollectionStatistics::instance()->invalidateCollection(collection);
+ CollectionStatistics::self()->invalidateCollection(collection);
collectionNotification( NotificationMessageV2::Unsubscribe, collection, collection.parentId(), -1, resource, QSet<QByteArray>() );
}
@@ -288,7 +288,7 @@ void NotificationCollector::itemNotification( NotificationMessageV2::Operation o
copy.setParentCollection( iter.key() );
copy.setResource( resource );
- CollectionStatistics::instance()->invalidateCollection(Collection::retrieveById(iter.key()));
+ CollectionStatistics::self()->invalidateCollection(Collection::retrieveById(iter.key()));
dispatchNotification( copy );
}
@@ -311,7 +311,7 @@ void NotificationCollector::itemNotification( NotificationMessageV2::Operation o
}
msg.setResource( res );
- CollectionStatistics::instance()->invalidateCollection(col);
+ CollectionStatistics::self()->invalidateCollection(col);
dispatchNotification( msg );
}
--
2.1.0

View file

@ -0,0 +1,74 @@
From da5751c7b1589d2ea5800a3cf96dfc93b23b9783 Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Tue, 9 Dec 2014 14:35:04 +0100
Subject: [PATCH 28/30] Extend imapparser benchmark and keep static data
around.
This gets rid of some temporary allocations and thus speeds up
the whole process a bit.
REVIEW: 121406
---
libs/imapparser.cpp | 5 +++--
libs/tests/imapparserbenchmark.cpp | 22 ++++++++++++++++++++++
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/libs/imapparser.cpp b/libs/imapparser.cpp
index 9b437e2..6f9f592 100644
--- a/libs/imapparser.cpp
+++ b/libs/imapparser.cpp
@@ -364,7 +364,8 @@ int ImapParser::parseNumber( const QByteArray &data, qint64 &result, bool *ok, i
QByteArray ImapParser::quote( const QByteArray &data )
{
if ( data.isEmpty() ) {
- return QByteArray( "\"\"" );
+ static const QByteArray empty( "\"\"" );
+ return empty;
}
const int inputLength = data.length();
@@ -499,7 +500,7 @@ int ImapParser::parseDateTime( const QByteArray &data, QDateTime &dateTime, int
}
pos += 3;
- const QByteArray shortMonthNames( "janfebmaraprmayjunjulaugsepoctnovdec" );
+ static const QByteArray shortMonthNames( "janfebmaraprmayjunjulaugsepoctnovdec" );
int month = shortMonthNames.indexOf( data.mid( pos, 3 ).toLower() );
if ( month == -1 ) {
return start;
diff --git a/libs/tests/imapparserbenchmark.cpp b/libs/tests/imapparserbenchmark.cpp
index 17dac66..fd4335c 100644
--- a/libs/tests/imapparserbenchmark.cpp
+++ b/libs/tests/imapparserbenchmark.cpp
@@ -94,6 +94,28 @@ class ImapParserBenchmark : public QObject
ImapParser::parseParenthesizedList( data, result, 0 );
}
}
+
+ void parseNumber()
+ {
+ QByteArray data( "123456" );
+ qint64 result;
+ bool ok = false;
+ QBENCHMARK {
+ ImapParser::parseNumber( data, result, &ok );
+ }
+ QVERIFY(ok);
+ QCOMPARE(result, qint64(123456));
+ }
+
+ void parseDateTime()
+ {
+ QByteArray data( "28-May-2006 01:03:35 +0000" );
+ QDateTime result;
+ QBENCHMARK {
+ ImapParser::parseDateTime( data, result );
+ }
+ QCOMPARE(result.toString( QString::fromUtf8( "dd-MMM-yyyy hh:mm:ss +0000" ) ), QString::fromUtf8( data ));
+ }
};
#include "imapparserbenchmark.moc"
--
2.1.0

View file

@ -0,0 +1,61 @@
From 89357c7b0fc5e76091510af504058c036fa1b2f9 Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Wed, 10 Dec 2014 21:04:24 +0100
Subject: [PATCH 29/30] Reduce the amount of allocations by preallocating a
buffer.
Sadly, QByteArray cannot be cleared without destroying the buffer.
But we can guesstimate the target size of the buffer and thus
reduce the amount of allocations.
This also adds a benchmark for ImapParser::parseString.
---
libs/imapparser.cpp | 1 +
libs/tests/imapparserbenchmark.cpp | 19 +++++++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/libs/imapparser.cpp b/libs/imapparser.cpp
index 6f9f592..f3301e7 100644
--- a/libs/imapparser.cpp
+++ b/libs/imapparser.cpp
@@ -186,6 +186,7 @@ int ImapParser::parseQuotedString( const QByteArray &data, QByteArray &result, i
// quoted string
if ( data[begin] == '"' ) {
++begin;
+ result.reserve(qMin(32, data.size() - begin));
for ( int i = begin; i < data.length(); ++i ) {
const char ch = data.at( i );
if ( foundSlash ) {
diff --git a/libs/tests/imapparserbenchmark.cpp b/libs/tests/imapparserbenchmark.cpp
index fd4335c..ee861a0 100644
--- a/libs/tests/imapparserbenchmark.cpp
+++ b/libs/tests/imapparserbenchmark.cpp
@@ -95,6 +95,25 @@ class ImapParserBenchmark : public QObject
}
}
+ void parseString_data()
+ {
+ QTest::addColumn<QByteArray>( "data" );
+ QTest::newRow("plain") << QByteArray("fooobarasdf something more lalala");
+ QTest::newRow("quoted") << QByteArray("\"fooobarasdf\" something more lalala");
+ }
+
+ void parseString()
+ {
+ QFETCH(QByteArray, data);
+ QByteArray result;
+ qint64 sum = 0;
+ QBENCHMARK {
+ sum += ImapParser::parseString( data, result );
+ }
+ QVERIFY(!result.isEmpty());
+ QVERIFY(sum > 0);
+ }
+
void parseNumber()
{
QByteArray data( "123456" );
--
2.1.0

View file

@ -0,0 +1,119 @@
From c733429f4fa9696fb027ddc946e54f6bbb68deaf Mon Sep 17 00:00:00 2001
From: Milian Wolff <mail@milianw.de>
Date: Wed, 10 Dec 2014 21:16:45 +0100
Subject: [PATCH 30/30] Preallocate a capacity of 16 for the returned list.
See also 159abaf2f372eaa633db8f69ff6b1edd459998cc in kdepimlibs on
why. I'll quote it here again:
In my data, most often the final size of fetchResponse is 16.
By reserving that data upfront, we can get rid of 3/4 of the
list allocations, according to the growth-strategy outlined here:
http://qt-project.org/doc/qt-4.8/containers.html#growth-strategies
I.e. before, we'd allocate 4, 8, 12, 16. Now we directly allocate
room for 16.
Thing is, doing this outside of akonadi has no effect, as QList::clear
destroys the internal buffer. With the added benchmark, I now verified
that this patch here does have a positive effect.
---
libs/imapparser.cpp | 2 ++
libs/tests/imapparserbenchmark.cpp | 52 ++++++++++++++++++++++++++------------
2 files changed, 38 insertions(+), 16 deletions(-)
diff --git a/libs/imapparser.cpp b/libs/imapparser.cpp
index f3301e7..f5a7457 100644
--- a/libs/imapparser.cpp
+++ b/libs/imapparser.cpp
@@ -79,6 +79,8 @@ int parseParenthesizedListHelper( const QByteArray &data, T &result, int start )
return start;
}
+ result.reserve(16);
+
int count = 0;
int sublistBegin = start;
bool insideQuote = false;
diff --git a/libs/tests/imapparserbenchmark.cpp b/libs/tests/imapparserbenchmark.cpp
index ee861a0..7545238 100644
--- a/libs/tests/imapparserbenchmark.cpp
+++ b/libs/tests/imapparserbenchmark.cpp
@@ -27,6 +27,25 @@ Q_DECLARE_METATYPE( QList<QByteArray> )
class ImapParserBenchmark : public QObject
{
Q_OBJECT
+ private:
+ void geneateParseParenthesizedListData()
+ {
+ QTest::addColumn<QByteArray>( "data" );
+ QTest::newRow( "empty" ) << QByteArray();
+ QTest::newRow( "unnested" ) << QByteArray("(\"Foo Bar\" NIL \"foobar\" \"test.com\")");
+ QTest::newRow( "nested" ) << QByteArray("((\"Foo Bar\" NIL \"foobar\" \"test.com\"))");
+ QTest::newRow( "nested-long" ) << QByteArray("(UID 86 REV 0 MIMETYPE \"message/rfc822\" COLLECTIONID 13 SIZE 6114 FLAGS (\\SEEN)"
+ " ANCESTORS ((13 \"/INBOX\") (12 \"imap://mail@mail.test.com/\") (0 \"\")) PLD:ENVELOPE[1] {396}"
+ " (\"Fri, 04 Jun 2010 09:07:54 +0200\" \"Re: [ADMIN] foobar available again!\""
+ " ((\"Foo Bar\" NIL \"foobar\" \"test.com\"))"
+ " NIL NIL"
+ " ((\"Asdf Bla Blub\" NIL \"asdf.bla.blub\" \"123test.org\"))"
+ " ((NIL NIL \"muh.kuh\" \"lalala.com\") (\"Konqi KDE\" NIL \"konqi\" \"kde.org\") (NIL NIL \"all\" \"test.com\"))"
+ " NIL \"<201006040905.33367.foo.bar@test.com>\" \"<4C08A64A.9020205@123test.org>\""
+ " \"<201006040142.56540.muh.kuh@lalala.com> <201006040704.39648.konqi@kde.org> <201006040905.33367.foo.bar@test.com>\""
+ "))");
+ }
+
private Q_SLOTS:
void quote_data()
{
@@ -68,25 +87,12 @@ class ImapParserBenchmark : public QObject
}
}
- void parseParenthesizedList_data()
+ void parseParenthesizedQVarLengthArray_data()
{
- QTest::addColumn<QByteArray>( "data" );
- QTest::newRow( "empty" ) << QByteArray();
- QTest::newRow( "unnested" ) << QByteArray("(\"Foo Bar\" NIL \"foobar\" \"test.com\")");
- QTest::newRow( "nested" ) << QByteArray("((\"Foo Bar\" NIL \"foobar\" \"test.com\"))");
- QTest::newRow( "nested-long" ) << QByteArray("(UID 86 REV 0 MIMETYPE \"message/rfc822\" COLLECTIONID 13 SIZE 6114 FLAGS (\\SEEN)"
- " ANCESTORS ((13 \"/INBOX\") (12 \"imap://mail@mail.test.com/\") (0 \"\")) PLD:ENVELOPE[1] {396}"
- " (\"Fri, 04 Jun 2010 09:07:54 +0200\" \"Re: [ADMIN] foobar available again!\""
- " ((\"Foo Bar\" NIL \"foobar\" \"test.com\"))"
- " NIL NIL"
- " ((\"Asdf Bla Blub\" NIL \"asdf.bla.blub\" \"123test.org\"))"
- " ((NIL NIL \"muh.kuh\" \"lalala.com\") (\"Konqi KDE\" NIL \"konqi\" \"kde.org\") (NIL NIL \"all\" \"test.com\"))"
- " NIL \"<201006040905.33367.foo.bar@test.com>\" \"<4C08A64A.9020205@123test.org>\""
- " \"<201006040142.56540.muh.kuh@lalala.com> <201006040704.39648.konqi@kde.org> <201006040905.33367.foo.bar@test.com>\""
- "))");
+ geneateParseParenthesizedListData();
}
- void parseParenthesizedList()
+ void parseParenthesizedQVarLengthArray()
{
QFETCH( QByteArray, data );
QVarLengthArray<QByteArray, 16> result;
@@ -95,6 +101,20 @@ class ImapParserBenchmark : public QObject
}
}
+ void parseParenthesizedQList_data()
+ {
+ geneateParseParenthesizedListData();
+ }
+
+ void parseParenthesizedQList()
+ {
+ QFETCH( QByteArray, data );
+ QList<QByteArray> result;
+ QBENCHMARK {
+ ImapParser::parseParenthesizedList( data, result, 0 );
+ }
+ }
+
void parseString_data()
{
QTest::addColumn<QByteArray>( "data" );
--
2.1.0

View file

@ -0,0 +1,52 @@
From c23607679fa1451f0c6890bd4a5656c07d519853 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Fri, 6 Feb 2015 13:33:50 +0100
Subject: [PATCH 31/34] Less C++11, fixes build with clang
---
server/tests/unittest/searchtest.cpp | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/server/tests/unittest/searchtest.cpp b/server/tests/unittest/searchtest.cpp
index f523b09..969d296 100644
--- a/server/tests/unittest/searchtest.cpp
+++ b/server/tests/unittest/searchtest.cpp
@@ -119,21 +119,21 @@ private Q_SLOTS:
QTest::addColumn<QStringList>("mimetypes");
QTest::addColumn<QVector<qint64>>("expectedResults");
- QTest::newRow("") << QVector<qint64>({ 0 })
- << QStringList({ QLatin1String("text/plain") })
- << QVector<qint64>({ col4.id(), col5.id(), col7.id() });
- QTest::newRow("") << QVector<qint64>({ 0 })
- << QStringList({ QLatin1String("application/octet-stream") })
- << QVector<qint64>({ col2.id(), col3.id(), col6.id(), col8.id() });
- QTest::newRow("") << QVector<qint64>({ col1.id() })
- << QStringList({ QLatin1String("text/plain") })
- << QVector<qint64>({ col4.id() });
- QTest::newRow("") << QVector<qint64>({ col1.id() })
- << QStringList({ QLatin1String("unique/mime-type") })
+ QTest::newRow("") << (QVector<qint64>() << 0)
+ << (QStringList() << QLatin1String("text/plain"))
+ << (QVector<qint64>() << col4.id() << col5.id() << col7.id());
+ QTest::newRow("") << (QVector<qint64>() << 0)
+ << (QStringList() << QLatin1String("application/octet-stream"))
+ << (QVector<qint64>() << col2.id() << col3.id() << col6.id() << col8.id());
+ QTest::newRow("") << (QVector<qint64>() << col1.id())
+ << (QStringList() << QLatin1String("text/plain"))
+ << (QVector<qint64>() << col4.id());
+ QTest::newRow("") << (QVector<qint64>() << col1.id())
+ << (QStringList() << QLatin1String("unique/mime-type"))
<< QVector<qint64>();
- QTest::newRow("") << QVector<qint64>({ col2.id(), col7.id() })
- << QStringList({ QLatin1String("application/octet-stream") })
- << QVector<qint64>({ col3.id(), col8.id() });
+ QTest::newRow("") << (QVector<qint64>() << col2.id() << col7.id())
+ << (QStringList() << QLatin1String("application/octet-stream"))
+ << (QVector<qint64>() << col3.id() << col8.id());
}
void testSearchHelperCollectionListing()
--
2.4.3

View file

@ -0,0 +1,60 @@
From abe71f46c3b2e657db25ac16c43a4c76b2212a9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Wed, 17 Jun 2015 13:04:13 +0200
Subject: [PATCH 32/34] Don't throw exception when MOVE handler finds no items
to move
Instead return "OK MOVE complete" right away. The reason for this is that
when client tries to move an Item from a folder into the same folder (it's
possible in KMail, also mailfilter agent might trigger this situation) the
subsequent command gets eaten by ImapStreamParser and the client's Job gets
stuck waiting for response forever. According to Laurent this could also fix
the Mail Filter Agent getting stuck occasionally.
The problem is in ImapStreamParser::atCommandEnd() method, which is called
by the Move handler at some point. atCommandEnd() checks whether we reached
command end in the stream by looking if the next characters in the stream
are "\r\n" and if so it will consume the command end ("\r\n"), effectively
moving the streaming position BEYOND the command. In case of MOVE the
command has already been completely parsed so we are actually at the end of
the command and so ImapStreamParser will consume the "\r\n" and position the
stream beyond the command end.
After that the Move handler tries to get the items from DB and throws the
exception (the second part of the condition in the SQL query causes that
the query yields no results in this situation) which gets us back to
Connection where we then call ImapStreamParser::skipCommand(). At this point
however there are no more data in the stream (because atCommandEnd() moved
us beyond the end of the MOVE command) and so ImapStreamParser will block
and wait for more data (with 30 seconds timeout). If client sends another
command within this time the ImapStreamParser will think that this is the
command to be skipped and will consume it. This means that the command never
really reaches the Connection as it's consumed as soon as it's captured by
ImapStreamParser. And because Akonadi never receives the command it cannot
send a response and thus the Job in client will wait forever and ever...
Proper fix would be to make ImapStreamParser::atCommandEnd() to only peek
instead of actually altering the position in the stream however I'm really
afraid that it could break some other stuff that relies on this (broken?)
behaviour and our test coverage is not sufficient at this point to be
reliable enough.
---
server/src/handler/move.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/src/handler/move.cpp b/server/src/handler/move.cpp
index 0a6c3bf..4cf9d4e 100644
--- a/server/src/handler/move.cpp
+++ b/server/src/handler/move.cpp
@@ -85,7 +85,7 @@ bool Move::parseStream()
if ( qb.exec() ) {
const QVector<PimItem> items = qb.result();
if ( items.isEmpty() ) {
- throw HandlerException( "No items found" );
+ return successResponse( "MOVE complete" );
}
// Split the list by source collection
--
2.4.3

View file

@ -0,0 +1,140 @@
From 9c0dc6b3f0826d32eac310b2e7ecd858ca3df681 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Mon, 29 Jun 2015 22:45:11 +0200
Subject: [PATCH 33/34] Don't leak old external payload files
Actually delete old payload files after we increase the payload revision or
switch from external to internal payload. This caused ~/.local/share/akonadi/file_db_data
to grow insanely for all users, leaving them with many duplicated files (just with
different revisions).
It is recommended that users run akonadictl fsck to clean up the leaked payload
files.
Note that there won't be any more releases of Akonadi 1.13 (and this has been
fixed in master already), so I strongly recommend distributions to pick this
patch into their packaging.
BUG: 341884
CCBUG: 338402
---
server/src/storage/partstreamer.cpp | 14 ++++++++++++++
server/tests/unittest/partstreamertest.cpp | 24 +++++++++++++-----------
2 files changed, 27 insertions(+), 11 deletions(-)
diff --git a/server/src/storage/partstreamer.cpp b/server/src/storage/partstreamer.cpp
index 2ec41fa..71bdca8 100644
--- a/server/src/storage/partstreamer.cpp
+++ b/server/src/storage/partstreamer.cpp
@@ -290,6 +290,12 @@ bool PartStreamer::stream(const QByteArray &command, bool checkExists,
mDataChanged = true;
}
+ // If the part is external, remember it's current file name
+ QString originalFile;
+ if (part.isValid() && part.external()) {
+ originalFile = PartHelper::resolveAbsolutePath(part.data());
+ }
+
part.setPartType(partType);
part.setVersion(partVersion);
part.setPimItemId(mItem.id());
@@ -306,6 +312,14 @@ bool PartStreamer::stream(const QByteArray &command, bool checkExists,
*changed = mDataChanged;
}
+ if (!originalFile.isEmpty()) {
+ // If the part was external but is not anymore, or if it's still external
+ // but the filename has changed (revision update), remove the original file
+ if (!part.external() || (part.external() && originalFile != PartHelper::resolveAbsolutePath(part.data()))) {
+ PartHelper::removeFile(originalFile);
+ }
+ }
+
return ok;
}
diff --git a/server/tests/unittest/partstreamertest.cpp b/server/tests/unittest/partstreamertest.cpp
index 05e3a8a..669bbbc 100644
--- a/server/tests/unittest/partstreamertest.cpp
+++ b/server/tests/unittest/partstreamertest.cpp
@@ -91,6 +91,7 @@ private Q_SLOTS:
QTest::addColumn<qint64>("expectedPartSize");
QTest::addColumn<bool>("expectedChanged");
QTest::addColumn<bool>("isExternal");
+ QTest::addColumn<int>("version");
QTest::addColumn<PimItem>("pimItem");
PimItem item;
@@ -101,22 +102,22 @@ private Q_SLOTS:
QVERIFY(item.insert());
// Order of these tests matters!
- QTest::newRow("item 1, internal") << QByteArray("PLD:DATA") << QByteArray("123") << 3ll << true << false << item;
- QTest::newRow("item 1, change to external") << QByteArray("PLD:DATA") << QByteArray("123456789") << 9ll << true << true << item;
- QTest::newRow("item 1, update external") << QByteArray("PLD:DATA") << QByteArray("987654321") << 9ll << true << true << item;
- QTest::newRow("item 1, external, no change") << QByteArray("PLD:DATA") << QByteArray("987654321") << 9ll << false << true << item;
- QTest::newRow("item 1, change to internal") << QByteArray("PLD:DATA") << QByteArray("1234") << 4ll << true << false << item;
- QTest::newRow("item 1, internal, no change") << QByteArray("PLD:DATA") << QByteArray("1234") << 4ll << false << false << item;
+ QTest::newRow("item 1, internal") << QByteArray("PLD:DATA") << QByteArray("123") << 3ll << true << false << -1 << item;
+ QTest::newRow("item 1, change to external") << QByteArray("PLD:DATA") << QByteArray("123456789") << 9ll << true << true << 0 << item;
+ QTest::newRow("item 1, update external") << QByteArray("PLD:DATA") << QByteArray("987654321") << 9ll << true << true << 1 << item;
+ QTest::newRow("item 1, external, no change") << QByteArray("PLD:DATA") << QByteArray("987654321") << 9ll << false << true << 2 << item;
+ QTest::newRow("item 1, change to internal") << QByteArray("PLD:DATA") << QByteArray("1234") << 4ll << true << false << 2 << item;
+ QTest::newRow("item 1, internal, no change") << QByteArray("PLD:DATA") << QByteArray("1234") << 4ll << false << false << 2 << item;
}
void testStreamer()
{
- return;
QFETCH(QByteArray, expectedPartName);
QFETCH(QByteArray, expectedData);
QFETCH(qint64, expectedPartSize);
QFETCH(bool, expectedChanged);
QFETCH(bool, isExternal);
+ QFETCH(int, version);
QFETCH(PimItem, pimItem);
FakeConnection connection;
@@ -160,17 +161,18 @@ private Q_SLOTS:
PimItem item = PimItem::retrieveById(pimItem.id());
const QVector<Part> parts = item.parts();
- QVERIFY(parts.count() == 1);
+ QCOMPARE(parts.count(), 1);
const Part part = parts[0];
QCOMPARE(part.datasize(), expectedPartSize);
QCOMPARE(part.external(), isExternal);
+ qDebug() << part.version() << part.data();
const QByteArray data = part.data();
if (isExternal) {
QVERIFY(streamerSpy.count() == 1);
QVERIFY(streamerSpy.first().count() == 1);
const Response response = streamerSpy.first().first().value<Akonadi::Server::Response>();
const QByteArray str = response.asString();
- const QByteArray expectedResponse = "+ STREAM [FILE " + QByteArray::number(part.id()) + "_r" + QByteArray::number(part.version()) + "]";
+ const QByteArray expectedResponse = "+ STREAM [FILE " + QByteArray::number(part.id()) + "_r" + QByteArray::number(version) + "]";
QCOMPARE(QString::fromUtf8(str), QString::fromUtf8(expectedResponse));
QFile file(PartHelper::resolveAbsolutePath(data));
@@ -182,7 +184,7 @@ private Q_SLOTS:
QCOMPARE(fileData, expectedData);
// Make sure no previous versions are left behind in file_db_data
- for (int i = 0; i < part.version(); ++i) {
+ for (int i = 0; i < version; ++i) {
const QByteArray fileName = QByteArray::number(part.id()) + "_r" + QByteArray::number(part.version());
const QString filePath = PartHelper::resolveAbsolutePath(fileName);
QVERIFY(!QFile::exists(filePath));
@@ -194,7 +196,7 @@ private Q_SLOTS:
QCOMPARE(data, expectedData);
// Make sure nothing is left behind in file_db_data
- for (int i = 0; i <= part.version(); ++i) {
+ for (int i = 0; i <= version; ++i) {
const QByteArray fileName = QByteArray::number(part.id()) + "_r" + QByteArray::number(part.version());
const QString filePath = PartHelper::resolveAbsolutePath(fileName);
QVERIFY(!QFile::exists(filePath));
--
2.4.3

View file

@ -0,0 +1,25 @@
From 18ed37d89b8185ac15a8bfe245de8a88d17f2c64 Mon Sep 17 00:00:00 2001
From: David Faure <faure@kde.org>
Date: Tue, 28 Jul 2015 12:47:44 +0200
Subject: [PATCH 34/34] set cmake_min_req to match kdelibs4 and enable newer
policies
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2d790c9..a64e724 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
+cmake_minimum_required(VERSION 2.8.9)
project(Akonadi)
-cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)
# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked
set(CMAKE_MODULE_PATH "${Akonadi_SOURCE_DIR}/cmake/modules")
--
2.4.3

View file

@ -0,0 +1,33 @@
diff -up akonadi-1.13.0/CMakeLists.txt.opt akonadi-1.13.0/CMakeLists.txt
--- akonadi-1.13.0/CMakeLists.txt.opt 2015-12-11 07:44:57.653216984 -0600
+++ akonadi-1.13.0/CMakeLists.txt 2015-12-11 07:52:14.749205933 -0600
@@ -339,22 +339,22 @@ endif()
include_directories(${Akonadi_SOURCE_DIR} ${Akonadi_BINARY_DIR} ${QT_INCLUDES} ${Boost_INCLUDE_DIR})
-add_subdirectory(interfaces)
+add_subdirectory(interfaces)
add_subdirectory(libs)
set(AKONADI_PROTOCOLINTERNALS_LIBS ${akonadiprotocolinternals_LIB_DEPENDS} akonadiprotocolinternals)
-add_subdirectory(shared)
-add_subdirectory(agentserver)
-add_subdirectory(server)
+#add_subdirectory(shared)
+#add_subdirectory(agentserver)
+#add_subdirectory(server)
-add_subdirectory(rds)
+#add_subdirectory(rds)
if(NOT WIN32)
- add_subdirectory(asapcat)
+ #add_subdirectory(asapcat)
endif()
if (NOT QT5_BUILD)
if(SQLITE_FOUND)
option(SQLITE_LINK_STATIC "link libsqlite3 statically" FALSE)
- add_subdirectory(qsqlite)
+ #add_subdirectory(qsqlite)
endif()
endif()

View file

@ -1,3 +1,5 @@
# Force out of source build
%undefine __cmake_in_source_build
# base pkg default to SQLITE now, install -mysql if you want that instead
%global database_backend SQLITE
@ -5,206 +7,322 @@
# trim changelog included in binary rpms
%global _changelog_trimtime %(date +%s -d "1 year ago")
%if 0%{?rhel} == 6
%define cmake_pkg cmake28
%else
%define cmake_pkg cmake
%endif
Summary: PIM Storage Service
Summary: PIM Storage Service Libraries
Name: akonadi
Version: 1.10.3
Release: 1%{?dist}
Version: 1.13.0
Release: 130%{?dist}
License: LGPLv2+
License: LGPL-2.0-or-later
URL: http://community.kde.org/KDE_PIM/Akonadi
%if 0%{?snap}
# git clone git://git.kde.org/akonadi
# git archive --prefix=akonadi-%{version}/ master | bzip2 > akonadi-%{version}-%{snap}.tar.bz2
Source0: akonadi-%{version}-%{snap}.tar.bz2
%else
# Official release
Source0: http://download.kde.org/stable/akonadi/src/akonadi-%{version}-1.tar.bz2
%endif
Source0: http://download.kde.org/stable/akonadi/src/akonadi-%{version}.tar.bz2
## mysql config
Source10: akonadiserverrc.mysql
## downstream patches
Patch100: akonadi-1.13.0-libs_only.patch
## upstreamable patches
## upstream patches
%define mysql_conf_timestamp 20130607
## upstream patches (1.13 branch)
Patch1: 0001-FindSqlite-Use-CMAKE_FLAGS-the-right-way-in-try_comp.patch
Patch2: 0002-Do-not-enter-the-test-directories-if-AKONADI_BUILD_T.patch
Patch3: 0003-STORE-Allow-modifying-items-tags-via-Tag-RID-or-GID.patch
Patch4: 0004-Fix-typo-in-if-condition.patch
Patch5: 0005-Fix-buffer-overflow-in-AKTEST_FAKESERVER_MAIN.patch
Patch6: 0006-Don-t-crash-when-setmntent-returns-NULL.patch
Patch7: 0007-Don-t-call-insert-from-Q_ASSERT-breaks-unit-tests-in.patch
Patch8: 0008-Suppress-unused-variable-warnings-in-release-mode.patch
Patch9: 0009-Test-whether-compiler-supports-all-required-C-11-fea.patch
Patch10: 0010-prevent-starting-a-QTimer-with-a-negative-interval.patch
Patch11: 0011-Convert-some-qDebugs-to-akDebugs.patch
Patch12: 0012-Optimize-Reduce-the-amount-of-allocations-required-t.patch
Patch13: 0013-Intern-entity-strings-for-table-and-column-names.patch
Patch14: 0014-No-semicolon-after-Q_DECLARE_METATYPE.patch
Patch15: 0015-Use-QMutexLocker-instead-of-manual-lock-unlock-calls.patch
Patch16: 0016-Use-an-QAtomicInt-instead-of-a-plain-bool-for-Entity.patch
Patch17: 0017-Optimize-Only-do-one-hash-lookup-to-retrieve-value-f.patch
Patch18: 0018-Optimize-Skip-value-condition-on-invalid-flags.patch
Patch19: 0019-Optimize-queries-Do-not-retrieve-known-key-used-in-t.patch
Patch20: 0020-Avoid-ridiculous-amount-of-SQL-queries-by-caching-Pa.patch
Patch21: 0021-Implement-support-for-CASE.WHEN.THEN-SQL-statements-.patch
Patch22: 0022-Implement-cache-for-CollectionStatistics-to-signific.patch
Patch23: 0023-Always-create-a-new-PartType-when-it-does-not-exist.patch
Patch24: 0024-Fix-compilation-with-strict-iterators.patch
Patch25: 0025-Avoid-repeated-calls-to-PimItem-flags-and-PimItem-ta.patch
Patch26: 0026-Avoid-recursive-collection-listing-in-SearchHelper.patch
Patch27: 0027-Minor-improvements-in-StatisticsCache-as-suggested-b.patch
Patch28: 0028-Extend-imapparser-benchmark-and-keep-static-data-aro.patch
Patch29: 0029-Reduce-the-amount-of-allocations-by-preallocating-a-.patch
Patch30: 0030-Preallocate-a-capacity-of-16-for-the-returned-list.patch
BuildRequires: automoc4
BuildRequires: boost-devel
BuildRequires: %{cmake_pkg} >= 2.8.8
BuildRequires: cmake >= 2.8.8
BuildRequires: gcc-c++
# for xsltproc
BuildRequires: libxslt
BuildRequires: pkgconfig(QtDBus) pkgconfig(QtSql) pkgconfig(QtXml)
BuildRequires: pkgconfig(shared-mime-info)
BuildRequires: pkgconfig(soprano)
BuildRequires: pkgconfig(sqlite3) >= 3.6.23
# %%check
BuildRequires: dbus-x11 xorg-x11-server-Xvfb
# backends, used at buildtime to query known locations of server binaries
# FIXME/TODO: set these via cmake directives, avoids needless buildroot items
BuildRequires: mysql-server
BuildRequires: postgresql-server
#%{?_qt4_version:Requires: qt4%{?_isa} >= %{_qt4_version}}
Requires: qt4%{?_isa} >= 4.8.5-10
Requires(postun): /sbin/ldconfig
%description
%{summary}.
%package devel
Summary: Developer files for %{name}
Conflicts: kf5-akonadi-server-devel
Requires: %{name}%{?_isa} = %{version}-%{release}
%description devel
%{summary}.
%package mysql
Summary: Akonadi MySQL backend support
# upgrade path
Obsoletes: akonadi < 1.7.90-2
Requires: %{name}%{?_isa} = %{version}-%{release}
Requires: mysql-server
Requires: qt4-mysql%{?_isa}
Requires(post): %{_sbindir}/update-alternatives
Requires(postun): %{_sbindir}/update-alternatives
%description mysql
Configures akonadi to use mysql backend by default.
Requires an available instance of mysql server at runtime.
Akonadi can spawn a per-user one automatically if the mysql-server
package is installed on the machine.
See also: %{_sysconfdir}/akonadi/mysql-global.conf
%prep
%setup -q -n akonadi-%{version}
%autosetup -p1 -n akonadi-%{version}
%build
mkdir -p %{_target_platform}
pushd %{_target_platform}
%{?cmake28}%{!?cmake28:%{?cmake}} \
-DCONFIG_INSTALL_DIR=%{_sysconfdir} \
%{?database_backend:-DDATABASE_BACKEND=%{database_backend}} \
-DINSTALL_QSQLITE_IN_QT_PREFIX:BOOL=ON \
..
popd
make %{?_smp_mflags} -C %{_target_platform}
%cmake -DCMAKE_BUILD_TYPE:STRING="Release"
%cmake_build
%install
rm -rf %{buildroot}
make install/fast DESTDIR=$RPM_BUILD_ROOT -C %{_target_platform}
%cmake_install
install -p -m644 -D %{SOURCE10} %{buildroot}%{_sysconfdir}/xdg/akonadi/akonadiserverrc.mysql
mkdir -p %{buildroot}%{_datadir}/akonadi/agents
# create "big" config (analog to -mobile.conf)
install -p \
%{buildroot}%{_sysconfdir}/akonadi/mysql-global.conf \
%{buildroot}%{_sysconfdir}/akonadi/mysql-global-big.conf
# default to small/mobile config
install -p \
%{buildroot}%{_sysconfdir}/akonadi/mysql-global-mobile.conf \
%{buildroot}%{_sysconfdir}/akonadi/mysql-global.conf
touch -d %{mysql_conf_timestamp} \
%{buildroot}%{_sysconfdir}/akonadi/mysql-global*.conf \
%{buildroot}%{_sysconfdir}/akonadi/mysql-local.conf
# create/own %{_libdir}/akondi
mkdir -p %{buildroot}%{_libdir}/akonadi
# %%ghost'd global akonadiserverrc
touch akonadiserverrc
install -p -m644 -D akonadiserverrc %{buildroot}%{_sysconfdir}/xdg/akonadi/akonadiserverrc
## unpackaged files
rm -fv %{buildroot}%{_datadir}/mime/packages/akonadi-mime.xml
%check
export PKG_CONFIG_PATH=%{buildroot}%{_datadir}/pkgconfig:%{buildroot}%{_libdir}/pkgconfig
test "$(pkg-config --modversion akonadi)" = "%{version}"
# this one (still) fails in mock (local build ok):
# 14/14 Test #14: akonadi-dbconfigtest
xvfb-run -a dbus-launch --exit-with-session make test -C %{_target_platform} ||:
%clean
rm -rf %{buildroot}
%post -p /sbin/ldconfig
%posttrans
update-mime-database %{_datadir}/mime &> /dev/null || :
%postun
/sbin/ldconfig ||:
if [ $1 -eq 0 ] ; then
update-mime-database %{_datadir}/mime &> /dev/null ||:
fi
%ldconfig_scriptlets
%files
%doc AUTHORS lgpl-license
%dir %{_sysconfdir}/xdg/akonadi/
%ghost %config(missingok,noreplace) %{_sysconfdir}/xdg/akonadi/akonadiserverrc
%dir %{_sysconfdir}/akonadi/
%{_bindir}/akonadi_agent_launcher
%{_bindir}/akonadi_agent_server
%{_bindir}/akonadi_control
%{_bindir}/akonadi_rds
%{_bindir}/akonadictl
%{_bindir}/akonadiserver
%{_libdir}/akonadi/
%doc AUTHORS
%license lgpl-license
%{_libdir}/libakonadiprotocolinternals.so.1*
%{_datadir}/dbus-1/interfaces/org.freedesktop.Akonadi.*.xml
%{_datadir}/dbus-1/services/org.freedesktop.Akonadi.*.service
%{_datadir}/mime/packages/akonadi-mime.xml
%{_datadir}/akonadi/
%{_qt4_plugindir}/sqldrivers/libqsqlite3.so
%files devel
%{_bindir}/asapcat
%{_includedir}/akonadi/
%{_libdir}/pkgconfig/akonadi.pc
%{_libdir}/libakonadiprotocolinternals.so
%{_libdir}/cmake/Akonadi/
%post mysql
%{_sbindir}/update-alternatives \
--install %{_sysconfdir}/xdg/akonadi/akonadiserverrc \
akonadiserverrc \
%{_sysconfdir}/xdg/akonadi/akonadiserverrc.mysql \
10
%postun mysql
if [ $1 -eq 0 ]; then
%{_sbindir}/update-alternatives \
--remove akonadiserverrc \
%{_sysconfdir}/xdg/akonadi/akonadiserverrc.mysql
fi
%files mysql
%config(noreplace) %{_sysconfdir}/xdg/akonadi/akonadiserverrc.mysql
%config(noreplace) %{_sysconfdir}/akonadi/mysql-global.conf
%config(noreplace) %{_sysconfdir}/akonadi/mysql-local.conf
# example conf's
%{_sysconfdir}/akonadi/mysql-global-big.conf
%{_sysconfdir}/akonadi/mysql-global-mobile.conf
%{_datadir}/dbus-1/interfaces/org.freedesktop.Akonadi.*.xml
%changelog
* Fri Jan 16 2026 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-130
- Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild
* Wed Jul 23 2025 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-129
- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild
* Thu Jan 16 2025 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-128
- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild
* Wed Jul 17 2024 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-127
- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild
* Mon Jan 22 2024 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-126
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Fri Jan 19 2024 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-125
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Wed Jul 19 2023 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-124
- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
* Mon Jun 12 2023 Than Ngo <than@redhat.com> - 1.13.0-123
- migrated to SPDX license
* Wed Jan 18 2023 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-122
- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild
* Wed Jul 20 2022 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-121
- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild
* Wed Jan 19 2022 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-120
- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild
* Wed Jul 21 2021 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-119
- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
* Mon Jan 25 2021 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-118
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Fri Jul 31 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-117
- Second attempt - Rebuilt for
https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-116
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-115
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Wed Jul 24 2019 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-114
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
* Thu Jan 31 2019 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-113
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Fri Jan 25 2019 Jonathan Wakely <jwakely@redhat.com> - 1.13.0-112
- Rebuilt for Boost 1.69
* Thu Jul 12 2018 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-111
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Tue Feb 20 2018 Rex Dieter <rdieter@fedoraproject.org> - 1.13.0-110
- BR: gcc-c++, use %%ldconfig_scriptlets
* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-109
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
* Tue Jan 23 2018 Jonathan Wakely <jwakely@redhat.com> - 1.13.0-108
- Rebuilt for Boost 1.66
* Sun Aug 06 2017 Björn Esser <besser82@fedoraproject.org> - 1.13.0-107
- Rebuilt for AutoReq cmake-filesystem
* Wed Aug 02 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-106
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild
* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-105
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
* Tue Jul 18 2017 Jonathan Wakely <jwakely@redhat.com> - 1.13.0-104
- Rebuilt for Boost 1.64
* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-103
- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
* Wed Feb 03 2016 Fedora Release Engineering <releng@fedoraproject.org> - 1.13.0-102
- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
* Thu Dec 17 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-101
- -devel: re-enable dbus-1/interfaces, Conflicts: kf5-akonadi-server-devel
* Fri Dec 11 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-100
- for kf5 kdepim world, build libakonadi bits only (omit server and related files)
* Thu Nov 12 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-22
- Recommends: akonadi-mysql
* Sun Aug 30 2015 Jonathan Wakely <jwakely@redhat.com> 1.13.0-21
- Rebuilt for Boost 1.59
* Wed Aug 05 2015 Jonathan Wakely <jwakely@redhat.com> 1.13.0-20
- Rebuilt for Boost 1.58
* Fri Jul 31 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-19
- pull in latest 1.13 branch fixes
* Wed Jul 29 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.13.0-18
- Rebuilt for https://fedoraproject.org/wiki/Changes/F23Boost159
* Wed Jul 22 2015 David Tardon <dtardon@redhat.com> - 1.13.0-17
- rebuild for Boost 1.58
* Mon Jun 29 2015 Daniel Vrátil <dvratil@redhat.com> - 1.13.0-16
- pull upstream fix for KDE#341884
* Tue Jun 16 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.13.0-15
- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
* Thu May 07 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-14
- %%build: explicitly set -DCMAKE_BUILD_TYPE="Release" (-DQT_NO_DEBUG was used already)
* Mon Apr 27 2015 Daniel Vrátil <dvratil@redhat.com> - 1.13.0-13
- Rebuild for boost
* Sun Mar 08 2015 Rex Dieter <rdieter@fedoraproject.org> - 1.13.0-12
- explicit BuildRequires: mariadb-server
- -mysql: Recommends: mariadb-server (f21+, #1199797)
* Sun Mar 08 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-11
- explicitly Requires: mariadb(-sesrver) only as needed (#1199797)
* Wed Feb 18 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-10
- rebuild (gcc5)
* Wed Feb 04 2015 Petr Machata <pmachata@redhat.com> - 1.13.0-9
- Bump for rebuild.
* Sat Jan 31 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-8
- latest 1.13 branch fixes
* Tue Jan 27 2015 Petr Machata <pmachata@redhat.com> - 1.13.0-7
- Rebuild for boost 1.57.0
* Thu Jan 08 2015 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-6
- drop el6/cmake hacks
* Fri Oct 31 2014 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-5
- latest 1.13 branch fixes
* Sat Sep 27 2014 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-4
- explicitly Requires: mariadb-server/mysql-server as appropriate
* Mon Sep 15 2014 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-3
- pull in some upstream fixes
* Fri Aug 15 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.13.0-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
* Thu Aug 14 2014 Rex Dieter <rdieter@fedoraproject.org> 1.13.0-1
- 1.13.0
* Mon Aug 04 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.91-1
- 1.12.91
* Tue Jul 08 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-10
- scriptlet polish
* Thu Jul 03 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-9
- optimized mimeinfo scriptlet
* Mon Jun 09 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-8
- pull in latest 1.12 branch commits
* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.12.1-7
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
* Thu May 22 2014 Petr Machata <pmachata@redhat.com> - 1.12.1-6
- Rebuild for boost 1.55.0
* Tue Apr 22 2014 Daniel Vrátil <dvratil@redhat.com> 1.12.1-5
- backport 1.12.2 patch to fix upgrade from Akonadi < 1.12 for users with invalid entries in DB
* Wed Apr 16 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-4
- backport master/ branch commits to test sqlite backend concurrency support
* Wed Apr 16 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-3
- WITH_SOPRANO=OFF (kde-4.13,fc21+)
* Tue Apr 15 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-2
- drop mysql-global-mobile.conf, it's too minimalistic
- drop Requires: qt4 >= 4.8.5-10 (workaround for psql driver bug had only a small window a long time ago)
* Tue Apr 08 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.1-1
- 1.12.1
* Wed Mar 26 2014 Rex Dieter <rdieter@fedoraproject.org> 1.12.0-1
- 1.12.0
* Wed Mar 19 2014 Daniel Vrátil <dvratil@redhat.com> 1.11.90-1
- 1.11.90
* Mon Mar 17 2014 Rex Dieter <rdieter@fedoraproject.org> 1.11.80-1
- 1.11.80
* Mon Feb 03 2014 Rex Dieter <rdieter@fedoraproject.org> 1.11.0-2
- rebuild
* Sat Nov 30 2013 Rex Dieter <rdieter@fedoraproject.org> 1.11.0-1
- 1.11.0
* Fri Nov 15 2013 Rex Dieter <rdieter@fedoraproject.org> 1.10.80-1
- 1.10.80
* Mon Oct 07 2013 Daniel Vrátil <dvratil@redhat.com> - 1.10.3-1
- 1.10.3

View file

@ -1 +1 @@
3929b765baa3dc0d548a26893c64abcf akonadi-1.10.3-1.tar.bz2
84eb2e471bd6bdfe54a2a2f1d858c07d akonadi-1.13.0.tar.bz2