File indexing completed on 2024-12-08 12:44:36

0001 // SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert <jbb@kaidan.im>
0002 //
0003 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0004 
0005 #pragma once
0006 
0007 class QUrl;
0008 class QSqlDatabase;
0009 
0010 #include <QString>
0011 #include <QObject>
0012 #include <QFuture>
0013 #include <QSharedDataPointer>
0014 
0015 #include <memory>
0016 #include <optional>
0017 #include <tuple>
0018 #include <vector>
0019 
0020 #include "threadeddatabase_p.h"
0021 
0022 #include <futuresql_export.h>
0023 
0024 struct DatabaseConfigurationPrivate;
0025 
0026 ///
0027 /// Selection of database types.
0028 /// If the required one is not included, use the DatabaseConfiguration::setType QString overload instead.
0029 ///
0030 enum DatabaseType {
0031     SQLite
0032 };
0033 
0034 ///
0035 /// Options for connecting to a database
0036 ///
0037 class FUTURESQL_EXPORT DatabaseConfiguration {
0038 public:
0039     DatabaseConfiguration();
0040     DatabaseConfiguration(const DatabaseConfiguration &);
0041     ~DatabaseConfiguration();
0042 
0043     /// Set the name of the database driver. If it is included in DatabaseType, use the enum overload instead
0044     void setType(const QString &type);
0045     /// Set the type of database. If DatabaseType doesn't include the one you need, use the QString overload instead
0046     void setType(DatabaseType type);
0047     /// Get the name of the database driver
0048     const QString &type() const;
0049 
0050     /// Set the hostname
0051     void setHostName(const QString &hostName);
0052     const std::optional<QString> &hostName() const;
0053 
0054     /// Set the name of the database (path of the file for SQLite)
0055     void setDatabaseName(const QString &databaseName);
0056     const std::optional<QString> &databaseName() const;
0057 
0058     /// Set user name
0059     void setUserName(const QString &userName);
0060     const std::optional<QString> &userName() const;
0061 
0062     /// Set password
0063     void setPassword(const QString &password);
0064     const std::optional<QString> &password() const;
0065 
0066 private:
0067     QSharedDataPointer<DatabaseConfigurationPrivate> d;
0068 };
0069 
0070 template <typename T>
0071 concept FromSql = requires(T v, typename T::ColumnTypes row)
0072 {
0073     typename T::ColumnTypes;
0074     { std::tuple(row) } -> std::same_as<typename T::ColumnTypes>;
0075 };
0076 
0077 namespace detail {
0078 
0079 template <typename ...Args>
0080 constexpr bool isQVariantConvertible = std::conjunction_v<std::is_convertible<Args, QVariant>...>;
0081 
0082 }
0083 
0084 struct ThreadedDatabasePrivate;
0085 
0086 ///
0087 /// A database connection that lives on a new thread
0088 ///
0089 class FUTURESQL_EXPORT ThreadedDatabase : public QThread {
0090 public:
0091     ///
0092     /// \brief Connect to a database
0093     /// \param config Configuration of the database connection
0094     /// \return
0095     ///
0096     static std::unique_ptr<ThreadedDatabase> establishConnection(const DatabaseConfiguration &config);
0097 
0098     ///
0099     /// \brief Execute an SQL query on the database, ignoring the result.
0100     /// \param sqlQuery SQL query string to execute
0101     /// \param args Parameters to bind to the placeholders in the SQL Query
0102     /// \return
0103     ///
0104     template <typename ...Args>
0105     requires detail::isQVariantConvertible<Args...>
0106     auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
0107         return db().execute(sqlQuery, args...);
0108     }
0109 
0110     ///
0111     /// Run the database migrations in the given directory.
0112     /// The directory needs to contain a subdirectory for each migration.
0113     /// The subdirectories need to be named so that when sorted alphabetically the migrations will be run in the correct order.
0114     /// Each subdirectory needs to contain a file named up.sql.
0115     ///
0116     /// \param migrationDirectory Directory which contains the migrations.
0117     /// \return a future that finishes when the database changes are finished
0118     ///
0119     auto runMigrations(const QString &migrationDirectory) -> QFuture<void>;
0120 
0121     ///
0122     /// Declare that the database is currently at the state of the migration in the migration subdirectory
0123     /// migrationName.
0124     ///
0125     /// The automatic migrations will then start with all migrations that are newer than migrationName.
0126     ///
0127     /// @warning This function should only be used for the initial switch from a different migration system, for example a custom made one.
0128     /// \param migrationName
0129     /// \return a future that finishes when the database changes are finished
0130     ///
0131     auto setCurrentMigrationLevel(const QString &migrationName) -> QFuture<void>;
0132 
0133     ///
0134     /// \brief Execute an SQL query on the database, retrieving the result.
0135     /// \param sqlQuery SQL Query to execute
0136     /// \param args Parameters to bind to the placeholders in the SQL query.
0137     /// \return Future of a list of lists of variants.
0138     ///
0139     /// T must provide a tuple of the column types as `using ColumnTypes = std::tuple<...>`
0140     /// and a, if the column types are not the same types in the same order as the attributes of the struct,
0141     /// a `static T fromSql(ColumnTypes tuple)` deserialization method.
0142     ///
0143     template <typename T, typename ...Args>
0144     requires FromSql<T> && detail::isQVariantConvertible<Args...>
0145     auto getResults(const QString &sqlQuery, Args... args) -> QFuture<std::vector<T>> {
0146         return db().getResults<T, Args...>(sqlQuery, args...);
0147     }
0148 
0149     ///
0150     /// \brief Like getResults, but for retrieving just one row.
0151     /// \param sqlQuery SQL Query to execute
0152     /// \param args Parameters to bind to the placeholders in the SQL query.
0153     ///
0154     template <typename T, typename ...Args>
0155     requires FromSql<T> && detail::isQVariantConvertible<Args...>
0156     auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<T>> {
0157         return db().getResult<T, Args...>(sqlQuery, args...);
0158     }
0159 
0160     ///
0161     /// \brief Run a custom function on the database thread. The function is passed the internal QSqlDatabase.
0162     /// \param func A function that takes a QSqlDatabase
0163     /// \return The result of the function, wrapped in a QFuture
0164     ///
0165     template <typename Func>
0166     requires std::is_invocable_v<Func, const QSqlDatabase &>
0167     auto runOnThread(Func &&func) -> QFuture<std::invoke_result_t<Func, const QSqlDatabase &>> {
0168         return db().runOnThread(std::move(func));
0169     }
0170 
0171     ~ThreadedDatabase();
0172 
0173 private:
0174     ThreadedDatabase();
0175 
0176     asyncdatabase_private::AsyncSqlDatabase &db();
0177 
0178     std::unique_ptr<ThreadedDatabasePrivate> d;
0179 };
0180 
0181 ///
0182 /// \brief Deserialize just a single value from a query result.
0183 ///
0184 template <typename T>
0185 struct SingleValue {
0186     using ColumnTypes = std::tuple<T>;
0187 
0188     static SingleValue fromSql(ColumnTypes tuple) {
0189         auto [value] = tuple;
0190         return SingleValue { value };
0191     }
0192 
0193     operator const T &() const {
0194         return value;
0195     }
0196 
0197     T value;
0198 };