File indexing completed on 2024-05-12 04:59:34

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