5#include "threadeddatabase.h"
6#include "threadeddatabase_p.h"
12#include <QStringBuilder>
16#include <QLoggingCategory>
18#include <unordered_map>
20#define SCHAMA_MIGRATIONS_TABLE "__qt_schema_migrations"
22Q_DECLARE_LOGGING_CATEGORY(asyncdatabase)
23Q_LOGGING_CATEGORY(asyncdatabase,
"futuresql")
25namespace asyncdatabase_private {
29 QSqlQuery query(QStringLiteral(
"create table if not exists " SCHAMA_MIGRATIONS_TABLE
" ("
30 "version Text primary key not null, "
31 "run_on timestamp not null default current_timestamp)"), database);
38 qCDebug(asyncdatabase) <<
"Marking migration" <<
name <<
"as done.";
41 if (!
query.prepare(QStringLiteral(
"insert into " SCHAMA_MIGRATIONS_TABLE
" (version) values (:name)"))) {
44 query.bindValue(QStringLiteral(
":name"), name);
52 query.prepare(QStringLiteral(
"select version from " SCHAMA_MIGRATIONS_TABLE
" order by version desc limit 1"));
64 createInternalTable(database);
67 const auto entries =
dir.entryList(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDir::SortFlag::Name);
69 const QString currentVersion = currentDatabaseVersion(database);
70 for (
const auto &entry : entries) {
72 if (subdir.dirName() > currentVersion) {
75 qCDebug(asyncdatabase) <<
"Failed to open migration file" << file.fileName();
77 qCDebug(asyncdatabase) <<
"Running migration" << subdir.dirName();
82 const auto statements = file.readAll().split(
';');
84 bool migrationSuccessful =
true;
85 for (
const QByteArray &statement : statements) {
89 if (!trimmedStatement.isEmpty()) {
90 qCDebug(asyncdatabase) <<
"Running" << trimmedStatement;
91 if (!
query.prepare(trimmedStatement)) {
93 migrationSuccessful =
false;
95 bool success =
query.exec();
96 migrationSuccessful &= success;
104 if (migrationSuccessful) {
106 markMigrationRun(database, subdir.dirName());
108 qCWarning(asyncdatabase) <<
"Migration" << subdir.dirName() <<
"failed, retrying next time.";
109 qCWarning(asyncdatabase) <<
"Stopping migrations here, as the next migration may depens on this one.";
115 qCDebug(asyncdatabase) <<
"Migrations finished";
118struct AsyncSqlDatabasePrivate {
120 std::unordered_map<QString, QSqlQuery> preparedQueryCache;
126 return runAsync([=,
this] {
128 if (configuration.databaseName()) {
129 d->database.setDatabaseName(*configuration.databaseName());
131 if (configuration.hostName()) {
132 d->database.setHostName(*configuration.hostName());
134 if (configuration.userName()) {
135 d->database.setUserName(*configuration.userName());
137 if (configuration.password()) {
138 d->database.setPassword(*configuration.password());
141 if (!d->database.open()) {
142 qCDebug(asyncdatabase) <<
"Failed to open database" << d->database.lastError().text();
143 if (configuration.databaseName()) {
144 qCDebug(asyncdatabase) <<
"Tried to use database" << *configuration.databaseName();
151 return runAsync([=,
this] {
152 runDatabaseMigrations(d->database, migrationDirectory);
156 return runAsync([=,
this] {
157 createInternalTable(d->database);
158 markMigrationRun(d->database, migrationName);
162AsyncSqlDatabase::AsyncSqlDatabase()
164 , d(
std::make_unique<AsyncSqlDatabasePrivate>())
168AsyncSqlDatabase::~AsyncSqlDatabase() {
169 runAsync([db = d->database] {
170 QSqlDatabase::removeDatabase(db.databaseName());
174Row AsyncSqlDatabase::retrieveRow(
const QSqlQuery &query) {
179 if (
query.isValid()) {
182 row.push_back(std::move(value));
194Rows AsyncSqlDatabase::retrieveRows(
QSqlQuery &query)
197 while (
query.next()) {
198 rows.push_back(retrieveRow(query));
204std::optional<Row> AsyncSqlDatabase::retrieveOptionalRow(
QSqlQuery &query)
208 if (
query.isValid()) {
209 return retrieveRow(query);
220void printSqlError(
const QSqlQuery &query)
222 qCDebug(asyncdatabase) <<
"SQL error:" <<
query.lastError().text();
225std::optional<QSqlQuery> AsyncSqlDatabase::prepareQuery(
const QSqlDatabase &database,
const QString &sqlQuery)
227 qCDebug(asyncdatabase) <<
"Running" << sqlQuery;
230 if (d->preparedQueryCache.contains(sqlQuery)) {
231 return d->preparedQueryCache[sqlQuery];
238 if (!
query.prepare(sqlQuery)) {
239 printSqlError(query);
244 d->preparedQueryCache.insert({sqlQuery,
query});
251 printSqlError(query);
253 return std::move(query);
258struct DatabaseConfigurationPrivate :
public QSharedData {
260 std::optional<QString> hostName;
261 std::optional<QString> databaseName;
262 std::optional<QString> userName;
263 std::optional<QString> password;
266DatabaseConfiguration::DatabaseConfiguration() : d(new DatabaseConfigurationPrivate)
269DatabaseConfiguration::~DatabaseConfiguration() =
default;
279 case DatabaseType::SQLite:
280 d->type = QStringLiteral(
"QSQLITE");
292 d->hostName = hostName;
295const std::optional<QString> &DatabaseConfiguration::hostName()
const {
300 d->databaseName = databaseName;
303const std::optional<QString> &DatabaseConfiguration::databaseName()
const {
304 return d->databaseName;
308 d->userName = userName;
311const std::optional<QString> &DatabaseConfiguration::userName()
const {
316 d->password = password;
319const std::optional<QString> &DatabaseConfiguration::password()
const {
324struct ThreadedDatabasePrivate {
325 asyncdatabase_private::AsyncSqlDatabase db;
329 auto threadedDb = std::unique_ptr<ThreadedDatabase>(
new ThreadedDatabase());
330 threadedDb->setObjectName(QStringLiteral(
"database thread"));
331 threadedDb->d->db.moveToThread(&*threadedDb);
333 threadedDb->d->db.establishConnection(config);
338 return d->db.runMigrations(migrationDirectory);
342 return d->db.setCurrentMigrationLevel(migrationName);
345ThreadedDatabase::ThreadedDatabase()
347 , d(
std::make_unique<ThreadedDatabasePrivate>())
351ThreadedDatabase::~ThreadedDatabase()
357asyncdatabase_private::AsyncSqlDatabase &ThreadedDatabase::db()
Options for connecting to a database.
void setDatabaseName(const QString &databaseName)
Set the name of the database (path of the file for SQLite)
void setType(const QString &type)
Set the name of the database driver. If it is included in DatabaseType, use the enum overload instead...
void setHostName(const QString &hostName)
Set the hostname.
void setUserName(const QString &userName)
Set user name.
const QString & type() const
Get the name of the database driver.
void setPassword(const QString &password)
Set password.
A database connection that lives on a new thread.
auto runMigrations(const QString &migrationDirectory) -> QFuture< void >
Run the database migrations in the given directory.
static std::unique_ptr< ThreadedDatabase > establishConnection(const DatabaseConfiguration &config)
Connect to a database.
auto setCurrentMigrationLevel(const QString &migrationName) -> QFuture< void >
Declare that the database is currently at the state of the migration in the migration subdirectory mi...
Type type(const QSqlDatabase &db)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardShortcut id)
T value(qsizetype i) const const
QSqlDatabase addDatabase(QSqlDriver *driver, const QString &connectionName)
QSqlDatabase database(const QString &connectionName, bool open)
QString fromUtf8(QByteArrayView str)
bool wait(QDeadlineTimer deadline)
bool isValid() const const