// SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert #include #include #include #include #include #include #include #include #include class DatabaseConfiguration; namespace asyncdatabase_private { // Helpers for iterating over tuples template inline constexpr void iterate_impl(Tuple &tup, Func fun) { if constexpr(i >= std::tuple_size_v>) { return; } else { fun(std::get(tup)); return asyncdatabase_private::iterate_impl(tup, fun); } } template inline constexpr void iterate_tuple(Tuple &tup, Func fun) { asyncdatabase_private::iterate_impl(tup, fun); } // Helpers to construct a struct from a tuple template constexpr T constructFromTuple(std::tuple &&args) { return std::apply([](auto && ...args) { return T { args...}; }, std::move(args)); } template concept FromSql = requires(T v, typename T::ColumnTypes row) { typename T::ColumnTypes; { std::tuple(row) } -> std::same_as; }; template concept FromSqlCustom = requires(T v, typename T::ColumnTypes row) { typename T::ColumnTypes; { std::tuple(row) } -> std::same_as; { T::fromSql(std::move(row)) } -> std::same_as; }; template requires FromSql && (!FromSqlCustom) auto deserialize(typename T::ColumnTypes &&row) { return constructFromTuple(std::move(row)); } template requires FromSqlCustom auto deserialize(typename T::ColumnTypes &&row) { return T::fromSql(std::move(row)); } using Row = std::vector; using Rows = std::vector; template auto parseRow(const Row &row) -> RowTypesTuple { auto tuple = RowTypesTuple(); int i = 0; asyncdatabase_private::iterate_tuple(tuple, [&](auto &elem) { elem = row.at(i).value>(); i++; }); return tuple; } template auto parseRows(const Rows &rows) -> std::vector { std::vector parsedRows; parsedRows.reserve(rows.size()); std::transform(rows.begin(), rows.end(), std::back_inserter(parsedRows), parseRow); return parsedRows; } void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory); void printSqlError(const QSqlQuery &query); struct AsyncSqlDatabasePrivate; class FUTURESQL_EXPORT AsyncSqlDatabase : public QObject { Q_OBJECT public: AsyncSqlDatabase(); ~AsyncSqlDatabase() override; QFuture establishConnection(const DatabaseConfiguration &configuration); template auto getResults(const QString &sqlQuery, Args... args) -> QFuture> { return runAsync([=, this] { auto query = executeQuery(sqlQuery, args...); // If the query failed to execute, don't try to deserialize it if (!query) { return std::vector {}; } auto rows = parseRows(retrieveRows(*query)); std::vector deserializedRows; std::transform(rows.begin(), rows.end(), std::back_inserter(deserializedRows), [](auto &&row) { return deserialize(std::move(row)); }); return deserializedRows; }); } template auto getResult(const QString &sqlQuery, Args... args) -> QFuture> { return runAsync([=, this]() -> std::optional { auto query = executeQuery(sqlQuery, args...); // If the query failed to execute, don't try to deserialize it if (!query) { return {}; } if (const auto row = retrieveOptionalRow(*query)) { return deserialize(parseRow(*row)); } return {}; }); } template auto execute(const QString &sqlQuery, Args... args) -> QFuture { return runAsync([=, this] { executeQuery(sqlQuery, args...); }); } template requires std::is_invocable_v auto runOnThread(Func &&func) -> QFuture> { return runAsync([func = std::move(func), this]() { return func(db()); }); } auto runMigrations(const QString &migrationDirectory) -> QFuture; auto setCurrentMigrationLevel(const QString &migrationName) -> QFuture; private: template std::optional executeQuery(const QString &sqlQuery, Args... args) { auto query = prepareQuery(db(), sqlQuery); if (!query) { return {}; } auto argsTuple = std::make_tuple(std::move(args)...); int i = 0; asyncdatabase_private::iterate_tuple(argsTuple, [&](auto &arg) { query->bindValue(i, arg); i++; }); return runQuery(std::move(*query)); } template QFuture> runAsync(Functor func) { using ReturnType = std::invoke_result_t; QFutureInterface interface; QMetaObject::invokeMethod(this, [interface, func]() mutable { if constexpr (!std::is_same_v) { auto result = func(); interface.reportResult(result); } else { func(); } interface.reportFinished(); }); return interface.future(); } Row retrieveRow(const QSqlQuery &query); Rows retrieveRows(QSqlQuery &query); std::optional retrieveOptionalRow(QSqlQuery &query); QSqlDatabase &db(); // non-template helper functions to allow patching a much as possible in the shared library std::optional prepareQuery(const QSqlDatabase &database, const QString &sqlQuery); QSqlQuery runQuery(QSqlQuery &&query); std::unique_ptr d; }; }