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 }