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 <vikas.pachdha@qt.io>
This commit is contained in:
Marco Bubke
2023-02-20 16:15:57 +01:00
parent 786fb803aa
commit b4ab1e173b
5 changed files with 108 additions and 11 deletions

View File

@@ -33,6 +33,7 @@ add_qtc_library(Sqlite
sqliteexception.cpp sqliteexception.h sqliteexception.cpp sqliteexception.h
sqliteglobal.cpp sqliteglobal.h sqliteglobal.cpp sqliteglobal.h
sqliteindex.h sqliteindex.h
sqliteprogresshandler.h
sqlitereadstatement.h sqlitereadstatement.h
sqlitereadwritestatement.h sqlitereadwritestatement.h
sqlitesessionchangeset.cpp sqlitesessionchangeset.h sqlitesessionchangeset.cpp sqlitesessionchangeset.h

View File

@@ -459,6 +459,36 @@ void DatabaseBackend::setBusyHandler(DatabaseBackend::BusyHandler &&busyHandler)
registerBusyHandler(); registerBusyHandler();
} }
namespace {
int progressHandlerCallback(void *userData)
{
auto &&progressHandler = *static_cast<DatabaseBackend::ProgressHandler *>(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) void DatabaseBackend::throwExceptionStatic(const char *whatHasHappens)
{ {
throw Exception(whatHasHappens); throw Exception(whatHasHappens);

View File

@@ -15,10 +15,13 @@ namespace Sqlite {
class Database; class Database;
enum class Progress { Interrupt, Continue };
class SQLITE_EXPORT DatabaseBackend class SQLITE_EXPORT DatabaseBackend
{ {
public: public:
using BusyHandler = std::function<bool(int count)>; using BusyHandler = std::function<bool(int count)>;
using ProgressHandler = std::function<Progress()>;
DatabaseBackend(Database &database); DatabaseBackend(Database &database);
~DatabaseBackend(); ~DatabaseBackend();
@@ -76,6 +79,8 @@ public:
void resetUpdateHook(); void resetUpdateHook();
void setBusyHandler(BusyHandler &&busyHandler); void setBusyHandler(BusyHandler &&busyHandler);
void setProgressHandler(int operationCount, ProgressHandler &&progressHandler);
void resetProgressHandler();
void registerBusyHandler(); void registerBusyHandler();
@@ -112,6 +117,7 @@ private:
Database &m_database; Database &m_database;
sqlite3 *m_databaseHandle; sqlite3 *m_databaseHandle;
BusyHandler m_busyHandler; BusyHandler m_busyHandler;
ProgressHandler m_progressHandler;
}; };
} // namespace Sqlite } // namespace Sqlite

View File

@@ -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<typename Callable>
ProgressHandler(Callable &&callable, int operationCount, Database &database)
: m_database{database}
{
std::unique_lock<TransactionInterface> locker{m_database};
m_database.backend().setProgressHandler(operationCount, std::forward<Callable>(callable));
}
~ProgressHandler()
{
std::unique_lock<TransactionInterface> locker{m_database};
m_database.backend().resetProgressHandler();
}
private:
Database &m_database;
};
} // namespace Sqlite

View File

@@ -6,6 +6,7 @@
#include "spydummy.h" #include "spydummy.h"
#include <sqlitedatabase.h> #include <sqlitedatabase.h>
#include <sqliteprogresshandler.h>
#include <sqlitereadstatement.h> #include <sqlitereadstatement.h>
#include <sqlitetable.h> #include <sqlitetable.h>
#include <sqlitewritestatement.h> #include <sqlitewritestatement.h>
@@ -32,16 +33,10 @@ class SqliteDatabase : public ::testing::Test
protected: protected:
SqliteDatabase() SqliteDatabase()
{ {
database.lock();
database.setJournalMode(JournalMode::Memory);
database.setDatabaseFilePath(databaseFilePath);
Table table;
table.setName("test"); table.setName("test");
table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}});
table.addColumn("name"); table.addColumn("name");
database.open();
table.initialize(database); table.initialize(database);
} }
@@ -49,7 +44,6 @@ protected:
{ {
if (database.isOpen()) if (database.isOpen())
database.close(); database.close();
database.unlock();
} }
std::vector<Utils::SmallString> names() const std::vector<Utils::SmallString> names() const
@@ -68,17 +62,18 @@ protected:
protected: protected:
SpyDummy spyDummy; SpyDummy spyDummy;
QString databaseFilePath{":memory:"}; Table table;
mutable Sqlite::Database database; mutable Sqlite::Database database{":memory:", JournalMode::Memory};
Sqlite::TransactionInterface &transactionInterface = database; Sqlite::TransactionInterface &transactionInterface = database;
MockFunction<void(Sqlite::ChangeType tupe, char const *, char const *, long long)> callbackMock; MockFunction<void(Sqlite::ChangeType tupe, char const *, char const *, long long)> callbackMock;
std::function<void(Sqlite::ChangeType tupe, char const *, char const *, long long)> std::function<void(Sqlite::ChangeType tupe, char const *, char const *, long long)>
callback = callbackMock.AsStdFunction(); callback = callbackMock.AsStdFunction();
std::unique_lock<Sqlite::Database> lock{database};
}; };
TEST_F(SqliteDatabase, SetDatabaseFilePath) TEST_F(SqliteDatabase, SetDatabaseFilePath)
{ {
ASSERT_THAT(database.databaseFilePath(), databaseFilePath); ASSERT_THAT(database.databaseFilePath(), ":memory:");
} }
TEST_F(SqliteDatabase, SetJournalMode) TEST_F(SqliteDatabase, SetJournalMode)
@@ -353,7 +348,41 @@ TEST_F(SqliteDatabase, SessionsRollback)
.write(2, "hoo"); .write(2, "hoo");
database.applyAndUpdateSessions(); 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 } // namespace