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"