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"));
56 return query.value(0).toString();
64 createInternalTable(database);
68 qCWarning(asyncdatabase) <<
"The migrations directory" << migrationDirectory
71 const auto entries =
dir.entryList(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDir::SortFlag::Name);
73 const QString currentVersion = currentDatabaseVersion(database);
74 for (
const auto &entry : entries) {
76 if (subdir.dirName() > currentVersion) {
79 qCDebug(asyncdatabase) <<
"Failed to open migration file" << file.fileName();
81 qCDebug(asyncdatabase) <<
"Running migration" << subdir.dirName();
86 const auto statements = file.readAll().split(
';');
88 bool migrationSuccessful =
true;
89 for (
const QByteArray &statement : statements) {
93 if (!trimmedStatement.isEmpty()) {
94 qCDebug(asyncdatabase) <<
"Running" << trimmedStatement;
95 if (!
query.prepare(trimmedStatement)) {
97 migrationSuccessful =
false;
99 bool success =
query.exec();
100 migrationSuccessful &= success;
102 printSqlError(query);
108 if (migrationSuccessful) {
110 markMigrationRun(database, subdir.dirName());
112 qCWarning(asyncdatabase) <<
"Migration" << subdir.dirName() <<
"failed, retrying next time.";
113 qCWarning(asyncdatabase) <<
"Stopping migrations here, as the next migration may depens on this one.";
119 qCDebug(asyncdatabase) <<
"Migrations finished";
122struct AsyncSqlDatabasePrivate {
124 std::unordered_map<QString, QSqlQuery> preparedQueryCache;
130 return runAsync([=,
this] {
132 if (configuration.databaseName()) {
133 d->database.setDatabaseName(*configuration.databaseName());
135 if (configuration.hostName()) {
136 d->database.setHostName(*configuration.hostName());
138 if (configuration.userName()) {
139 d->database.setUserName(*configuration.userName());
141 if (configuration.password()) {
142 d->database.setPassword(*configuration.password());
145 if (!d->database.open()) {
146 qCDebug(asyncdatabase) <<
"Failed to open database" << d->database.lastError().text();
147 if (configuration.databaseName()) {
148 qCDebug(asyncdatabase) <<
"Tried to use database" << *configuration.databaseName();
155 return runAsync([=,
this] {
156 runDatabaseMigrations(d->database, migrationDirectory);
160 return runAsync([=,
this] {
161 createInternalTable(d->database);
162 markMigrationRun(d->database, migrationName);
166AsyncSqlDatabase::AsyncSqlDatabase()
168 , d(std::make_unique<AsyncSqlDatabasePrivate>())
172AsyncSqlDatabase::~AsyncSqlDatabase() {
173 runAsync([db = d->database] {
174 QSqlDatabase::removeDatabase(db.databaseName());
178Row AsyncSqlDatabase::retrieveRow(
const QSqlQuery &query) {
183 if (
query.isValid()) {
186 row.push_back(std::move(value));
198Rows AsyncSqlDatabase::retrieveRows(
QSqlQuery &query)
201 while (
query.next()) {
202 rows.push_back(retrieveRow(query));
208std::optional<Row> AsyncSqlDatabase::retrieveOptionalRow(
QSqlQuery &query)
212 if (
query.isValid()) {
213 return retrieveRow(query);
224void printSqlError(
const QSqlQuery &query)
226 qCWarning(asyncdatabase) <<
"SQL error:" <<
query.lastError().text();
229std::optional<QSqlQuery> AsyncSqlDatabase::prepareQuery(
const QSqlDatabase &database,
const QString &sqlQuery)
231 qCDebug(asyncdatabase) <<
"Running" << sqlQuery;
234 if (d->preparedQueryCache.contains(sqlQuery)) {
235 return d->preparedQueryCache[sqlQuery];
242 if (!
query.prepare(sqlQuery)) {
243 printSqlError(query);
248 d->preparedQueryCache.insert({sqlQuery,
query});
255 printSqlError(query);
257 return std::move(query);
262struct DatabaseConfigurationPrivate :
public QSharedData {
264 std::optional<QString> hostName;
265 std::optional<QString> databaseName;
266 std::optional<QString> userName;
267 std::optional<QString> password;
270DatabaseConfiguration::DatabaseConfiguration() : d(new DatabaseConfigurationPrivate)
273DatabaseConfiguration::~DatabaseConfiguration() =
default;
283 case DatabaseType::SQLite:
284 d->type = QStringLiteral(
"QSQLITE");
296 d->hostName = hostName;
299const std::optional<QString> &DatabaseConfiguration::hostName()
const {
304 d->databaseName = databaseName;
307const std::optional<QString> &DatabaseConfiguration::databaseName()
const {
308 return d->databaseName;
312 d->userName = userName;
315const std::optional<QString> &DatabaseConfiguration::userName()
const {
320 d->password = password;
323const std::optional<QString> &DatabaseConfiguration::password()
const {
328struct ThreadedDatabasePrivate {
329 asyncdatabase_private::AsyncSqlDatabase db;
333 auto threadedDb = std::unique_ptr<ThreadedDatabase>(
new ThreadedDatabase());
334 threadedDb->setObjectName(QStringLiteral(
"database thread"));
335 threadedDb->d->db.moveToThread(&*threadedDb);
337 threadedDb->d->db.establishConnection(config);
342 return d->db.runMigrations(migrationDirectory);
346 return d->db.setCurrentMigrationLevel(migrationName);
349ThreadedDatabase::ThreadedDatabase()
351 , d(std::make_unique<ThreadedDatabasePrivate>())
355ThreadedDatabase::~ThreadedDatabase()
361asyncdatabase_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)
std::optional< QSqlQuery > query(const QString &queryStatement)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
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