PlasmaActivitiesStats

Database.cpp
1/*
2 SPDX-FileCopyrightText: 2014 Ivan Cukic <ivan.cukic@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5*/
6
7#include "Database.h"
8
9#include <common/database/schema/ResourcesDatabaseSchema.h>
10
11#include <QDebug>
12#include <QSqlDatabase>
13#include <QSqlDriver>
14#include <QSqlError>
15#include <QSqlField>
16#include <QThread>
17
18#include <map>
19#include <mutex>
20
21#include "plasma-activities-stats-logsettings.h"
22
23namespace Common
24{
25namespace
26{
27std::mutex databases_mutex;
28
29struct DatabaseInfo {
30 Qt::HANDLE thread;
31 Database::OpenMode openMode;
32};
33
34bool operator<(const DatabaseInfo &left, const DatabaseInfo &right)
35{
36 return left.thread < right.thread ? true : left.thread > right.thread ? false : left.openMode < right.openMode;
37}
38
39std::map<DatabaseInfo, std::weak_ptr<Database>> databases;
40}
41
42class QSqlDatabaseWrapper
43{
44private:
45 QSqlDatabase m_database;
46 bool m_open;
47 QString m_connectionName;
48
49public:
50 QSqlDatabaseWrapper(const DatabaseInfo &info)
51 : m_open(false)
52 {
53 m_connectionName = QStringLiteral("kactivities_db_resources_")
54 // Adding the thread number to the database name
55 + QString::number((quintptr)info.thread)
56 // And whether it is read-only or read-write
57 + (info.openMode == Database::ReadOnly ? QStringLiteral("_readonly") : QStringLiteral("_readwrite"));
58
59 m_database = QSqlDatabase::contains(m_connectionName) ? QSqlDatabase::database(m_connectionName)
60 : QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
61
62 if (info.openMode == Database::ReadOnly) {
63 m_database.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY"));
64 }
65
66 // We are allowing the database file to be overridden mostly for testing purposes
67 m_database.setDatabaseName(ResourcesDatabaseSchema::path());
68
69 m_open = m_database.open();
70
71 if (!m_open) {
72 qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName()
73 << m_database.lastError();
74
75 if (info.openMode == Database::ReadWrite) {
76 qFatal("PlasmaActivities: Opening the database in RW mode should always succeed");
77 }
78 }
79 }
80
81 ~QSqlDatabaseWrapper()
82 {
83 qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Closing SQL connection: " << m_connectionName;
84 }
85
86 QSqlDatabase &get()
87 {
88 return m_database;
89 }
90
91 bool isOpen() const
92 {
93 return m_open;
94 }
95
96 QString connectionName() const
97 {
98 return m_connectionName;
99 }
100};
101
102class Database::Private
103{
104public:
105 Private()
106 {
107 }
108
109 QSqlQuery query(const QString &query)
110 {
111 return database ? QSqlQuery(query, database->get()) : QSqlQuery();
112 }
113
114 QSqlQuery query()
115 {
116 return database ? QSqlQuery(database->get()) : QSqlQuery();
117 }
118
119 std::unique_ptr<QSqlDatabaseWrapper> database;
120};
121
122Database::Locker::Locker(Database &database)
123 : m_database(database.d->database->get())
124{
125 m_database.transaction();
126}
127
128Database::Locker::~Locker()
129{
130 m_database.commit();
131}
132
133Database::Ptr Database::instance(Source source, OpenMode openMode)
134{
135 Q_UNUSED(source) // for the time being
136
137 std::lock_guard<std::mutex> lock(databases_mutex);
138
139 // We are saving instances per thread and per read/write mode
140 DatabaseInfo info;
141 info.thread = QThread::currentThreadId();
142 info.openMode = openMode;
143
144 // Do we have an instance matching the request?
145 auto search = databases.find(info);
146 if (search != databases.end()) {
147 auto ptr = search->second.lock();
148
149 if (ptr) {
150 return ptr;
151 }
152 }
153
154 // Creating a new database instance
155 auto ptr = std::make_shared<Database>();
156
157 ptr->d->database = std::make_unique<QSqlDatabaseWrapper>(info);
158
159 if (!ptr->d->database->isOpen()) {
160 return nullptr;
161 }
162
163 databases[info] = ptr;
164
165 if (info.openMode == ReadOnly) {
166 // From now on, only SELECT queries will work
167 ptr->setPragma(QStringLiteral("query_only = 1"));
168
169 // These should not make any difference
170 ptr->setPragma(QStringLiteral("synchronous = 0"));
171
172 } else {
173 // Using the write-ahead log and sync = NORMAL for faster writes
174 ptr->setPragma(QStringLiteral("synchronous = 1"));
175 }
176
177 // Maybe we should use the write-ahead log
178 auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL"));
179
180 if (walResult != QLatin1String("wal")) {
181 qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database can not be opened in WAL mode. Check the "
182 "SQLite version (required >3.7.0). And whether your filesystem "
183 "supports shared memory";
184
185 return nullptr;
186 }
187
188 // We don't have a big database, lets flush the WAL when
189 // it reaches 400k, not 4M as is default
190 ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100"));
191
192 qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database connection: " << ptr->d->database->connectionName()
193 << "\n query_only: " << ptr->pragma(QStringLiteral("query_only"))
194 << "\n journal_mode: " << ptr->pragma(QStringLiteral("journal_mode"))
195 << "\n wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint"))
196 << "\n synchronous: " << ptr->pragma(QStringLiteral("synchronous"));
197
198 return ptr;
199}
200
201Database::Database()
202 : d(new Database::Private())
203{
204}
205
206Database::~Database()
207{
208}
209
210QSqlQuery Database::createQuery() const
211{
212 return d->query();
213}
214
215QSqlQuery Database::execQuery(const QString &query) const
216{
217 return d->query(query);
218}
219
220QSqlQuery Database::execQueries(const QStringList &queries) const
221{
222 QSqlQuery result;
223
224 for (const auto &query : queries) {
225 result = execQuery(query);
226 }
227
228 return result;
229}
230
231void Database::setPragma(const QString &pragma)
232{
233 execQuery(QStringLiteral("PRAGMA ") + pragma);
234}
235
236QVariant Database::pragma(const QString &pragma) const
237{
238 return value(QStringLiteral("PRAGMA ") + pragma);
239}
240
241QVariant Database::value(const QString &query) const
242{
243 auto result = execQuery(query);
244 return result.next() ? result.value(0) : QVariant();
245}
246
247} // namespace Common
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
bool operator<(const PosRange< Trait > &l, const PosRange< Trait > &r)
bool contains(const QString &connectionName)
QSqlDatabase database(const QString &connectionName, bool open)
bool transaction()
bool next()
QVariant value(const QString &name) const const
QString number(double n, char format, int precision)
typedef HANDLE
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
Qt::HANDLE currentThreadId()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 12:01:02 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.