File indexing completed on 2024-11-17 05:00:55
0001 /* 0002 SPDX-FileCopyrightText: 2014 Ivan Cukic <ivan.cukic@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "Database.h" 0008 0009 #include <common/database/schema/ResourcesDatabaseSchema.h> 0010 0011 #include <QDebug> 0012 #include <QSqlDatabase> 0013 #include <QSqlDriver> 0014 #include <QSqlError> 0015 #include <QSqlField> 0016 #include <QThread> 0017 0018 #include <map> 0019 #include <mutex> 0020 0021 #include "plasma-activities-stats-logsettings.h" 0022 0023 namespace Common 0024 { 0025 namespace 0026 { 0027 std::mutex databases_mutex; 0028 0029 struct DatabaseInfo { 0030 Qt::HANDLE thread; 0031 Database::OpenMode openMode; 0032 }; 0033 0034 bool operator<(const DatabaseInfo &left, const DatabaseInfo &right) 0035 { 0036 return left.thread < right.thread ? true : left.thread > right.thread ? false : left.openMode < right.openMode; 0037 } 0038 0039 std::map<DatabaseInfo, std::weak_ptr<Database>> databases; 0040 } 0041 0042 class QSqlDatabaseWrapper 0043 { 0044 private: 0045 QSqlDatabase m_database; 0046 bool m_open; 0047 QString m_connectionName; 0048 0049 public: 0050 QSqlDatabaseWrapper(const DatabaseInfo &info) 0051 : m_open(false) 0052 { 0053 m_connectionName = QStringLiteral("kactivities_db_resources_") 0054 // Adding the thread number to the database name 0055 + QString::number((quintptr)info.thread) 0056 // And whether it is read-only or read-write 0057 + (info.openMode == Database::ReadOnly ? QStringLiteral("_readonly") : QStringLiteral("_readwrite")); 0058 0059 m_database = QSqlDatabase::contains(m_connectionName) ? QSqlDatabase::database(m_connectionName) 0060 : QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName); 0061 0062 if (info.openMode == Database::ReadOnly) { 0063 m_database.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY")); 0064 } 0065 0066 // We are allowing the database file to be overridden mostly for testing purposes 0067 m_database.setDatabaseName(ResourcesDatabaseSchema::path()); 0068 0069 m_open = m_database.open(); 0070 0071 if (!m_open) { 0072 qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName() 0073 << m_database.lastError(); 0074 0075 if (info.openMode == Database::ReadWrite) { 0076 qFatal("PlasmaActivities: Opening the database in RW mode should always succeed"); 0077 } 0078 } 0079 } 0080 0081 ~QSqlDatabaseWrapper() 0082 { 0083 qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Closing SQL connection: " << m_connectionName; 0084 } 0085 0086 QSqlDatabase &get() 0087 { 0088 return m_database; 0089 } 0090 0091 bool isOpen() const 0092 { 0093 return m_open; 0094 } 0095 0096 QString connectionName() const 0097 { 0098 return m_connectionName; 0099 } 0100 }; 0101 0102 class Database::Private 0103 { 0104 public: 0105 Private() 0106 { 0107 } 0108 0109 QSqlQuery query(const QString &query) 0110 { 0111 return database ? QSqlQuery(query, database->get()) : QSqlQuery(); 0112 } 0113 0114 QSqlQuery query() 0115 { 0116 return database ? QSqlQuery(database->get()) : QSqlQuery(); 0117 } 0118 0119 std::unique_ptr<QSqlDatabaseWrapper> database; 0120 }; 0121 0122 Database::Locker::Locker(Database &database) 0123 : m_database(database.d->database->get()) 0124 { 0125 m_database.transaction(); 0126 } 0127 0128 Database::Locker::~Locker() 0129 { 0130 m_database.commit(); 0131 } 0132 0133 Database::Ptr Database::instance(Source source, OpenMode openMode) 0134 { 0135 Q_UNUSED(source) // for the time being 0136 0137 std::lock_guard<std::mutex> lock(databases_mutex); 0138 0139 // We are saving instances per thread and per read/write mode 0140 DatabaseInfo info; 0141 info.thread = QThread::currentThreadId(); 0142 info.openMode = openMode; 0143 0144 // Do we have an instance matching the request? 0145 auto search = databases.find(info); 0146 if (search != databases.end()) { 0147 auto ptr = search->second.lock(); 0148 0149 if (ptr) { 0150 return ptr; 0151 } 0152 } 0153 0154 // Creating a new database instance 0155 auto ptr = std::make_shared<Database>(); 0156 0157 ptr->d->database = std::make_unique<QSqlDatabaseWrapper>(info); 0158 0159 if (!ptr->d->database->isOpen()) { 0160 return nullptr; 0161 } 0162 0163 databases[info] = ptr; 0164 0165 if (info.openMode == ReadOnly) { 0166 // From now on, only SELECT queries will work 0167 ptr->setPragma(QStringLiteral("query_only = 1")); 0168 0169 // These should not make any difference 0170 ptr->setPragma(QStringLiteral("synchronous = 0")); 0171 0172 } else { 0173 // Using the write-ahead log and sync = NORMAL for faster writes 0174 ptr->setPragma(QStringLiteral("synchronous = 1")); 0175 } 0176 0177 // Maybe we should use the write-ahead log 0178 auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL")); 0179 0180 if (walResult != QLatin1String("wal")) { 0181 qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database can not be opened in WAL mode. Check the " 0182 "SQLite version (required >3.7.0). And whether your filesystem " 0183 "supports shared memory"; 0184 0185 return nullptr; 0186 } 0187 0188 // We don't have a big database, lets flush the WAL when 0189 // it reaches 400k, not 4M as is default 0190 ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100")); 0191 0192 qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database connection: " << ptr->d->database->connectionName() 0193 << "\n query_only: " << ptr->pragma(QStringLiteral("query_only")) 0194 << "\n journal_mode: " << ptr->pragma(QStringLiteral("journal_mode")) 0195 << "\n wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint")) 0196 << "\n synchronous: " << ptr->pragma(QStringLiteral("synchronous")); 0197 0198 return ptr; 0199 } 0200 0201 Database::Database() 0202 : d(new Database::Private()) 0203 { 0204 } 0205 0206 Database::~Database() 0207 { 0208 } 0209 0210 QSqlQuery Database::createQuery() const 0211 { 0212 return d->query(); 0213 } 0214 0215 QSqlQuery Database::execQuery(const QString &query) const 0216 { 0217 return d->query(query); 0218 } 0219 0220 QSqlQuery Database::execQueries(const QStringList &queries) const 0221 { 0222 QSqlQuery result; 0223 0224 for (const auto &query : queries) { 0225 result = execQuery(query); 0226 } 0227 0228 return result; 0229 } 0230 0231 void Database::setPragma(const QString &pragma) 0232 { 0233 execQuery(QStringLiteral("PRAGMA ") + pragma); 0234 } 0235 0236 QVariant Database::pragma(const QString &pragma) const 0237 { 0238 return value(QStringLiteral("PRAGMA ") + pragma); 0239 } 0240 0241 QVariant Database::value(const QString &query) const 0242 { 0243 auto result = execQuery(query); 0244 return result.next() ? result.value(0) : QVariant(); 0245 } 0246 0247 } // namespace Common