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