Verified Commit bd7843bf authored by Linus Jahn's avatar Linus Jahn 🔌
Browse files

Introduce DatabaseComponent

Makes it easier to use the database and features run function to do
something on the database thread pool.

Currently there are two ways of handling database queries:
 1. Directly on the database thread (where all *Db classes are living)
 2. Using DatabaseComponent::run() (QThreadPool) with QFutures
The second one with its QFutures is the future.
parent 6c8dda1a
Pipeline #68684 passed with stage
in 2 minutes and 48 seconds
......@@ -10,6 +10,7 @@ set(KAIDAN_SOURCES
src/ClientWorker.cpp
src/AvatarFileStorage.cpp
src/Database.cpp
src/DatabaseComponent.cpp
src/RosterItem.cpp
src/RosterModel.cpp
src/RosterFilterProxyModel.cpp
......
......@@ -42,6 +42,7 @@
#include <QSqlRecord>
#include <QStandardPaths>
#include <QThreadStorage>
#include <QThreadPool>
#include "Kaidan.h"
......@@ -122,6 +123,7 @@ enum DatabaseVersion {
struct DatabasePrivate
{
QThreadPool pool;
QMutex tableCreationMutex;
int version = DbNotLoaded;
int transactions = 0;
......@@ -132,6 +134,8 @@ Database::Database(QObject *parent)
: QObject(parent),
d(new DatabasePrivate)
{
d->pool.setMaxThreadCount(1);
d->pool.setExpiryTimeout(-1);
connect(this, &Database::transactionRequested, this, &Database::transaction);
connect(this, &Database::commitRequested, this, &Database::commit);
}
......@@ -261,6 +265,11 @@ int &Database::activeTransactions()
return activeTransactions;
}
QThreadPool &Database::threadPool()
{
return d->pool;
}
bool Database::needToConvert()
{
return d->version < DATABASE_LATEST_VERSION;
......
......@@ -35,6 +35,7 @@
class QSqlQuery;
class QSqlDatabase;
class QThreadPool;
struct DatabasePrivate;
/**
......@@ -44,6 +45,7 @@ struct DatabasePrivate;
class Database : public QObject
{
Q_OBJECT
friend class DatabaseComponent;
public:
Database(QObject *parent = nullptr);
......@@ -55,19 +57,6 @@ public:
*/
void createTables();
/**
* Begins a transaction if none has been started.
*/
void transaction();
/**
* Commits the transaction if every transaction has been finished.
*/
void commit();
QSqlDatabase currentDatabase();
QSqlQuery createQuery();
signals:
/// Emit, to begin a transaction if none has been started already.
void transactionRequested();
......@@ -76,8 +65,17 @@ signals:
void commitRequested();
private:
QSqlDatabase currentDatabase();
QSqlQuery createQuery();
/// Returns the number of active transactions on the current thread.
int &activeTransactions();
/// Begins a transaction if none has been started.
void transaction();
/// Commits the transaction if every transaction has been finished.
void commit();
QThreadPool &threadPool();
/**
* @return true if the database has to be converted using @c convertDatabase()
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2021 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DatabaseComponent.h"
#include <QSqlDatabase>
#include <QSqlDriver>
#include <QSqlQuery>
#include <QSqlRecord>
#include "Database.h"
DatabaseComponent::DatabaseComponent(Database *database, QObject *parent)
: QObject(parent),
m_database(database)
{
}
QSqlQuery DatabaseComponent::createQuery()
{
return m_database->createQuery();
}
QSqlDriver &DatabaseComponent::sqlDriver()
{
return *m_database->currentDatabase().driver();
}
QSqlRecord DatabaseComponent::sqlRecord(const QString &tableName)
{
return m_database->currentDatabase().record(tableName);
}
void DatabaseComponent::transaction()
{
m_database->transaction();
}
void DatabaseComponent::commit()
{
m_database->commit();
}
QThreadPool &DatabaseComponent::threadPool() const
{
return m_database->threadPool();
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2021 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QtConcurrent/QtConcurrentRun>
class QThreadPool;
class QSqlQuery;
class QSqlDriver;
class QSqlRecord;
class Database;
class DatabaseComponent : public QObject
{
Q_OBJECT
public:
DatabaseComponent(Database *database, QObject *parent = nullptr);
QSqlQuery createQuery();
QSqlDriver &sqlDriver();
QSqlRecord sqlRecord(const QString &tableName);
void transaction();
void commit();
template<typename Functor>
void run(Functor function)
{
QtConcurrent::run(threadPool(), function);
}
private:
QThreadPool &threadPool() const;
Database *m_database;
};
......@@ -47,8 +47,7 @@
MessageDb *MessageDb::s_instance = nullptr;
MessageDb::MessageDb(Database *db, QObject *parent)
: QObject(parent),
m_db(db)
: DatabaseComponent(db, parent)
{
Q_ASSERT(!MessageDb::s_instance);
s_instance = this;
......@@ -192,7 +191,7 @@ QSqlRecord MessageDb::createUpdateRecord(const Message &oldMsg, const Message &n
void MessageDb::fetchMessages(const QString &user1, const QString &user2, int index)
{
auto query = m_db->createQuery();
auto query = createQuery();
QMap<QString, QVariant> bindValues;
bindValues[":user1"] = user1;
......@@ -218,7 +217,7 @@ void MessageDb::fetchMessages(const QString &user1, const QString &user2, int in
Message MessageDb::fetchLastMessage(const QString &user1, const QString &user2)
{
auto query = m_db->createQuery();
auto query = createQuery();
QMap<QString, QVariant> bindValues = {
{ QStringLiteral(":user1"), user1 },
......@@ -245,7 +244,7 @@ Message MessageDb::fetchLastMessage(const QString &user1, const QString &user2)
void MessageDb::fetchLastMessageStamp()
{
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(query, "SELECT timestamp FROM Messages ORDER BY timestamp DESC LIMIT 1");
QDateTime stamp;
......@@ -280,9 +279,7 @@ void MessageDb::addMessage(const Message &msg, MessageOrigin origin)
// to speed up the whole process emit signal first and do the actual insert after that
emit messageAdded(msg, origin);
auto db = m_db->currentDatabase();
QSqlRecord record = db.record(DB_TABLE_MESSAGES);
QSqlRecord record = sqlRecord(DB_TABLE_MESSAGES);
record.setValue("author", msg.from());
record.setValue("recipient", msg.to());
record.setValue("timestamp", msg.stamp().toString(Qt::ISODate));
......@@ -303,8 +300,8 @@ void MessageDb::addMessage(const Message &msg, MessageOrigin origin)
record.setValue("originId", msg.originId());
record.setValue("stanzaId", msg.stanzaId());
auto query = m_db->createQuery();
Utils::execQuery(query, db.driver()->sqlStatement(
auto query = createQuery();
Utils::execQuery(query, sqlDriver().sqlStatement(
QSqlDriver::InsertStatement,
DB_TABLE_MESSAGES,
record,
......@@ -314,7 +311,7 @@ void MessageDb::addMessage(const Message &msg, MessageOrigin origin)
void MessageDb::removeMessages(const QString &, const QString &)
{
QSqlQuery query(m_db->currentDatabase());
auto query = createQuery();
Utils::execQuery(query, "DELETE FROM " DB_TABLE_MESSAGES);
}
......@@ -322,8 +319,7 @@ void MessageDb::updateMessage(const QString &id,
const std::function<void (Message &)> &updateMsg)
{
// load current message item from db
auto db = m_db->currentDatabase();
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(
query,
"SELECT * FROM " DB_TABLE_MESSAGES " WHERE id = ? LIMIT 1",
......@@ -342,16 +338,17 @@ void MessageDb::updateMessage(const QString &id,
if (msgs.first() != msg) {
// create an SQL record with only the differences
QSqlRecord rec = createUpdateRecord(msgs.first(), msg);
auto &driver = sqlDriver();
Utils::execQuery(
query,
db.driver()->sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_MESSAGES,
rec,
false
) +
Utils::simpleWhereStatement(db.driver(), "id", id)
query,
driver.sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_MESSAGES,
rec,
false
) +
Utils::simpleWhereStatement(&driver, "id", id)
);
}
}
......@@ -360,17 +357,17 @@ void MessageDb::updateMessage(const QString &id,
void MessageDb::updateMessageRecord(const QString &id,
const QSqlRecord &updateRecord)
{
auto db = m_db->currentDatabase();
auto query = m_db->createQuery();
auto query = createQuery();
auto &driver = sqlDriver();
Utils::execQuery(
query,
db.driver()->sqlStatement(
driver.sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_MESSAGES,
updateRecord,
false
) +
Utils::simpleWhereStatement(db.driver(), "id", id)
Utils::simpleWhereStatement(&driver, "id", id)
);
}
......@@ -411,7 +408,7 @@ bool MessageDb::checkMessageExists(const Message &message)
idConditionSql %
QStringLiteral(")) ORDER BY timestamp DESC LIMIT " CHECK_MESSAGE_EXISTS_DEPTH_LIMIT);
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(query, querySql, bindValues);
int count = 0;
......@@ -423,7 +420,7 @@ bool MessageDb::checkMessageExists(const Message &message)
void MessageDb::fetchPendingMessages(const QString& userJid)
{
auto query = m_db->createQuery();
auto query = createQuery();
QMap<QString, QVariant> bindValues;
bindValues[":user"] = userJid;
......
......@@ -33,6 +33,7 @@
#include <QObject>
#include "Message.h"
#include "DatabaseComponent.h"
class Database;
class QSqlQuery;
......@@ -45,7 +46,7 @@ class QSqlRecord;
* All queries must be executed only after the Kaidan SQL connection has been opened in
* the Database class.
*/
class MessageDb : public QObject
class MessageDb : public DatabaseComponent
{
Q_OBJECT
......@@ -171,5 +172,4 @@ private slots:
private:
static MessageDb *s_instance;
Database *m_db;
};
......@@ -30,7 +30,6 @@
#include "RosterDb.h"
// Kaidan
#include "Database.h"
#include "Globals.h"
#include "Utils.h"
#include "RosterItem.h"
......@@ -45,8 +44,7 @@
RosterDb *RosterDb::s_instance = nullptr;
RosterDb::RosterDb(Database *db, QObject *parent)
: QObject(parent),
m_db(db)
: DatabaseComponent(db, parent)
{
Q_ASSERT(!RosterDb::s_instance);
s_instance = this;
......@@ -105,14 +103,13 @@ void RosterDb::addItem(const RosterItem &item)
void RosterDb::addItems(const QVector<RosterItem> &items)
{
auto db = m_db->currentDatabase();
auto query = m_db->createQuery();
m_db->transaction();
auto query = createQuery();
transaction();
Utils::prepareQuery(query, db.driver()->sqlStatement(
Utils::prepareQuery(query, sqlDriver().sqlStatement(
QSqlDriver::InsertStatement,
DB_TABLE_ROSTER,
db.record(DB_TABLE_ROSTER),
sqlRecord(DB_TABLE_ROSTER),
true
));
......@@ -125,14 +122,14 @@ void RosterDb::addItems(const QVector<RosterItem> &items)
Utils::execQuery(query);
}
m_db->commit();
commit();
}
void RosterDb::updateItem(const QString &jid,
const std::function<void (RosterItem &)> &updateItem)
{
// load current roster item from db
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(
query,
"SELECT * FROM Roster WHERE jid = ? LIMIT 1",
......@@ -163,13 +160,13 @@ void RosterDb::updateItem(const QString &jid,
void RosterDb::replaceItems(const QHash<QString, RosterItem> &items)
{
// load current items
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(query, "SELECT * FROM Roster");
QVector<RosterItem> currentItems;
parseItemsFromQuery(query, currentItems);
m_db->transaction();
transaction();
QList<QString> keys = items.keys();
QSet<QString> newJids = QSet<QString>(keys.begin(), keys.end());
......@@ -197,38 +194,38 @@ void RosterDb::replaceItems(const QHash<QString, RosterItem> &items)
for (const QString &jid : newJids)
addItem(items[jid]);
m_db->commit();
commit();
}
void RosterDb::removeItems(const QString &, const QString &)
{
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(query, "DELETE FROM Roster");
}
void RosterDb::setItemName(const QString &jid, const QString &name)
{
auto db = m_db->currentDatabase();
auto query = m_db->createQuery();
auto query = createQuery();
auto &driver = sqlDriver();
QSqlRecord rec;
rec.append(Utils::createSqlField("name", name));
Utils::execQuery(
query,
db.driver()->sqlStatement(
driver.sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_ROSTER,
rec,
false
) +
Utils::simpleWhereStatement(db.driver(), "jid", jid)
Utils::simpleWhereStatement(&driver, "jid", jid)
);
}
void RosterDb::fetchItems(const QString &accountId)
{
auto query = m_db->createQuery();
auto query = createQuery();
Utils::execQuery(query, "SELECT * FROM Roster");
QVector<RosterItem> items;
......@@ -245,8 +242,8 @@ void RosterDb::fetchItems(const QString &accountId)
void RosterDb::updateItemByRecord(const QString &jid, const QSqlRecord &record)
{
auto db = m_db->currentDatabase();
auto query = m_db->createQuery();
auto query = createQuery();
auto &driver = sqlDriver();
QMap<QString, QVariant> keyValuePairs = {
{ "jid", jid }
......@@ -254,12 +251,12 @@ void RosterDb::updateItemByRecord(const QString &jid, const QSqlRecord &record)
Utils::execQuery(
query,
db.driver()->sqlStatement(
driver.sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_ROSTER,
record,
false
) +
Utils::simpleWhereStatement(db.driver(), keyValuePairs)
Utils::simpleWhereStatement(&driver, keyValuePairs)
);
}
......@@ -30,15 +30,11 @@
#pragma once
// Qt
#include <QObject>
class QSqlQuery;
class QSqlRecord;
// Kaidan
#include "DatabaseComponent.h"
class RosterItem;
class Database;
class RosterDb : public QObject
class RosterDb : public DatabaseComponent
{
Q_OBJECT
......@@ -97,7 +93,5 @@ private slots:
private:
void updateItemByRecord(const QString &jid, const QSqlRecord &record);
Database *m_db;
static RosterDb *s_instance;
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment