File indexing completed on 2024-06-23 05:07:02

0001 
0002 /***************************************************************************
0003  *   SPDX-FileCopyrightText: 2006-2009 Tobias Koenig <tokoe@kde.org>       *
0004  *                                                                         *
0005  *   SPDX-License-Identifier: LGPL-2.0-or-later                            *
0006  ***************************************************************************/
0007 
0008 #include "itemfetchhelper.h"
0009 
0010 #include "akonadi.h"
0011 #include "connection.h"
0012 #include "handler.h"
0013 #include "handlerhelper.h"
0014 #include "shared/akranges.h"
0015 #include "storage/itemqueryhelper.h"
0016 #include "storage/itemretrievalmanager.h"
0017 #include "storage/itemretrievalrequest.h"
0018 #include "storage/parthelper.h"
0019 #include "storage/parttypehelper.h"
0020 #include "storage/selectquerybuilder.h"
0021 #include "storage/transaction.h"
0022 
0023 #include "agentmanagerinterface.h"
0024 #include "akonadiserver_debug.h"
0025 #include "intervalcheck.h"
0026 #include "relationfetchhandler.h"
0027 #include "tagfetchhelper.h"
0028 #include "utils.h"
0029 
0030 #include "private/dbus_p.h"
0031 
0032 #include <QDateTime>
0033 #include <QSqlQuery>
0034 #include <QStringList>
0035 #include <QVariant>
0036 
0037 #include <QElapsedTimer>
0038 
0039 using namespace Akonadi;
0040 using namespace Akonadi::Server;
0041 using namespace AkRanges;
0042 
0043 #define ENABLE_FETCH_PROFILING 0
0044 #if ENABLE_FETCH_PROFILING
0045 #define BEGIN_TIMER(name)                                                                                                                                      \
0046     QElapsedTimer name##Timer;                                                                                                                                 \
0047     name##Timer.start();
0048 
0049 #define END_TIMER(name) const double name##Elapsed = name##Timer.nsecsElapsed() / 1000000.0;
0050 #define PROF_INC(name) ++name;
0051 #else
0052 #define BEGIN_TIMER(name)
0053 #define END_TIMER(name)
0054 #define PROF_INC(name)
0055 #endif
0056 
0057 ItemFetchHelper::ItemFetchHelper(Connection *connection,
0058                                  const Scope &scope,
0059                                  const Protocol::ItemFetchScope &itemFetchScope,
0060                                  const Protocol::TagFetchScope &tagFetchScope,
0061                                  AkonadiServer &akonadi,
0062                                  const Protocol::FetchLimit &itemsLimit)
0063     : ItemFetchHelper(connection, connection->context(), scope, itemFetchScope, tagFetchScope, akonadi, itemsLimit)
0064 {
0065 }
0066 
0067 ItemFetchHelper::ItemFetchHelper(Connection *connection,
0068                                  const CommandContext &context,
0069                                  const Scope &scope,
0070                                  const Protocol::ItemFetchScope &itemFetchScope,
0071                                  const Protocol::TagFetchScope &tagFetchScope,
0072                                  AkonadiServer &akonadi,
0073                                  const Protocol::FetchLimit &itemsLimit)
0074     : mConnection(connection)
0075     , mContext(context)
0076     , mScope(scope)
0077     , mItemFetchScope(itemFetchScope)
0078     , mTagFetchScope(tagFetchScope)
0079     , mAkonadi(akonadi)
0080     , mItemsLimit(itemsLimit)
0081     , mItemQuery(PimItem::tableName())
0082     , mPimItemQueryAlias(QLatin1StringView("pimItem_alias"))
0083 {
0084     std::fill(mItemQueryColumnMap, mItemQueryColumnMap + ItemQueryColumnCount, -1);
0085 }
0086 
0087 void ItemFetchHelper::disableATimeUpdates()
0088 {
0089     mUpdateATimeEnabled = false;
0090 }
0091 
0092 enum PartQueryColumns {
0093     PartQueryPimIdColumn,
0094     PartQueryTypeIdColumn,
0095     PartQueryDataColumn,
0096     PartQueryStorageColumn,
0097     PartQueryVersionColumn,
0098     PartQueryDataSizeColumn
0099 };
0100 
0101 QSqlQuery ItemFetchHelper::buildPartQuery(const QList<QByteArray> &partList, bool allPayload, bool allAttrs)
0102 {
0103     /// TODO: merge with ItemQuery
0104     QueryBuilder partQuery(PimItem::tableName());
0105     if (mItemsLimit.limit() > 0) {
0106         partQuery = QueryBuilder(mItemQuery.query(), mPimItemQueryAlias);
0107     }
0108 
0109     if (!partList.isEmpty() || allPayload || allAttrs) {
0110         partQuery.addJoin(QueryBuilder::InnerJoin, Part::tableName(), partQuery.getTableWithColumn(PimItem::idColumn()), Part::pimItemIdFullColumnName());
0111         partQuery.addColumn(partQuery.getTableWithColumn(PimItem::idColumn()));
0112         partQuery.addColumn(Part::partTypeIdFullColumnName());
0113         partQuery.addColumn(Part::dataFullColumnName());
0114         partQuery.addColumn(Part::storageFullColumnName());
0115         partQuery.addColumn(Part::versionFullColumnName());
0116         partQuery.addColumn(Part::datasizeFullColumnName());
0117 
0118         partQuery.addSortColumn(partQuery.getTableWithColumn(PimItem::idColumn()), Query::Descending);
0119 
0120         if (!partList.isEmpty() || allPayload || allAttrs) {
0121             Query::Condition cond(Query::Or);
0122             for (const QByteArray &b : std::as_const(partList)) {
0123                 if (b.startsWith("PLD") || b.startsWith("ATR")) {
0124                     cond.addValueCondition(Part::partTypeIdFullColumnName(), Query::Equals, PartTypeHelper::fromFqName(b).id());
0125                 }
0126             }
0127             if (allPayload || allAttrs) {
0128                 partQuery.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName());
0129                 if (allPayload) {
0130                     cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("PLD"));
0131                 }
0132                 if (allAttrs) {
0133                     cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("ATR"));
0134                 }
0135             }
0136 
0137             partQuery.addCondition(cond);
0138         }
0139 
0140         ItemQueryHelper::scopeToQuery(mScope, mContext, partQuery);
0141 
0142         if (!partQuery.exec()) {
0143             throw HandlerException("Unable to list item parts");
0144         }
0145         partQuery.query().next();
0146     }
0147 
0148     return partQuery.query();
0149 }
0150 
0151 QSqlQuery ItemFetchHelper::buildItemQuery()
0152 {
0153     int column = 0;
0154 #define ADD_COLUMN(colName, colId)                                                                                                                             \
0155     {                                                                                                                                                          \
0156         mItemQuery.addColumn(colName);                                                                                                                         \
0157         mItemQueryColumnMap[colId] = column++;                                                                                                                 \
0158     }
0159     ADD_COLUMN(PimItem::idFullColumnName(), ItemQueryPimItemIdColumn);
0160     if (mItemFetchScope.fetchRemoteId()) {
0161         ADD_COLUMN(PimItem::remoteIdFullColumnName(), ItemQueryPimItemRidColumn)
0162     }
0163     ADD_COLUMN(PimItem::mimeTypeIdFullColumnName(), ItemQueryMimeTypeIdColumn)
0164     ADD_COLUMN(PimItem::revFullColumnName(), ItemQueryRevColumn)
0165     if (mItemFetchScope.fetchRemoteRevision()) {
0166         ADD_COLUMN(PimItem::remoteRevisionFullColumnName(), ItemQueryRemoteRevisionColumn)
0167     }
0168     if (mItemFetchScope.fetchSize()) {
0169         ADD_COLUMN(PimItem::sizeFullColumnName(), ItemQuerySizeColumn)
0170     }
0171     if (mItemFetchScope.fetchMTime()) {
0172         ADD_COLUMN(PimItem::datetimeFullColumnName(), ItemQueryDatetimeColumn)
0173     }
0174     ADD_COLUMN(PimItem::collectionIdFullColumnName(), ItemQueryCollectionIdColumn)
0175     if (mItemFetchScope.fetchGID()) {
0176         ADD_COLUMN(PimItem::gidFullColumnName(), ItemQueryPimItemGidColumn)
0177     }
0178 #undef ADD_COLUMN
0179 
0180     mItemQuery.addSortColumn(PimItem::idFullColumnName(), static_cast<Query::SortOrder>(mItemsLimit.sortOrder()));
0181     if (mItemsLimit.limit() > 0) {
0182         mItemQuery.setLimit(mItemsLimit.limit(), mItemsLimit.limitOffset());
0183     }
0184 
0185     ItemQueryHelper::scopeToQuery(mScope, mContext, mItemQuery);
0186 
0187     if (mItemFetchScope.changedSince().isValid()) {
0188         mItemQuery.addValueCondition(PimItem::datetimeFullColumnName(), Query::GreaterOrEqual, mItemFetchScope.changedSince().toUTC());
0189     }
0190 
0191     if (!mItemQuery.exec()) {
0192         throw HandlerException("Unable to list items");
0193     }
0194 
0195     mItemQuery.query().next();
0196 
0197     return mItemQuery.query();
0198 }
0199 
0200 enum FlagQueryColumns {
0201     FlagQueryPimItemIdColumn,
0202     FlagQueryFlagIdColumn,
0203 };
0204 
0205 QSqlQuery ItemFetchHelper::buildFlagQuery()
0206 {
0207     QueryBuilder flagQuery(PimItem::tableName());
0208     if (mItemsLimit.limit() > 0) {
0209         flagQuery = QueryBuilder(mItemQuery.query(), mPimItemQueryAlias);
0210     }
0211 
0212     flagQuery.addJoin(QueryBuilder::InnerJoin,
0213                       PimItemFlagRelation::tableName(),
0214                       flagQuery.getTableWithColumn(PimItem::idColumn()),
0215                       PimItemFlagRelation::leftFullColumnName());
0216 
0217     flagQuery.addColumn(flagQuery.getTableWithColumn(PimItem::idColumn()));
0218     flagQuery.addColumn(PimItemFlagRelation::rightFullColumnName());
0219 
0220     ItemQueryHelper::scopeToQuery(mScope, mContext, flagQuery);
0221     flagQuery.addSortColumn(flagQuery.getTableWithColumn(PimItem::idColumn()), Query::Descending);
0222 
0223     if (!flagQuery.exec()) {
0224         throw HandlerException("Unable to retrieve item flags");
0225     }
0226 
0227     flagQuery.query().next();
0228 
0229     return flagQuery.query();
0230 }
0231 
0232 enum TagQueryColumns {
0233     TagQueryItemIdColumn,
0234     TagQueryTagIdColumn,
0235 };
0236 
0237 QSqlQuery ItemFetchHelper::buildTagQuery()
0238 {
0239     QueryBuilder tagQuery(PimItem::tableName());
0240     if (mItemsLimit.limit() > 0) {
0241         tagQuery = QueryBuilder(mItemQuery.query(), mPimItemQueryAlias);
0242     }
0243 
0244     tagQuery.addJoin(QueryBuilder::InnerJoin,
0245                      PimItemTagRelation::tableName(),
0246                      tagQuery.getTableWithColumn(PimItem::idColumn()),
0247                      PimItemTagRelation::leftFullColumnName());
0248     tagQuery.addJoin(QueryBuilder::InnerJoin, Tag::tableName(), Tag::idFullColumnName(), PimItemTagRelation::rightFullColumnName());
0249     tagQuery.addColumn(tagQuery.getTableWithColumn(PimItem::idColumn()));
0250     tagQuery.addColumn(Tag::idFullColumnName());
0251 
0252     ItemQueryHelper::scopeToQuery(mScope, mContext, tagQuery);
0253     tagQuery.addSortColumn(tagQuery.getTableWithColumn(PimItem::idColumn()), Query::Descending);
0254 
0255     if (!tagQuery.exec()) {
0256         throw HandlerException("Unable to retrieve item tags");
0257     }
0258 
0259     tagQuery.query().next();
0260 
0261     return tagQuery.query();
0262 }
0263 
0264 enum VRefQueryColumns {
0265     VRefQueryCollectionIdColumn,
0266     VRefQueryItemIdColumn,
0267 };
0268 
0269 QSqlQuery ItemFetchHelper::buildVRefQuery()
0270 {
0271     QueryBuilder vRefQuery(PimItem::tableName());
0272     if (mItemsLimit.limit() > 0) {
0273         vRefQuery = QueryBuilder(mItemQuery.query(), mPimItemQueryAlias);
0274     }
0275 
0276     vRefQuery.addJoin(QueryBuilder::LeftJoin,
0277                       CollectionPimItemRelation::tableName(),
0278                       CollectionPimItemRelation::rightFullColumnName(),
0279                       vRefQuery.getTableWithColumn(PimItem::idColumn()));
0280     vRefQuery.addColumn(CollectionPimItemRelation::leftFullColumnName());
0281     vRefQuery.addColumn(CollectionPimItemRelation::rightFullColumnName());
0282     ItemQueryHelper::scopeToQuery(mScope, mContext, vRefQuery);
0283     vRefQuery.addSortColumn(vRefQuery.getTableWithColumn(PimItem::idColumn()), Query::Descending);
0284 
0285     if (!vRefQuery.exec()) {
0286         throw HandlerException("Unable to retrieve virtual references");
0287     }
0288 
0289     vRefQuery.query().next();
0290 
0291     return vRefQuery.query();
0292 }
0293 
0294 bool ItemFetchHelper::isScopeLocal(const Scope &scope)
0295 {
0296     // The only agent allowed to override local scope is the Baloo Indexer
0297     if (!mConnection->sessionId().startsWith("akonadi_indexing_agent")) {
0298         return false;
0299     }
0300 
0301     // Get list of all resources that own all items in the scope
0302     QueryBuilder qb(PimItem::tableName(), QueryBuilder::Select);
0303     qb.setDistinct(true);
0304     qb.addColumn(Resource::nameFullColumnName());
0305     qb.addJoin(QueryBuilder::LeftJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName());
0306     qb.addJoin(QueryBuilder::LeftJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName());
0307     ItemQueryHelper::scopeToQuery(scope, mContext, qb);
0308     if (mContext.resource().isValid()) {
0309         qb.addValueCondition(Resource::nameFullColumnName(), Query::NotEquals, mContext.resource().name());
0310     }
0311 
0312     if (!qb.exec()) {
0313         throw HandlerException("Failed to query database");
0314         return false;
0315     }
0316 
0317     // If there is more than one resource, i.e. this is a fetch from multiple
0318     // collections, then don't bother and just return FALSE. This case is aimed
0319     // specifically on Baloo, which fetches items from each collection independently,
0320     // so it will pass this check.
0321     QSqlQuery query = qb.query();
0322     if (query.size() != 1) {
0323         return false;
0324     }
0325 
0326     query.next();
0327     const QString resourceName = query.value(0).toString();
0328     query.finish();
0329 
0330     org::freedesktop::Akonadi::AgentManager manager(DBus::serviceName(DBus::Control), QStringLiteral("/AgentManager"), QDBusConnection::sessionBus());
0331     const QString typeIdentifier = manager.agentInstanceType(resourceName);
0332     const QVariantMap properties = manager.agentCustomProperties(typeIdentifier);
0333     return properties.value(QStringLiteral("HasLocalStorage"), false).toBool();
0334 }
0335 
0336 DataStore *ItemFetchHelper::storageBackend() const
0337 {
0338     if (mConnection) {
0339         if (auto store = mConnection->storageBackend()) {
0340             return store;
0341         }
0342     }
0343 
0344     return DataStore::self();
0345 }
0346 
0347 bool ItemFetchHelper::fetchItems(std::function<void(Protocol::FetchItemsResponse &&)> &&itemCallback)
0348 {
0349     BEGIN_TIMER(fetch)
0350 
0351     // retrieve missing parts
0352     // HACK: isScopeLocal() is a workaround for resources that have cache expiration
0353     // because when the cache expires, Baloo is not able to content of the items. So
0354     // we allow fetch of items that belong to local resources (like maildir) to ignore
0355     // cacheOnly and retrieve missing parts from the resource. However ItemRetriever
0356     // is painfully slow with many items and is generally designed to fetch a few
0357     // messages, not all of them. In the long term, we need a better way to do this.
0358     BEGIN_TIMER(itemRetriever)
0359     BEGIN_TIMER(scopeLocal)
0360 #if ENABLE_FETCH_PROFILING
0361     double scopeLocalElapsed = 0;
0362 #endif
0363     if (!mItemFetchScope.cacheOnly() || isScopeLocal(mScope)) {
0364 #if ENABLE_FETCH_PROFILING
0365         scopeLocalElapsed = scopeLocalTimer.elapsed();
0366 #endif
0367 
0368         // trigger a collection sync if configured to do so
0369         triggerOnDemandFetch();
0370 
0371         // Prepare for a call to ItemRetriever::exec();
0372         // From a resource perspective the only parts that can be fetched are payloads.
0373         ItemRetriever retriever(mAkonadi.itemRetrievalManager(), mConnection, mContext);
0374         retriever.setScope(mScope);
0375         retriever.setRetrieveParts(mItemFetchScope.requestedPayloads());
0376         retriever.setRetrieveFullPayload(mItemFetchScope.fullPayload());
0377         retriever.setChangedSince(mItemFetchScope.changedSince());
0378         if (!retriever.exec() && !mItemFetchScope.ignoreErrors()) { // There we go, retrieve the missing parts from the resource.
0379             if (mContext.resource().isValid()) {
0380                 throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1, resource %2) : %3")
0381                                            .arg(mContext.collectionId())
0382                                            .arg(mContext.resource().id())
0383                                            .arg(QString::fromLatin1(retriever.lastError())));
0384             } else {
0385                 throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1) : %2")
0386                                            .arg(mContext.collectionId())
0387                                            .arg(QString::fromLatin1(retriever.lastError())));
0388             }
0389         }
0390     }
0391     END_TIMER(itemRetriever)
0392 
0393     BEGIN_TIMER(items)
0394     QSqlQuery itemQuery = buildItemQuery();
0395     END_TIMER(items)
0396 
0397     // error if query did not find any item and scope is not listing items but
0398     // a request for a specific item
0399     if (!itemQuery.isValid()) {
0400         if (mItemFetchScope.ignoreErrors()) {
0401             return true;
0402         }
0403         switch (mScope.scope()) {
0404         case Scope::Uid: // fall through
0405         case Scope::Rid: // fall through
0406         case Scope::HierarchicalRid: // fall through
0407         case Scope::Gid:
0408             throw HandlerException("Item query returned empty result set");
0409             break;
0410         default:
0411             break;
0412         }
0413     }
0414     // build part query if needed
0415     BEGIN_TIMER(parts)
0416     QSqlQuery partQuery(storageBackend()->database());
0417     if (!mItemFetchScope.requestedParts().isEmpty() || mItemFetchScope.fullPayload() || mItemFetchScope.allAttributes()) {
0418         partQuery = buildPartQuery(mItemFetchScope.requestedParts(), mItemFetchScope.fullPayload(), mItemFetchScope.allAttributes());
0419     }
0420     END_TIMER(parts)
0421 
0422     // build flag query if needed
0423     BEGIN_TIMER(flags)
0424     QSqlQuery flagQuery(storageBackend()->database());
0425     if (mItemFetchScope.fetchFlags()) {
0426         flagQuery = buildFlagQuery();
0427     }
0428     END_TIMER(flags)
0429 
0430     // build tag query if needed
0431     BEGIN_TIMER(tags)
0432     QSqlQuery tagQuery(storageBackend()->database());
0433     if (mItemFetchScope.fetchTags()) {
0434         tagQuery = buildTagQuery();
0435     }
0436     END_TIMER(tags)
0437 
0438     BEGIN_TIMER(vRefs)
0439     QSqlQuery vRefQuery(storageBackend()->database());
0440     if (mItemFetchScope.fetchVirtualReferences()) {
0441         vRefQuery = buildVRefQuery();
0442     }
0443     END_TIMER(vRefs)
0444 
0445 #if ENABLE_FETCH_PROFILING
0446     int itemsCount = 0;
0447     int flagsCount = 0;
0448     int partsCount = 0;
0449     int tagsCount = 0;
0450     int vRefsCount = 0;
0451 #endif
0452 
0453     BEGIN_TIMER(processing)
0454     QHash<qint64, QByteArray> flagIdNameCache;
0455     QHash<qint64, QString> mimeTypeIdNameCache;
0456     QHash<qint64, QByteArray> partTypeIdNameCache;
0457     while (itemQuery.isValid()) {
0458         PROF_INC(itemsCount)
0459 
0460         const qint64 pimItemId = extractQueryResult(itemQuery, ItemQueryPimItemIdColumn).toLongLong();
0461         const int pimItemRev = extractQueryResult(itemQuery, ItemQueryRevColumn).toInt();
0462 
0463         Protocol::FetchItemsResponse response;
0464         response.setId(pimItemId);
0465         response.setRevision(pimItemRev);
0466         const qint64 mimeTypeId = extractQueryResult(itemQuery, ItemQueryMimeTypeIdColumn).toLongLong();
0467         auto mtIter = mimeTypeIdNameCache.find(mimeTypeId);
0468         if (mtIter == mimeTypeIdNameCache.end()) {
0469             mtIter = mimeTypeIdNameCache.insert(mimeTypeId, MimeType::retrieveById(mimeTypeId).name());
0470         }
0471         response.setMimeType(mtIter.value());
0472         if (mItemFetchScope.fetchRemoteId()) {
0473             response.setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString());
0474         }
0475         response.setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong());
0476 
0477         if (mItemFetchScope.fetchSize()) {
0478             response.setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong());
0479         }
0480         if (mItemFetchScope.fetchMTime()) {
0481             response.setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn)));
0482         }
0483         if (mItemFetchScope.fetchRemoteRevision()) {
0484             response.setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString());
0485         }
0486         if (mItemFetchScope.fetchGID()) {
0487             response.setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString());
0488         }
0489 
0490         if (mItemFetchScope.fetchFlags()) {
0491             QList<QByteArray> flags;
0492             while (flagQuery.isValid()) {
0493                 const qint64 id = flagQuery.value(FlagQueryPimItemIdColumn).toLongLong();
0494                 if (id > pimItemId) {
0495                     flagQuery.next();
0496                     continue;
0497                 } else if (id < pimItemId) {
0498                     break;
0499                 }
0500                 const qint64 flagId = flagQuery.value(FlagQueryFlagIdColumn).toLongLong();
0501                 auto flagNameIter = flagIdNameCache.find(flagId);
0502                 if (flagNameIter == flagIdNameCache.end()) {
0503                     flagNameIter = flagIdNameCache.insert(flagId, Flag::retrieveById(flagId).name().toUtf8());
0504                 }
0505                 flags << flagNameIter.value();
0506                 flagQuery.next();
0507             }
0508             response.setFlags(flags);
0509         }
0510 
0511         if (mItemFetchScope.fetchTags()) {
0512             QList<qint64> tagIds;
0513             QList<Protocol::FetchTagsResponse> tags;
0514             while (tagQuery.isValid()) {
0515                 PROF_INC(tagsCount)
0516                 const qint64 id = tagQuery.value(TagQueryItemIdColumn).toLongLong();
0517                 if (id > pimItemId) {
0518                     tagQuery.next();
0519                     continue;
0520                 } else if (id < pimItemId) {
0521                     break;
0522                 }
0523                 tagIds << tagQuery.value(TagQueryTagIdColumn).toLongLong();
0524                 tagQuery.next();
0525             }
0526 
0527             if (mTagFetchScope.fetchIdOnly()) {
0528                 tags = tagIds | Views::transform([](const auto tagId) {
0529                            Protocol::FetchTagsResponse resp;
0530                            resp.setId(tagId);
0531                            return resp;
0532                        })
0533                     | Actions::toQVector;
0534             } else {
0535                 tags = tagIds | Views::transform([this](const auto tagId) {
0536                            return HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId), mTagFetchScope, mConnection);
0537                        })
0538                     | Actions::toQVector;
0539             }
0540             response.setTags(tags);
0541         }
0542 
0543         if (mItemFetchScope.fetchVirtualReferences()) {
0544             QList<qint64> vRefs;
0545             while (vRefQuery.isValid()) {
0546                 PROF_INC(vRefsCount)
0547                 const qint64 id = vRefQuery.value(VRefQueryItemIdColumn).toLongLong();
0548                 if (id > pimItemId) {
0549                     vRefQuery.next();
0550                     continue;
0551                 } else if (id < pimItemId) {
0552                     break;
0553                 }
0554                 vRefs << vRefQuery.value(VRefQueryCollectionIdColumn).toLongLong();
0555                 vRefQuery.next();
0556             }
0557             response.setVirtualReferences(vRefs);
0558         }
0559 
0560         if (mItemFetchScope.fetchRelations()) {
0561             SelectQueryBuilder<Relation> qb;
0562             Query::Condition condition;
0563             condition.setSubQueryMode(Query::Or);
0564             condition.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, pimItemId);
0565             condition.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, pimItemId);
0566             qb.addCondition(condition);
0567             qb.addGroupColumns(QStringList() << Relation::leftIdColumn() << Relation::rightIdColumn() << Relation::typeIdColumn()
0568                                              << Relation::remoteIdColumn());
0569             if (!qb.exec()) {
0570                 throw HandlerException("Unable to list item relations");
0571             }
0572             QList<Protocol::FetchRelationsResponse> relations;
0573             const auto result = qb.result();
0574             relations.reserve(result.size());
0575             for (const Relation &rel : result) {
0576                 relations.push_back(HandlerHelper::fetchRelationsResponse(rel));
0577                 ;
0578             }
0579             response.setRelations(relations);
0580         }
0581 
0582         if (mItemFetchScope.ancestorDepth() != Protocol::ItemFetchScope::NoAncestor) {
0583             response.setAncestors(ancestorsForItem(response.parentId()));
0584         }
0585 
0586         bool skipItem = false;
0587 
0588         QList<QByteArray> cachedParts;
0589         QList<Protocol::StreamPayloadResponse> parts;
0590         while (partQuery.isValid()) {
0591             PROF_INC(partsCount)
0592             const qint64 id = partQuery.value(PartQueryPimIdColumn).toLongLong();
0593             if (id > pimItemId) {
0594                 partQuery.next();
0595                 continue;
0596             } else if (id < pimItemId) {
0597                 break;
0598             }
0599 
0600             const qint64 partTypeId = partQuery.value(PartQueryTypeIdColumn).toLongLong();
0601             auto ptIter = partTypeIdNameCache.find(partTypeId);
0602             if (ptIter == partTypeIdNameCache.end()) {
0603                 ptIter = partTypeIdNameCache.insert(partTypeId, PartTypeHelper::fullName(PartType::retrieveById(partTypeId)).toUtf8());
0604             }
0605             Protocol::PartMetaData metaPart;
0606             Protocol::StreamPayloadResponse partData;
0607             partData.setPayloadName(ptIter.value());
0608             metaPart.setName(ptIter.value());
0609             metaPart.setVersion(partQuery.value(PartQueryVersionColumn).toInt());
0610             metaPart.setSize(partQuery.value(PartQueryDataSizeColumn).toLongLong());
0611 
0612             const QByteArray data = Utils::variantToByteArray(partQuery.value(PartQueryDataColumn));
0613             if (mItemFetchScope.checkCachedPayloadPartsOnly()) {
0614                 if (!data.isEmpty()) {
0615                     cachedParts << ptIter.value();
0616                 }
0617                 partQuery.next();
0618             } else {
0619                 if (mItemFetchScope.ignoreErrors() && data.isEmpty()) {
0620                     // We wanted the payload, couldn't get it, and are ignoring errors. Skip the item.
0621                     // This is not an error though, it's fine to have empty payload parts (to denote existing but not cached parts)
0622                     qCDebug(AKONADISERVER_LOG) << "item" << id << "has an empty payload part in parttable for part" << metaPart.name();
0623                     skipItem = true;
0624                     break;
0625                 }
0626                 metaPart.setStorageType(static_cast<Protocol::PartMetaData::StorageType>(partQuery.value(PartQueryStorageColumn).toInt()));
0627                 if (data.isEmpty()) {
0628                     partData.setData(QByteArray(""));
0629                 } else {
0630                     partData.setData(data);
0631                 }
0632                 partData.setMetaData(metaPart);
0633 
0634                 if (mItemFetchScope.requestedParts().contains(ptIter.value()) || mItemFetchScope.fullPayload() || mItemFetchScope.allAttributes()) {
0635                     parts.append(partData);
0636                 }
0637 
0638                 partQuery.next();
0639             }
0640         }
0641         response.setParts(parts);
0642 
0643         if (skipItem) {
0644             itemQuery.next();
0645             continue;
0646         }
0647 
0648         if (mItemFetchScope.checkCachedPayloadPartsOnly()) {
0649             response.setCachedParts(cachedParts);
0650         }
0651 
0652         if (itemCallback) {
0653             itemCallback(std::move(response));
0654         } else {
0655             mConnection->sendResponse(std::move(response));
0656         }
0657 
0658         itemQuery.next();
0659     }
0660     tagQuery.finish();
0661     flagQuery.finish();
0662     partQuery.finish();
0663     vRefQuery.finish();
0664     itemQuery.finish();
0665     END_TIMER(processing)
0666 
0667     // update atime (only if the payload was actually requested, otherwise a simple resource sync prevents cache clearing)
0668     BEGIN_TIMER(aTime)
0669     if (mUpdateATimeEnabled && (needsAccessTimeUpdate(mItemFetchScope.requestedParts()) || mItemFetchScope.fullPayload())) {
0670         updateItemAccessTime();
0671     }
0672     END_TIMER(aTime)
0673 
0674     END_TIMER(fetch)
0675 #if ENABLE_FETCH_PROFILING
0676     qCDebug(AKONADISERVER_LOG) << "ItemFetchHelper execution stats:";
0677     qCDebug(AKONADISERVER_LOG) << "\tItems query:" << itemsElapsed << "ms," << itemsCount << " items in total";
0678     qCDebug(AKONADISERVER_LOG) << "\tFlags query:" << flagsElapsed << "ms, " << flagsCount << " flags in total";
0679     qCDebug(AKONADISERVER_LOG) << "\tParts query:" << partsElapsed << "ms, " << partsCount << " parts in total";
0680     qCDebug(AKONADISERVER_LOG) << "\tTags query: " << tagsElapsed << "ms, " << tagsCount << " tags in total";
0681     qCDebug(AKONADISERVER_LOG) << "\tVRefs query:" << vRefsElapsed << "ms, " << vRefsCount << " vRefs in total";
0682     qCDebug(AKONADISERVER_LOG) << "\t------------";
0683     qCDebug(AKONADISERVER_LOG) << "\tItem retriever:" << itemRetrieverElapsed << "ms (scope local:" << scopeLocalElapsed << "ms)";
0684     qCDebug(AKONADISERVER_LOG) << "\tTotal query:" << (itemsElapsed + flagsElapsed + partsElapsed + tagsElapsed + vRefsElapsed) << "ms";
0685     qCDebug(AKONADISERVER_LOG) << "\tTotal processing: " << processingElapsed << "ms";
0686     qCDebug(AKONADISERVER_LOG) << "\tATime update:" << aTimeElapsed << "ms";
0687     qCDebug(AKONADISERVER_LOG) << "\t============";
0688     qCDebug(AKONADISERVER_LOG) << "\tTotal FETCH:" << fetchElapsed << "ms";
0689     qCDebug(AKONADISERVER_LOG);
0690     qCDebug(AKONADISERVER_LOG);
0691 #endif
0692 
0693     return true;
0694 }
0695 
0696 bool ItemFetchHelper::needsAccessTimeUpdate(const QList<QByteArray> &parts)
0697 {
0698     // TODO technically we should compare the part list with the cache policy of
0699     // the parent collection of the retrieved items, but that's kinda expensive
0700     // Only updating the atime if the full payload was requested is a good
0701     // approximation though.
0702     return parts.contains(AKONADI_PARAM_PLD_RFC822);
0703 }
0704 
0705 void ItemFetchHelper::updateItemAccessTime()
0706 {
0707     Transaction transaction(storageBackend(), QStringLiteral("update atime"));
0708     QueryBuilder qb(PimItem::tableName(), QueryBuilder::Update);
0709     qb.setColumnValue(PimItem::atimeColumn(), QDateTime::currentDateTimeUtc());
0710     ItemQueryHelper::scopeToQuery(mScope, mContext, qb);
0711 
0712     if (!qb.exec()) {
0713         qCWarning(AKONADISERVER_LOG) << "Unable to update item access time";
0714     } else {
0715         transaction.commit();
0716     }
0717 }
0718 
0719 void ItemFetchHelper::triggerOnDemandFetch()
0720 {
0721     if (mContext.collectionId() <= 0 || mItemFetchScope.cacheOnly()) {
0722         return;
0723     }
0724 
0725     Collection collection = mContext.collection();
0726 
0727     // HACK: don't trigger on-demand syncing if the resource is the one triggering it
0728     if (mConnection->sessionId() == collection.resource().name().toLatin1()) {
0729         return;
0730     }
0731 
0732     storageBackend()->activeCachePolicy(collection);
0733     if (!collection.cachePolicySyncOnDemand()) {
0734         return;
0735     }
0736 
0737     mConnection->akonadi().intervalChecker().requestCollectionSync(collection);
0738 }
0739 
0740 QList<Protocol::Ancestor> ItemFetchHelper::ancestorsForItem(Collection::Id parentColId)
0741 {
0742     if (mItemFetchScope.ancestorDepth() == Protocol::ItemFetchScope::NoAncestor || parentColId == 0) {
0743         return QList<Protocol::Ancestor>();
0744     }
0745     const auto it = mAncestorCache.constFind(parentColId);
0746     if (it != mAncestorCache.cend()) {
0747         return *it;
0748     }
0749 
0750     QList<Protocol::Ancestor> ancestors;
0751     Collection col = Collection::retrieveById(parentColId);
0752     const int depthNum = mItemFetchScope.ancestorDepth() == Protocol::ItemFetchScope::ParentAncestor ? 1 : INT_MAX;
0753     for (int i = 0; i < depthNum; ++i) {
0754         if (!col.isValid()) {
0755             Protocol::Ancestor ancestor;
0756             ancestor.setId(0);
0757             ancestors << ancestor;
0758             break;
0759         }
0760         Protocol::Ancestor ancestor;
0761         ancestor.setId(col.id());
0762         ancestor.setRemoteId(col.remoteId());
0763         ancestors << ancestor;
0764         col = col.parent();
0765     }
0766     mAncestorCache.insert(parentColId, ancestors);
0767     return ancestors;
0768 }
0769 
0770 QVariant ItemFetchHelper::extractQueryResult(const QSqlQuery &query, ItemFetchHelper::ItemQueryColumns column) const
0771 {
0772     const int colId = mItemQueryColumnMap[column];
0773     Q_ASSERT(colId >= 0);
0774     return query.value(colId);
0775 }