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 #include <memory>
0008 #include <tuple>
0009 #include <optional>
0010 #include <concepts>
0011 
0012 #include <QFuture>
0013 #include <QFutureWatcher>
0014 #include <QSqlQuery>
0015 #include <QSqlDatabase>
0016 #include <QThread>
0017 
0018 #include <futuresql_export.h>
0019 
0020 class DatabaseConfiguration;
0021 
0022 namespace asyncdatabase_private {
0023 
0024 // Helpers for iterating over tuples
0025 template <typename Tuple, typename Func, std::size_t i>
0026 inline constexpr void iterate_impl(Tuple &tup, Func fun)
0027 {
0028     if constexpr(i >= std::tuple_size_v<std::decay_t<decltype(tup)>>) {
0029         return;
0030     } else {
0031         fun(std::get<i>(tup));
0032         return asyncdatabase_private::iterate_impl<Tuple, Func, i + 1>(tup, fun);
0033     }
0034 }
0035 
0036 template <typename Tuple, typename Func>
0037 inline constexpr void iterate_tuple(Tuple &tup, Func fun)
0038 {
0039     asyncdatabase_private::iterate_impl<Tuple, Func, 0>(tup, fun);
0040 }
0041 
0042 // Helpers to construct a struct from a tuple
0043 template <typename T, typename ...Args>
0044 constexpr T constructFromTuple(std::tuple<Args...> &&args) {
0045     return std::apply([](auto && ...args) { return T { args...}; }, std::move(args));
0046 }
0047 
0048 template <typename T>
0049 concept FromSql = requires(T v, typename T::ColumnTypes row)
0050 {
0051     typename T::ColumnTypes;
0052     { std::tuple(row) } -> std::same_as<typename T::ColumnTypes>;
0053 };
0054 
0055 template <typename T>
0056 concept FromSqlCustom = requires(T v, typename T::ColumnTypes row)
0057 {
0058     typename T::ColumnTypes;
0059     { std::tuple(row) } -> std::same_as<typename T::ColumnTypes>;
0060     { T::fromSql(std::move(row)) } -> std::same_as<T>;
0061 };
0062 
0063 template <typename T>
0064 requires FromSql<T> && (!FromSqlCustom<T>)
0065 auto deserialize(typename T::ColumnTypes &&row) {
0066     return constructFromTuple<T>(std::move(row));
0067 }
0068 
0069 template <typename T>
0070 requires FromSqlCustom<T>
0071 auto deserialize(typename T::ColumnTypes &&row) {
0072     return T::fromSql(std::move(row));
0073 }
0074 
0075 using Row  = std::vector<QVariant>;
0076 using Rows = std::vector<Row>;
0077 
0078 template <typename RowTypesTuple>
0079 auto parseRow(const Row &row) -> RowTypesTuple
0080 {
0081     auto tuple = RowTypesTuple();
0082     int i = 0;
0083     asyncdatabase_private::iterate_tuple(tuple, [&](auto &elem) {
0084         elem = row.at(i).value<std::decay_t<decltype(elem)>>();
0085         i++;
0086     });
0087     return tuple;
0088 }
0089 
0090 template <typename RowTypesTuple>
0091 auto parseRows(const Rows &rows) -> std::vector<RowTypesTuple> {
0092     std::vector<RowTypesTuple> parsedRows;
0093     parsedRows.reserve(rows.size());
0094     std::transform(rows.begin(), rows.end(), std::back_inserter(parsedRows), parseRow<RowTypesTuple>);
0095     return parsedRows;
0096 }
0097 
0098 void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory);
0099 
0100 void printSqlError(const QSqlQuery &query);
0101 
0102 struct AsyncSqlDatabasePrivate;
0103 
0104 class FUTURESQL_EXPORT AsyncSqlDatabase : public QObject {
0105     Q_OBJECT
0106 
0107 public:
0108     AsyncSqlDatabase();
0109     ~AsyncSqlDatabase() override;
0110 
0111     QFuture<void> establishConnection(const DatabaseConfiguration &configuration);
0112 
0113     template <typename T, typename ...Args>
0114     auto getResults(const QString &sqlQuery, Args... args) -> QFuture<std::vector<T>> {
0115         return runAsync([=, this] {
0116             auto query = executeQuery(sqlQuery, args...);
0117 
0118             // If the query failed to execute, don't try to deserialize it
0119             if (!query) {
0120                 return std::vector<T> {};
0121             }
0122 
0123             auto rows = parseRows<typename T::ColumnTypes>(retrieveRows(*query));
0124 
0125             std::vector<T> deserializedRows;
0126             std::transform(rows.begin(), rows.end(), std::back_inserter(deserializedRows), [](auto &&row) {
0127                 return deserialize<T>(std::move(row));
0128             });
0129             return deserializedRows;
0130         });
0131     }
0132 
0133     template <typename T, typename ...Args>
0134     auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<T>> {
0135         return runAsync([=, this]() -> std::optional<T> {
0136             auto query = executeQuery(sqlQuery, args...);
0137 
0138             // If the query failed to execute, don't try to deserialize it
0139             if (!query) {
0140                 return {};
0141             }
0142 
0143             if (const auto row = retrieveOptionalRow(*query)) {
0144                 return deserialize<T>(parseRow<typename T::ColumnTypes>(*row));
0145             }
0146 
0147             return {};
0148         });
0149     }
0150 
0151     template <typename ...Args>
0152     auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
0153         return runAsync([=, this] {
0154             executeQuery(sqlQuery, args...);
0155         });
0156     }
0157 
0158     template <typename Func>
0159     requires std::is_invocable_v<Func, const QSqlDatabase &>
0160     auto runOnThread(Func &&func) -> QFuture<std::invoke_result_t<Func, const QSqlDatabase &>> {
0161         return runAsync([func = std::move(func), this]() {
0162             return func(db());
0163         });
0164     }
0165 
0166     auto runMigrations(const QString &migrationDirectory) -> QFuture<void>;
0167 
0168     auto setCurrentMigrationLevel(const QString &migrationName) -> QFuture<void>;
0169 
0170 private:
0171     template <typename ...Args>
0172     std::optional<QSqlQuery> executeQuery(const QString &sqlQuery, Args... args) {
0173         auto query = prepareQuery(db(), sqlQuery);
0174         if (!query) {
0175             return {};
0176         }
0177 
0178         auto argsTuple = std::make_tuple<Args...>(std::move(args)...);
0179         int i = 0;
0180         asyncdatabase_private::iterate_tuple(argsTuple, [&](auto &arg) {
0181             query->bindValue(i, arg);
0182             i++;
0183         });
0184         return runQuery(std::move(*query));
0185     }
0186 
0187     template <typename Functor>
0188     QFuture<std::invoke_result_t<Functor>> runAsync(Functor func) {
0189         using ReturnType = std::invoke_result_t<Functor>;
0190         QFutureInterface<ReturnType> interface;
0191         QMetaObject::invokeMethod(this, [interface, func]() mutable {
0192             if constexpr (!std::is_same_v<ReturnType, void>) {
0193                 auto result = func();
0194                 interface.reportResult(result);
0195             } else {
0196                 func();
0197             }
0198 
0199             interface.reportFinished();
0200         });
0201 
0202         return interface.future();
0203     }
0204 
0205     Row retrieveRow(const QSqlQuery &query);
0206     Rows retrieveRows(QSqlQuery &query);
0207     std::optional<Row> retrieveOptionalRow(QSqlQuery &query);
0208 
0209     QSqlDatabase &db();
0210 
0211     // non-template helper functions to allow patching a much as possible in the shared library
0212     std::optional<QSqlQuery> prepareQuery(const QSqlDatabase &database, const QString &sqlQuery);
0213     QSqlQuery runQuery(QSqlQuery &&query);
0214 
0215     std::unique_ptr<AsyncSqlDatabasePrivate> d;
0216 };
0217 
0218 }