1 /*
2 * SPDX-FileCopyrightText: 2014-2016 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 #include <utils/d_ptr_implementation.h>
11
12 #include <QDebug>
13 #include <QSqlDatabase>
14 #include <QSqlDriver>
15 #include <QSqlError>
16 #include <QSqlField>
17 #include <QThread>
18
19 #include <map>
20 #include <mutex>
21
22 #include "DebugResources.h"
23
24 namespace Common
25 {
26 namespace
27 {
28 #ifdef QT_DEBUG
29 QString lastExecutedQuery;
30 #endif
31
32 std::mutex databases_mutex;
33
34 struct DatabaseInfo {
35 Qt::HANDLE thread;
36 Database::OpenMode openMode;
37 };
38
operator <(const DatabaseInfo & left,const DatabaseInfo & right)39 bool operator<(const DatabaseInfo &left, const DatabaseInfo &right)
40 {
41 return left.thread < right.thread ? true : left.thread > right.thread ? false : left.openMode < right.openMode;
42 }
43
44 std::map<DatabaseInfo, std::weak_ptr<Database>> databases;
45 }
46
47 class QSqlDatabaseWrapper
48 {
49 private:
50 QSqlDatabase m_database;
51 bool m_open;
52 QString m_connectionName;
53
54 public:
QSqlDatabaseWrapper(const DatabaseInfo & info)55 QSqlDatabaseWrapper(const DatabaseInfo &info)
56 : m_open(false)
57 {
58 m_connectionName = "kactivities_db_resources_"
59 // Adding the thread number to the database name
60 + QString::number((quintptr)info.thread)
61 // And whether it is read-only or read-write
62 + (info.openMode == Database::ReadOnly ? "_readonly" : "_readwrite");
63
64 m_database = QSqlDatabase::contains(m_connectionName) ? QSqlDatabase::database(m_connectionName)
65 : QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
66
67 if (info.openMode == Database::ReadOnly) {
68 m_database.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY"));
69 }
70
71 // We are allowing the database file to be overridden mostly for testing purposes
72 m_database.setDatabaseName(ResourcesDatabaseSchema::path());
73
74 m_open = m_database.open();
75
76 if (!m_open) {
77 qCWarning(KAMD_LOG_RESOURCES) << "KActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName()
78 << m_database.lastError();
79
80 if (info.openMode == Database::ReadWrite) {
81 qFatal("KActivities: Opening the database in RW mode should always succeed");
82 }
83 }
84 }
85
~QSqlDatabaseWrapper()86 ~QSqlDatabaseWrapper()
87 {
88 qCDebug(KAMD_LOG_RESOURCES) << "Closing SQL connection: " << m_connectionName;
89 }
90
close()91 void close()
92 {
93 m_database.close();
94 }
95
get()96 QSqlDatabase &get()
97 {
98 return m_database;
99 }
100
isOpen() const101 bool isOpen() const
102 {
103 return m_open;
104 }
105
connectionName() const106 QString connectionName() const
107 {
108 return m_connectionName;
109 }
110 };
111
112 class Database::Private
113 {
114 public:
Private()115 Private()
116 {
117 }
118
query(const QString & query)119 QSqlQuery query(const QString &query)
120 {
121 return database ? QSqlQuery(query, database->get()) : QSqlQuery();
122 }
123
query()124 QSqlQuery query()
125 {
126 return database ? QSqlQuery(database->get()) : QSqlQuery();
127 }
128
129 QScopedPointer<QSqlDatabaseWrapper> database;
130 };
131
Locker(Database & database)132 Database::Locker::Locker(Database &database)
133 : m_database(database.d->database->get())
134 {
135 m_database.transaction();
136 }
137
~Locker()138 Database::Locker::~Locker()
139 {
140 m_database.commit();
141 }
142
instance(Source source,OpenMode openMode)143 Database::Ptr Database::instance(Source source, OpenMode openMode)
144 {
145 Q_UNUSED(source) // for the time being
146
147 std::lock_guard<std::mutex> lock(databases_mutex);
148
149 // We are saving instances per thread and per read/write mode
150 DatabaseInfo info;
151 info.thread = QThread::currentThreadId();
152 info.openMode = openMode;
153
154 // Do we have an instance matching the request?
155 auto search = databases.find(info);
156 if (search != databases.end()) {
157 auto ptr = search->second.lock();
158
159 if (ptr) {
160 return ptr;
161 }
162 }
163
164 // Creating a new database instance
165 auto ptr = std::make_shared<Database>();
166
167 ptr->d->database.reset(new QSqlDatabaseWrapper(info));
168
169 if (!ptr->d->database->isOpen()) {
170 return nullptr;
171 }
172
173 databases[info] = ptr;
174
175 if (info.openMode == ReadOnly) {
176 // From now on, only SELECT queries will work
177 ptr->setPragma(QStringLiteral("query_only = 1"));
178
179 // These should not make any difference
180 ptr->setPragma(QStringLiteral("synchronous = 0"));
181
182 } else {
183 // Using the write-ahead log and sync = NORMAL for faster writes
184 ptr->setPragma(QStringLiteral("synchronous = 1"));
185 }
186
187 // Maybe we should use the write-ahead log
188 auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL"));
189
190 if (walResult != "wal") {
191 qCWarning(KAMD_LOG_RESOURCES) << "KActivities: Database can not be opened in WAL mode. Check the "
192 "SQLite version (required >3.7.0). And whether your filesystem "
193 "supports shared memory";
194
195 ptr->d->database->close();
196
197 return nullptr;
198 }
199
200 // We don't have a big database, lets flush the WAL when
201 // it reaches 400k, not 4M as is default
202 ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100"));
203
204 qCDebug(KAMD_LOG_RESOURCES) << "KActivities: Database connection: " << ptr->d->database->connectionName()
205 << "\n query_only: " << ptr->pragma(QStringLiteral("query_only"))
206 << "\n journal_mode: " << ptr->pragma(QStringLiteral("journal_mode"))
207 << "\n wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint"))
208 << "\n synchronous: " << ptr->pragma(QStringLiteral("synchronous"));
209
210 return ptr;
211 }
212
Database()213 Database::Database()
214 {
215 }
216
~Database()217 Database::~Database()
218 {
219 }
220
createQuery() const221 QSqlQuery Database::createQuery() const
222 {
223 return d->query();
224 }
225
reportError(const QSqlError & error_)226 void Database::reportError(const QSqlError &error_)
227 {
228 Q_EMIT error(error_);
229 }
230
lastQuery() const231 QString Database::lastQuery() const
232 {
233 #ifdef QT_DEBUG
234 return lastExecutedQuery;
235 #endif
236 return QString();
237 }
238
execQuery(const QString & query,bool ignoreErrors) const239 QSqlQuery Database::execQuery(const QString &query, bool ignoreErrors) const
240 {
241 Q_UNUSED(ignoreErrors);
242 #ifdef QT_NO_DEBUG
243 auto result = d->query(query);
244
245 if (!ignoreErrors && result.lastError().isValid()) {
246 Q_EMIT error(result.lastError());
247 }
248
249 return result;
250 #else
251 auto result = d->query(query);
252
253 lastExecutedQuery = query;
254
255 if (!ignoreErrors && result.lastError().isValid()) {
256 qCWarning(KAMD_LOG_RESOURCES) << "SQL: "
257 << "\n error: " << result.lastError() << "\n query: " << query;
258 }
259
260 return result;
261 #endif
262 }
263
execQueries(const QStringList & queries) const264 QSqlQuery Database::execQueries(const QStringList &queries) const
265 {
266 QSqlQuery result;
267
268 for (const auto &query : queries) {
269 result = execQuery(query);
270 }
271
272 return result;
273 }
274
setPragma(const QString & pragma)275 void Database::setPragma(const QString &pragma)
276 {
277 execQuery(QStringLiteral("PRAGMA ") + pragma);
278 }
279
pragma(const QString & pragma) const280 QVariant Database::pragma(const QString &pragma) const
281 {
282 return value("PRAGMA " + pragma);
283 }
284
value(const QString & query) const285 QVariant Database::value(const QString &query) const
286 {
287 auto result = execQuery(query);
288 return result.next() ? result.value(0) : QVariant();
289 }
290
291 } // namespace Common
292