File: Database.cpp

package info (click to toggle)
kio-extras 4%3A25.04.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 31,928 kB
  • sloc: cpp: 28,852; ansic: 3,084; perl: 1,048; xml: 116; sh: 92; python: 28; makefile: 9
file content (269 lines) | stat: -rw-r--r-- 6,661 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/*
 *   SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic@kde.org>
 *
 *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
 */

#include "Database.h"

#include <common/database/schema/ResourcesDatabaseSchema.h>
#include <utils/d_ptr_implementation.h>

#include <QDebug>
#include <QSqlDatabase>
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlField>
#include <QThread>

#include <map>
#include <mutex>

namespace Common
{

namespace
{
#ifdef QT_DEBUG
QString lastExecutedQuery;
#endif

std::mutex databases_mutex;

struct DatabaseInfo {
    Qt::HANDLE thread;
    Database::OpenMode openMode;
};

bool operator<(const DatabaseInfo &left, const DatabaseInfo &right)
{
    return left.thread < right.thread ? true : left.thread > right.thread ? false : left.openMode < right.openMode;
}

std::map<DatabaseInfo, std::weak_ptr<Database>> databases;
};

class QSqlDatabaseWrapper
{
private:
    QSqlDatabase m_database;
    bool m_open;
    QString m_connectionName;

public:
    QSqlDatabaseWrapper(const DatabaseInfo &info)
        : m_open(false)
    {
        m_connectionName = "kactivities_db_resources_"
            // Adding the thread number to the database name
            + QString::number((quintptr)info.thread)
            // And whether it is read-only or read-write
            + (info.openMode == Database::ReadOnly ? "_readonly" : "_readwrite");

        m_database = QSqlDatabase::contains(m_connectionName) ? QSqlDatabase::database(m_connectionName)
                                                              : QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);

        if (info.openMode == Database::ReadOnly) {
            m_database.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY"));
        }

        // We are allowing the database file to be overridden mostly for testing purposes
        m_database.setDatabaseName(ResourcesDatabaseSchema::path());

        m_open = m_database.open();

        if (!m_open) {
            qWarning() << "KActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName() << m_database.lastError();

            if (info.openMode == Database::ReadWrite) {
                qFatal("KActivities: Opening the database in RW mode should always succeed");
            }
        }
    }

    ~QSqlDatabaseWrapper()
    {
        qDebug() << "Closing SQL connection: " << m_connectionName;
    }

    QSqlDatabase &get()
    {
        return m_database;
    }

    bool isOpen() const
    {
        return m_open;
    }

    QString connectionName() const
    {
        return m_connectionName;
    }
};

class Database::Private
{
public:
    Private()
    {
    }

    QSqlQuery query(const QString &query)
    {
        return database ? QSqlQuery(query, database->get()) : QSqlQuery();
    }

    QSqlQuery query()
    {
        return database ? QSqlQuery(database->get()) : QSqlQuery();
    }

    QScopedPointer<QSqlDatabaseWrapper> database;
};

Database::Locker::Locker(Database &database)
    : m_database(database.d->database->get())
{
    m_database.transaction();
}

Database::Locker::~Locker()
{
    m_database.commit();
}

Database::Ptr Database::instance(Source source, OpenMode openMode)
{
    Q_UNUSED(source) // for the time being

    std::lock_guard<std::mutex> lock(databases_mutex);

    // We are saving instances per thread and per read/write mode
    DatabaseInfo info;
    info.thread = QThread::currentThreadId();
    info.openMode = openMode;

    // Do we have an instance matching the request?
    auto search = databases.find(info);
    if (search != databases.end()) {
        auto ptr = search->second.lock();

        if (ptr) {
            return ptr;
        }
    }

    // Creating a new database instance
    auto ptr = std::make_shared<Database>();

    ptr->d->database.reset(new QSqlDatabaseWrapper(info));

    if (!ptr->d->database->isOpen()) {
        return nullptr;
    }

    databases[info] = ptr;

    if (info.openMode == ReadOnly) {
        // From now on, only SELECT queries will work
        ptr->setPragma(QStringLiteral("query_only = 1"));

        // These should not make any difference
        ptr->setPragma(QStringLiteral("synchronous = 0"));

    } else {
        // Using the write-ahead log and sync = NORMAL for faster writes
        ptr->setPragma(QStringLiteral("synchronous = 1"));
    }

    // Maybe we should use the write-ahead log
    auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL"));

    if (walResult != "wal") {
        qFatal(
            "KActivities: Database can not be opened in WAL mode. Check the "
            "SQLite version (required >3.7.0). And whether your filesystem "
            "supports shared memory");
    }

    // We don't have a big database, lets flush the WAL when
    // it reaches 400k, not 4M as is default
    ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100"));

    qDebug() << "KActivities: Database connection: " << ptr->d->database->connectionName()
             << "\n    query_only:         " << ptr->pragma(QStringLiteral("query_only"))
             << "\n    journal_mode:       " << ptr->pragma(QStringLiteral("journal_mode"))
             << "\n    wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint"))
             << "\n    synchronous:        " << ptr->pragma(QStringLiteral("synchronous"));

    return ptr;
}

Database::Database()
{
}

Database::~Database()
{
}

QSqlQuery Database::createQuery() const
{
    return d->query();
}

QString Database::lastQuery() const
{
#ifdef QT_DEBUG
    return lastExecutedQuery;
#endif
    return QString();
}

QSqlQuery Database::execQuery(const QString &query, bool ignoreErrors) const
{
#ifdef QT_NO_DEBUG
    return d->query(query);
#else
    auto result = d->query(query);

    lastExecutedQuery = query;

    if (!ignoreErrors && result.lastError().isValid()) {
        qWarning() << "SQL: "
                   << "\n    error: " << result.lastError() << "\n    query: " << query;
    }

    return result;
#endif
}

QSqlQuery Database::execQueries(const QStringList &queries) const
{
    QSqlQuery result;

    for (const auto &query : queries) {
        result = execQuery(query);
    }

    return result;
}

void Database::setPragma(const QString &pragma)
{
    execQuery(QStringLiteral("PRAGMA ") + pragma);
}

QVariant Database::pragma(const QString &pragma) const
{
    return value("PRAGMA " + pragma);
}

QVariant Database::value(const QString &query) const
{
    auto result = execQuery(query);
    return result.next() ? result.value(0) : QVariant();
}

} // namespace Common