Warning, file /frameworks/kactivities-stats/src/resultset.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic <ivan.cukic(at)kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "resultset.h" 0008 0009 // Qt 0010 #include <QSqlQuery> 0011 #include <QSqlError> 0012 #include <QCoreApplication> 0013 #include <QDir> 0014 #include <QUrl> 0015 0016 // Local 0017 #include <common/database/Database.h> 0018 #include <common/specialvalues.h> 0019 #include <utils/debug_and_return.h> 0020 #include <utils/qsqlquery_iterator.h> 0021 #include "kactivities-stats-logsettings.h" 0022 0023 // STL 0024 #include <iterator> 0025 #include <functional> 0026 #include <mutex> 0027 0028 // KActivities 0029 #include "activitiessync_p.h" 0030 0031 #define DEBUG_QUERIES 0 0032 0033 namespace KActivities { 0034 namespace Stats { 0035 0036 using namespace Terms; 0037 0038 class ResultSet_ResultPrivate { 0039 public: 0040 QString resource; 0041 QString title; 0042 QString mimetype; 0043 double score; 0044 uint lastUpdate; 0045 uint firstUpdate; 0046 ResultSet::Result::LinkStatus linkStatus; 0047 QStringList linkedActivities; 0048 0049 }; 0050 0051 ResultSet::Result::Result() 0052 : d(new ResultSet_ResultPrivate()) 0053 { 0054 } 0055 0056 ResultSet::Result::Result(Result &&result) 0057 : d(result.d) 0058 { 0059 result.d = nullptr; 0060 } 0061 0062 ResultSet::Result::Result(const Result &result) 0063 : d(new ResultSet_ResultPrivate(*result.d)) 0064 { 0065 } 0066 0067 ResultSet::Result &ResultSet::Result::operator=(Result result) 0068 { 0069 std::swap(d, result.d); 0070 0071 return *this; 0072 } 0073 0074 ResultSet::Result::~Result() 0075 { 0076 delete d; 0077 } 0078 0079 /* clang-format off */ 0080 #define CREATE_GETTER_AND_SETTER(Type, Name, Set) \ 0081 Type ResultSet::Result::Name() const \ 0082 { \ 0083 return d->Name; \ 0084 } \ 0085 \ 0086 void ResultSet::Result::Set(Type Name) \ 0087 { \ 0088 d->Name = Name; \ 0089 } 0090 /* clang-format on */ 0091 0092 CREATE_GETTER_AND_SETTER(QString, resource, setResource) 0093 CREATE_GETTER_AND_SETTER(QString, title, setTitle) 0094 CREATE_GETTER_AND_SETTER(QString, mimetype, setMimetype) 0095 CREATE_GETTER_AND_SETTER(double, score, setScore) 0096 CREATE_GETTER_AND_SETTER(uint, lastUpdate, setLastUpdate) 0097 CREATE_GETTER_AND_SETTER(uint, firstUpdate, setFirstUpdate) 0098 CREATE_GETTER_AND_SETTER(ResultSet::Result::LinkStatus, linkStatus, setLinkStatus) 0099 CREATE_GETTER_AND_SETTER(QStringList, linkedActivities, setLinkedActivities) 0100 0101 #undef CREATE_GETTER_AND_SETTER 0102 0103 QUrl ResultSet::Result::url() const 0104 { 0105 if (QDir::isAbsolutePath(d->resource)) { 0106 return QUrl::fromLocalFile(d->resource); 0107 } else { 0108 return QUrl(d->resource); 0109 } 0110 } 0111 0112 class ResultSetPrivate { 0113 public: 0114 Common::Database::Ptr database; 0115 QSqlQuery query; 0116 Query queryDefinition; 0117 0118 mutable ActivitiesSync::ConsumerPtr activities; 0119 0120 void initQuery() 0121 { 0122 if (!database || query.isActive()) { 0123 return; 0124 } 0125 0126 auto selection = queryDefinition.selection(); 0127 0128 /* clang-format off */ 0129 query = database->execQuery(replaceQueryParameters( 0130 selection == LinkedResources ? linkedResourcesQuery() 0131 : selection == UsedResources ? usedResourcesQuery() 0132 : selection == AllResources ? allResourcesQuery() 0133 : QString())); 0134 /* clang-format on */ 0135 0136 if (query.lastError().isValid()) { 0137 qCWarning(KACTIVITIES_STATS_LOG) << "[Error at ResultSetPrivate::initQuery]: " << query.lastError(); 0138 } 0139 } 0140 0141 QString agentClause(const QString &agent) const 0142 { 0143 if (agent == QLatin1String(":any")) { 0144 return QStringLiteral("1"); 0145 } 0146 0147 /* clang-format off */ 0148 return QStringLiteral("agent = '") + ( 0149 agent == QLatin1String(":current") ? QCoreApplication::instance()->applicationName() : 0150 agent 0151 ) + QStringLiteral("'"); 0152 /* clang-format on */ 0153 } 0154 0155 QString activityClause(const QString &activity) const 0156 { 0157 if (activity == QLatin1String(":any")) { 0158 return QStringLiteral("1"); 0159 } 0160 0161 /* clang-format off */ 0162 return QStringLiteral("activity = '") + ( 0163 activity == QLatin1String(":current") ? ActivitiesSync::currentActivity(activities) : 0164 activity 0165 ) + QStringLiteral("'"); 0166 /* clang-format on */ 0167 } 0168 0169 inline QString starPattern(const QString &pattern) const 0170 { 0171 return Common::parseStarPattern(pattern, QStringLiteral("%"), [] (QString str) { 0172 return str.replace(QLatin1String("%"), QLatin1String("\\%")).replace(QLatin1String("_"), QLatin1String("\\_")); 0173 }); 0174 } 0175 0176 QString urlFilterClause(const QString &urlFilter) const 0177 { 0178 if (urlFilter == QLatin1String("*")) { 0179 return QStringLiteral("1"); 0180 } 0181 0182 return QStringLiteral("resource LIKE '") + Common::starPatternToLike(urlFilter) + QStringLiteral("' ESCAPE '\\'"); 0183 } 0184 0185 QString mimetypeClause(const QString &mimetype) const 0186 { 0187 if (mimetype == ANY_TYPE_TAG || mimetype == QLatin1String("*")) { 0188 return QStringLiteral("1"); 0189 0190 } else if (mimetype == FILES_TYPE_TAG) { 0191 return QStringLiteral("mimetype != 'inode/directory' AND mimetype != ''"); 0192 } else if (mimetype == DIRECTORIES_TYPE_TAG) { 0193 return QStringLiteral("mimetype = 'inode/directory'"); 0194 } 0195 0196 return QStringLiteral("mimetype LIKE '") + Common::starPatternToLike(mimetype) + QStringLiteral("' ESCAPE '\\'"); 0197 } 0198 0199 QString dateClause(QDate start, QDate end) const { 0200 if (end.isNull()) { 0201 // only date filtering 0202 return QStringLiteral("DATE(re.start, 'unixepoch') = '") + 0203 start.toString(Qt::ISODate) + QStringLiteral("' "); 0204 } else { 0205 // date range filtering 0206 return QStringLiteral("DATE(re.start, 'unixepoch') >= '") + 0207 start.toString(Qt::ISODate) + QStringLiteral("' AND DATE(re.start, 'unixepoch') <= '") + 0208 end.toString(Qt::ISODate) + QStringLiteral("' "); 0209 } 0210 } 0211 0212 QString resourceEventJoinClause() const { 0213 return QStringLiteral(R"sql( 0214 LEFT JOIN 0215 ResourceEvent re 0216 ON from_table.targettedResource = re.targettedResource 0217 AND from_table.usedActivity = re.usedActivity 0218 AND from_table.initiatingAgent = re.initiatingAgent 0219 )sql"); 0220 } 0221 0222 /** 0223 * Transforms the input list's elements with the f member method, 0224 * and returns the resulting list 0225 */ 0226 template <typename F> 0227 inline 0228 QStringList transformedList(const QStringList &input, F f) const 0229 { 0230 using namespace std::placeholders; 0231 0232 QStringList result; 0233 std::transform(input.cbegin(), input.cend(), 0234 std::back_inserter(result), 0235 std::bind(f, this, _1)); 0236 0237 return result; 0238 } 0239 0240 QString limitOffsetSuffix() const 0241 { 0242 QString result; 0243 0244 const int limit = queryDefinition.limit(); 0245 if (limit > 0) { 0246 result += QStringLiteral(" LIMIT ") + QString::number(limit); 0247 0248 const int offset = queryDefinition.offset(); 0249 if (offset > 0) { 0250 result += QStringLiteral(" OFFSET ") + QString::number(offset); 0251 } 0252 } 0253 0254 return result; 0255 } 0256 0257 inline QString replaceQueryParameters(const QString &_query) const 0258 { 0259 // ORDER BY column 0260 auto ordering = queryDefinition.ordering(); 0261 /* clang-format off */ 0262 QString orderingColumn = QStringLiteral("linkStatus DESC, ") + ( 0263 ordering == HighScoredFirst ? QStringLiteral("score DESC,") 0264 : ordering == RecentlyCreatedFirst ? QStringLiteral("firstUpdate DESC,") 0265 : ordering == RecentlyUsedFirst ? QStringLiteral("lastUpdate DESC,") 0266 : ordering == OrderByTitle ? QStringLiteral("title ASC,") 0267 : QString() 0268 ); 0269 /* clang-format on */ 0270 0271 // WHERE clause for filtering on agents 0272 QStringList agentsFilter = transformedList( 0273 queryDefinition.agents(), &ResultSetPrivate::agentClause); 0274 0275 // WHERE clause for filtering on activities 0276 QStringList activitiesFilter = transformedList( 0277 queryDefinition.activities(), &ResultSetPrivate::activityClause); 0278 0279 // WHERE clause for filtering on resource URLs 0280 QStringList urlFilter = transformedList( 0281 queryDefinition.urlFilters(), &ResultSetPrivate::urlFilterClause); 0282 0283 // WHERE clause for filtering on resource mime 0284 QStringList mimetypeFilter = transformedList( 0285 queryDefinition.types(), &ResultSetPrivate::mimetypeClause); 0286 0287 QString dateColumn = QStringLiteral("1"); 0288 QString resourceEventJoin; 0289 // WHERE clause for access date filtering and ResourceEvent table Join 0290 if (!queryDefinition.dateStart().isNull()) { 0291 dateColumn = dateClause(queryDefinition.dateStart(), queryDefinition.dateEnd()); 0292 0293 resourceEventJoin = resourceEventJoinClause(); 0294 } 0295 0296 auto queryString = _query; 0297 0298 queryString.replace(QStringLiteral("ORDER_BY_CLAUSE"), QStringLiteral("ORDER BY $orderingColumn resource ASC")) 0299 .replace(QStringLiteral("LIMIT_CLAUSE"), limitOffsetSuffix()); 0300 0301 return kamd::utils::debug_and_return(DEBUG_QUERIES, "Query: ", 0302 queryString 0303 .replace(QLatin1String("$orderingColumn"), orderingColumn) 0304 .replace(QLatin1String("$agentsFilter"), agentsFilter.join(QStringLiteral(" OR "))) 0305 .replace(QLatin1String("$activitiesFilter"), activitiesFilter.join(QStringLiteral(" OR "))) 0306 .replace(QLatin1String("$urlFilter"), urlFilter.join(QStringLiteral(" OR "))) 0307 .replace(QLatin1String("$mimetypeFilter"), mimetypeFilter.join(QStringLiteral(" OR "))) 0308 .replace(QLatin1String("$resourceEventJoin"), resourceEventJoin) 0309 .replace(QLatin1String("$dateFilter"), dateColumn) 0310 ); 0311 } 0312 0313 static const QString &linkedResourcesQuery() 0314 { 0315 // TODO: We need to correct the scores based on the time that passed 0316 // since the cache was last updated, although, for this query, 0317 // scores are not that important. 0318 /* clang-format off */ 0319 static const QString queryString = 0320 QStringLiteral(R"sql( 0321 SELECT 0322 from_table.targettedResource as resource 0323 , SUM(rsc.cachedScore) as score 0324 , MIN(rsc.firstUpdate) as firstUpdate 0325 , MAX(rsc.lastUpdate) as lastUpdate 0326 , from_table.usedActivity as activity 0327 , from_table.initiatingAgent as agent 0328 , COALESCE(ri.title, from_table.targettedResource) as title 0329 , ri.mimetype as mimetype 0330 , 2 as linkStatus 0331 0332 FROM 0333 ResourceLink from_table 0334 LEFT JOIN 0335 ResourceScoreCache rsc 0336 ON from_table.targettedResource = rsc.targettedResource 0337 AND from_table.usedActivity = rsc.usedActivity 0338 AND from_table.initiatingAgent = rsc.initiatingAgent 0339 LEFT JOIN 0340 ResourceInfo ri 0341 ON from_table.targettedResource = ri.targettedResource 0342 0343 $resourceEventJoin 0344 0345 WHERE 0346 ($agentsFilter) 0347 AND ($activitiesFilter) 0348 AND ($urlFilter) 0349 AND ($mimetypeFilter) 0350 AND ($dateFilter) 0351 0352 GROUP BY resource, title 0353 0354 ORDER_BY_CLAUSE 0355 LIMIT_CLAUSE 0356 )sql") 0357 ; 0358 /* clang-format on */ 0359 0360 return queryString; 0361 } 0362 0363 static const QString &usedResourcesQuery() 0364 { 0365 // TODO: We need to correct the scores based on the time that passed 0366 // since the cache was last updated 0367 static const QString queryString = 0368 QStringLiteral(R"sql( 0369 SELECT 0370 from_table.targettedResource as resource 0371 , SUM(from_table.cachedScore) as score 0372 , MIN(from_table.firstUpdate) as firstUpdate 0373 , MAX(from_table.lastUpdate) as lastUpdate 0374 , from_table.usedActivity as activity 0375 , from_table.initiatingAgent as agent 0376 , COALESCE(ri.title, from_table.targettedResource) as title 0377 , ri.mimetype as mimetype 0378 , 1 as linkStatus 0379 0380 FROM 0381 ResourceScoreCache from_table 0382 LEFT JOIN 0383 ResourceInfo ri 0384 ON from_table.targettedResource = ri.targettedResource 0385 0386 $resourceEventJoin 0387 0388 WHERE 0389 ($agentsFilter) 0390 AND ($activitiesFilter) 0391 AND ($urlFilter) 0392 AND ($mimetypeFilter) 0393 AND ($dateFilter) 0394 0395 GROUP BY resource, title 0396 0397 ORDER_BY_CLAUSE 0398 LIMIT_CLAUSE 0399 )sql") 0400 ; 0401 0402 return queryString; 0403 } 0404 0405 static const QString &allResourcesQuery() 0406 { 0407 // TODO: We need to correct the scores based on the time that passed 0408 // since the cache was last updated, although, for this query, 0409 // scores are not that important. 0410 static const QString queryString = 0411 QStringLiteral(R"sql( 0412 WITH 0413 LinkedResourcesResults AS ( 0414 SELECT from_table.targettedResource as resource 0415 , rsc.cachedScore as score 0416 , rsc.firstUpdate as firstUpdate 0417 , rsc.lastUpdate as lastUpdate 0418 , from_table.usedActivity as activity 0419 , from_table.initiatingAgent as agent 0420 , 2 as linkStatus 0421 0422 FROM 0423 ResourceLink from_table 0424 0425 LEFT JOIN 0426 ResourceScoreCache rsc 0427 ON from_table.targettedResource = rsc.targettedResource 0428 AND from_table.usedActivity = rsc.usedActivity 0429 AND from_table.initiatingAgent = rsc.initiatingAgent 0430 0431 $resourceEventJoin 0432 0433 WHERE 0434 ($agentsFilter) 0435 AND ($activitiesFilter) 0436 AND ($urlFilter) 0437 AND ($mimetypeFilter) 0438 AND ($dateFilter) 0439 ), 0440 0441 UsedResourcesResults AS ( 0442 SELECT from_table.targettedResource as resource 0443 , from_table.cachedScore as score 0444 , from_table.firstUpdate as firstUpdate 0445 , from_table.lastUpdate as lastUpdate 0446 , from_table.usedActivity as activity 0447 , from_table.initiatingAgent as agent 0448 , 0 as linkStatus 0449 0450 FROM 0451 ResourceScoreCache from_table 0452 0453 $resourceEventJoin 0454 0455 WHERE 0456 ($agentsFilter) 0457 AND ($activitiesFilter) 0458 AND ($urlFilter) 0459 AND ($mimetypeFilter) 0460 AND ($dateFilter) 0461 ), 0462 0463 CollectedResults AS ( 0464 SELECT * 0465 FROM LinkedResourcesResults 0466 0467 UNION 0468 0469 SELECT * 0470 FROM UsedResourcesResults 0471 WHERE resource NOT IN (SELECT resource FROM LinkedResourcesResults) 0472 ) 0473 0474 SELECT 0475 resource 0476 , SUM(score) as score 0477 , MIN(firstUpdate) as firstUpdate 0478 , MAX(lastUpdate) as lastUpdate 0479 , activity 0480 , agent 0481 , COALESCE(ri.title, resource) as title 0482 , ri.mimetype as mimetype 0483 , linkStatus 0484 0485 FROM CollectedResults cr 0486 0487 LEFT JOIN 0488 ResourceInfo ri 0489 ON cr.resource = ri.targettedResource 0490 0491 GROUP BY resource, title 0492 0493 ORDER_BY_CLAUSE 0494 LIMIT_CLAUSE 0495 )sql") 0496 ; 0497 0498 return queryString; 0499 } 0500 0501 ResultSet::Result currentResult() const 0502 { 0503 ResultSet::Result result; 0504 0505 if (!database || !query.isActive()) { 0506 return result; 0507 } 0508 0509 result.setResource(query.value(QStringLiteral("resource")).toString()); 0510 result.setTitle(query.value(QStringLiteral("title")).toString()); 0511 result.setMimetype(query.value(QStringLiteral("mimetype")).toString()); 0512 result.setScore(query.value(QStringLiteral("score")).toDouble()); 0513 result.setLastUpdate(query.value(QStringLiteral("lastUpdate")).toUInt()); 0514 result.setFirstUpdate(query.value(QStringLiteral("firstUpdate")).toUInt()); 0515 0516 result.setLinkStatus( 0517 static_cast<ResultSet::Result::LinkStatus>(query.value(QStringLiteral("linkStatus")).toUInt())); 0518 0519 auto linkedActivitiesQuery = database->createQuery(); 0520 0521 linkedActivitiesQuery.prepare(QStringLiteral(R"sql( 0522 SELECT usedActivity 0523 FROM ResourceLink 0524 WHERE targettedResource = :resource 0525 )sql")); 0526 0527 linkedActivitiesQuery.bindValue(QStringLiteral(":resource"), result.resource()); 0528 linkedActivitiesQuery.exec(); 0529 0530 QStringList linkedActivities; 0531 for (const auto &item: linkedActivitiesQuery) { 0532 linkedActivities << item[0].toString(); 0533 } 0534 0535 result.setLinkedActivities(linkedActivities); 0536 // qDebug(KACTIVITIES_STATS_LOG) << result.resource() << "linked to activities" << result.linkedActivities(); 0537 0538 return result; 0539 } 0540 }; 0541 0542 ResultSet::ResultSet(Query queryDefinition) 0543 : d(new ResultSetPrivate()) 0544 { 0545 using namespace Common; 0546 0547 d->database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); 0548 0549 if (!(d->database)) { 0550 qCWarning(KACTIVITIES_STATS_LOG) << "KActivities ERROR: There is no database. This probably means " 0551 "that you do not have the Activity Manager running, or that " 0552 "something else is broken on your system. Recent documents and " 0553 "alike will not work!"; 0554 } 0555 0556 d->queryDefinition = queryDefinition; 0557 0558 d->initQuery(); 0559 } 0560 0561 ResultSet::ResultSet(ResultSet &&source) 0562 : d(nullptr) 0563 { 0564 std::swap(d, source.d); 0565 } 0566 0567 ResultSet::ResultSet(const ResultSet &source) 0568 : d(new ResultSetPrivate(*source.d)) 0569 { 0570 } 0571 0572 ResultSet &ResultSet::operator= (ResultSet source) 0573 { 0574 std::swap(d, source.d); 0575 return *this; 0576 } 0577 0578 ResultSet::~ResultSet() 0579 { 0580 delete d; 0581 } 0582 0583 ResultSet::Result ResultSet::at(int index) const 0584 { 0585 if (!d->query.isActive()) { 0586 return Result(); 0587 } 0588 0589 d->query.seek(index); 0590 0591 return d->currentResult(); 0592 } 0593 0594 } // namespace Stats 0595 } // namespace KActivities 0596 0597 #include "resultset_iterator.cpp" 0598