File indexing completed on 2025-01-05 04:47:01

0001 /*
0002     SPDX-FileCopyrightText: 2012 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "querycache.h"
0008 #include "datastore.h"
0009 #include "dbtype.h"
0010 
0011 #include <QHash>
0012 #include <QSqlDriver>
0013 #include <QSqlQuery>
0014 #include <QThreadStorage>
0015 #include <QTimer>
0016 
0017 #include <chrono>
0018 #include <list>
0019 
0020 using namespace std::chrono_literals;
0021 using namespace Akonadi;
0022 using namespace Akonadi::Server;
0023 
0024 namespace
0025 {
0026 // After these seconds without activity the cache is cleaned
0027 constexpr auto CleanupTimeout = 60s;
0028 constexpr int MaxCacheSize = 50;
0029 
0030 /// LRU cache with limited size and auto-cleanup after given
0031 /// period of time
0032 class Cache
0033 {
0034 public:
0035     Cache()
0036     {
0037         QObject::connect(&m_cleanupTimer, &QTimer::timeout, &m_cleanupTimer, std::bind(&Cache::cleanup, this));
0038         m_cleanupTimer.setSingleShot(true);
0039     }
0040 
0041     std::optional<QSqlQuery> query(const QString &queryStatement)
0042     {
0043         m_cleanupTimer.start(CleanupTimeout);
0044         auto it = m_keys.find(queryStatement);
0045         if (it == m_keys.end()) {
0046             return std::nullopt;
0047         }
0048 
0049         auto node = **it;
0050         m_queries.erase(*it);
0051         m_queries.push_front(node);
0052         *it = m_queries.begin();
0053         return node.query;
0054     }
0055 
0056     void insert(const QString &queryStatement, const QSqlQuery &query)
0057     {
0058         if (m_queries.size() >= MaxCacheSize) {
0059             m_keys.remove(m_queries.back().queryStatement);
0060             m_queries.pop_back();
0061         }
0062 
0063         m_queries.emplace_front(Node{queryStatement, query});
0064         m_keys.insert(queryStatement, m_queries.begin());
0065     }
0066 
0067     void cleanup()
0068     {
0069         m_keys.clear();
0070         m_queries.clear();
0071     }
0072 
0073 public: // public, this is just a helper class
0074     struct Node {
0075         QString queryStatement;
0076         QSqlQuery query;
0077     };
0078     std::list<Node> m_queries;
0079     QHash<QString, std::list<Node>::iterator> m_keys;
0080     QTimer m_cleanupTimer;
0081 };
0082 
0083 QThreadStorage<Cache *> g_queryCache;
0084 
0085 Cache *perThreadCache()
0086 {
0087     if (!g_queryCache.hasLocalData()) {
0088         g_queryCache.setLocalData(new Cache());
0089     }
0090 
0091     return g_queryCache.localData();
0092 }
0093 
0094 } // namespace
0095 
0096 std::optional<QSqlQuery> QueryCache::query(const QString &queryStatement)
0097 {
0098     return perThreadCache()->query(queryStatement);
0099 }
0100 
0101 void QueryCache::insert(const QSqlDatabase &db, const QString &queryStatement, const QSqlQuery &query)
0102 {
0103     if (DbType::type(db) != DbType::Sqlite) {
0104         perThreadCache()->insert(queryStatement, query);
0105     }
0106 }
0107 
0108 void QueryCache::clear()
0109 {
0110     if (!g_queryCache.hasLocalData()) {
0111         return;
0112     }
0113 
0114     g_queryCache.localData()->cleanup();
0115 }