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