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