From b4ab1e173be812dc9e4f701bc1b24542dd733b1d Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 20 Feb 2023 16:15:57 +0100 Subject: [PATCH] Sqlite: Add progress handler Sqlite::ProgressHandler handler{ [] { return Sqlite::Progress::Continue; }, 1000, database}; is setting up a progress handler for this scope. If the handler is destructed it will automatically reset the handler on the database. The handler is active for the whole database connection. Task-number: QDS-9216 Change-Id: I59831f40d32c062eefdfb0c4dfbf3045058e1fd2 Reviewed-by: Vikas Pachdha --- src/libs/sqlite/CMakeLists.txt | 1 + src/libs/sqlite/sqlitedatabasebackend.cpp | 30 ++++++++++++ src/libs/sqlite/sqlitedatabasebackend.h | 6 +++ src/libs/sqlite/sqliteprogresshandler.h | 31 +++++++++++++ tests/unit/unittest/sqlitedatabase-test.cpp | 51 ++++++++++++++++----- 5 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 src/libs/sqlite/sqliteprogresshandler.h diff --git a/src/libs/sqlite/CMakeLists.txt b/src/libs/sqlite/CMakeLists.txt index 9e615420a61..888945b1be2 100644 --- a/src/libs/sqlite/CMakeLists.txt +++ b/src/libs/sqlite/CMakeLists.txt @@ -33,6 +33,7 @@ add_qtc_library(Sqlite sqliteexception.cpp sqliteexception.h sqliteglobal.cpp sqliteglobal.h sqliteindex.h + sqliteprogresshandler.h sqlitereadstatement.h sqlitereadwritestatement.h sqlitesessionchangeset.cpp sqlitesessionchangeset.h diff --git a/src/libs/sqlite/sqlitedatabasebackend.cpp b/src/libs/sqlite/sqlitedatabasebackend.cpp index 2861c45561b..e4d30b1cf6b 100644 --- a/src/libs/sqlite/sqlitedatabasebackend.cpp +++ b/src/libs/sqlite/sqlitedatabasebackend.cpp @@ -459,6 +459,36 @@ void DatabaseBackend::setBusyHandler(DatabaseBackend::BusyHandler &&busyHandler) registerBusyHandler(); } +namespace { + +int progressHandlerCallback(void *userData) +{ + auto &&progressHandler = *static_cast(userData); + + return progressHandler() == Progress::Interrupt; +} + +} // namespace + +void DatabaseBackend::setProgressHandler(int operationCount, ProgressHandler &&progressHandler) +{ + m_progressHandler = std::move(progressHandler); + + if (m_progressHandler) + sqlite3_progress_handler(sqliteDatabaseHandle(), + operationCount, + &progressHandlerCallback, + &m_progressHandler); + else { + sqlite3_progress_handler(sqliteDatabaseHandle(), 0, nullptr, nullptr); + } +} + +void DatabaseBackend::resetProgressHandler() +{ + sqlite3_progress_handler(sqliteDatabaseHandle(), 0, nullptr, nullptr); +} + void DatabaseBackend::throwExceptionStatic(const char *whatHasHappens) { throw Exception(whatHasHappens); diff --git a/src/libs/sqlite/sqlitedatabasebackend.h b/src/libs/sqlite/sqlitedatabasebackend.h index e9c24bd7a0c..5c7bd91d4e8 100644 --- a/src/libs/sqlite/sqlitedatabasebackend.h +++ b/src/libs/sqlite/sqlitedatabasebackend.h @@ -15,10 +15,13 @@ namespace Sqlite { class Database; +enum class Progress { Interrupt, Continue }; + class SQLITE_EXPORT DatabaseBackend { public: using BusyHandler = std::function; + using ProgressHandler = std::function; DatabaseBackend(Database &database); ~DatabaseBackend(); @@ -76,6 +79,8 @@ public: void resetUpdateHook(); void setBusyHandler(BusyHandler &&busyHandler); + void setProgressHandler(int operationCount, ProgressHandler &&progressHandler); + void resetProgressHandler(); void registerBusyHandler(); @@ -112,6 +117,7 @@ private: Database &m_database; sqlite3 *m_databaseHandle; BusyHandler m_busyHandler; + ProgressHandler m_progressHandler; }; } // namespace Sqlite diff --git a/src/libs/sqlite/sqliteprogresshandler.h b/src/libs/sqlite/sqliteprogresshandler.h new file mode 100644 index 00000000000..c4c670c71ab --- /dev/null +++ b/src/libs/sqlite/sqliteprogresshandler.h @@ -0,0 +1,31 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "sqlitedatabase.h" + +namespace Sqlite { + +class ProgressHandler +{ +public: + template + ProgressHandler(Callable &&callable, int operationCount, Database &database) + : m_database{database} + { + std::unique_lock locker{m_database}; + m_database.backend().setProgressHandler(operationCount, std::forward(callable)); + } + + ~ProgressHandler() + { + std::unique_lock locker{m_database}; + m_database.backend().resetProgressHandler(); + } + +private: + Database &m_database; +}; + +} // namespace Sqlite diff --git a/tests/unit/unittest/sqlitedatabase-test.cpp b/tests/unit/unittest/sqlitedatabase-test.cpp index 5771501500c..8cb52444f59 100644 --- a/tests/unit/unittest/sqlitedatabase-test.cpp +++ b/tests/unit/unittest/sqlitedatabase-test.cpp @@ -6,6 +6,7 @@ #include "spydummy.h" #include +#include #include #include #include @@ -32,16 +33,10 @@ class SqliteDatabase : public ::testing::Test protected: SqliteDatabase() { - database.lock(); - database.setJournalMode(JournalMode::Memory); - database.setDatabaseFilePath(databaseFilePath); - Table table; table.setName("test"); table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); table.addColumn("name"); - database.open(); - table.initialize(database); } @@ -49,7 +44,6 @@ protected: { if (database.isOpen()) database.close(); - database.unlock(); } std::vector names() const @@ -68,17 +62,18 @@ protected: protected: SpyDummy spyDummy; - QString databaseFilePath{":memory:"}; - mutable Sqlite::Database database; + Table table; + mutable Sqlite::Database database{":memory:", JournalMode::Memory}; Sqlite::TransactionInterface &transactionInterface = database; MockFunction callbackMock; std::function callback = callbackMock.AsStdFunction(); + std::unique_lock lock{database}; }; TEST_F(SqliteDatabase, SetDatabaseFilePath) { - ASSERT_THAT(database.databaseFilePath(), databaseFilePath); + ASSERT_THAT(database.databaseFilePath(), ":memory:"); } TEST_F(SqliteDatabase, SetJournalMode) @@ -353,7 +348,41 @@ TEST_F(SqliteDatabase, SessionsRollback) .write(2, "hoo"); database.applyAndUpdateSessions(); - ASSERT_THAT(names(), ElementsAre("foo", "hoo")); + ASSERT_THAT(names(), UnorderedElementsAre("foo", "hoo")); +} + +TEST_F(SqliteDatabase, ProgressHandlerInterrupts) +{ + Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database); + lock.unlock(); + Sqlite::ProgressHandler handler{[] { return Sqlite::Progress::Interrupt; }, 1, database}; + lock.lock(); + + ASSERT_THROW(statement.write(42), Sqlite::ExecutionInterrupted); + lock.unlock(); +} + +TEST_F(SqliteDatabase, ProgressHandlerContinues) +{ + Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database); + lock.unlock(); + Sqlite::ProgressHandler handler{[] { return Sqlite::Progress::Continue; }, 1, database}; + lock.lock(); + + ASSERT_NO_THROW(statement.write(42)); + lock.unlock(); +} + +TEST_F(SqliteDatabase, ProgressHandlerResetsAfterLeavingScope) +{ + lock.unlock(); + { + Sqlite::ProgressHandler handler{[] { return Sqlite::Progress::Interrupt; }, 1, database}; + } + lock.lock(); + Sqlite::WriteStatement<1> statement("INSERT INTO test(name) VALUES (?)", database); + + ASSERT_NO_THROW(statement.write(42)); } } // namespace