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