From 18fe4233f8a5dedb3740d59af73ae6f312c591a9 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Wed, 10 Feb 2021 15:25:00 +0100 Subject: [PATCH] Sqlite: Add callback method Sometimes it is better to have a callback instead of returning a container. The call has to manage the state if an exception is called but otherwise it will reduce the memory footprint. There will be to a RETURNING to Sqlite which will read back values as you write. Change-Id: I7eb49850e2c76f883a03277b31c5e713e9774c92 Reviewed-by: Thomas Hartmann --- src/libs/sqlite/sqlitebasestatement.h | 34 +++++++- src/libs/sqlite/sqliteglobal.h | 2 + src/libs/sqlite/sqlitereadstatement.h | 3 +- src/libs/sqlite/sqlitereadwritestatement.h | 3 +- tests/unit/unittest/mocksqlitestatement.h | 1 + tests/unit/unittest/sqlitestatement-test.cpp | 86 ++++++++++++++++++++ 6 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index 42063ce0b89..3b68d8a3fc6 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -37,9 +37,10 @@ #include #include +#include #include -#include #include +#include using std::int64_t; @@ -316,6 +317,25 @@ public: return statement.template fetchValue(0); } + template + void readCallback(Callable &&callable, const QueryTypes &...queryValues) + { + BaseStatement::checkColumnCount(ResultTypeCount); + + Resetter resetter{*this}; + + bindValues(queryValues...); + + while (BaseStatement::next()) { + auto control = callCallable(callable); + + if (control == CallbackControl::Abort) + break; + } + + resetter.reset(); + } + protected: ~StatementImplementation() = default; @@ -398,6 +418,18 @@ private: return assignValue(std::make_integer_sequence{}); } + template + CallbackControl callCallable(Callable &&callable, std::integer_sequence) + { + return std::invoke(callable, ValueGetter(*this, ColumnIndices)...); + } + + template + CallbackControl callCallable(Callable &&callable) + { + return callCallable(callable, std::make_integer_sequence{}); + } + template void bindValuesByIndex(int index, const ValueType &value) { diff --git a/src/libs/sqlite/sqliteglobal.h b/src/libs/sqlite/sqliteglobal.h index 308bc07fe03..6f6ecba5423 100644 --- a/src/libs/sqlite/sqliteglobal.h +++ b/src/libs/sqlite/sqliteglobal.h @@ -68,4 +68,6 @@ enum class ChangeType : int { Delete = 9, Insert = 18, Update = 23 }; enum class byte : unsigned char {}; +enum class CallbackControl : unsigned char { Continue, Abort }; + } // namespace Sqlite diff --git a/src/libs/sqlite/sqlitereadstatement.h b/src/libs/sqlite/sqlitereadstatement.h index 310f91fbc93..9314b54e0fe 100644 --- a/src/libs/sqlite/sqlitereadstatement.h +++ b/src/libs/sqlite/sqlitereadstatement.h @@ -34,9 +34,10 @@ class SQLITE_EXPORT ReadStatement final : protected StatementImplementation diff --git a/tests/unit/unittest/sqlitestatement-test.cpp b/tests/unit/unittest/sqlitestatement-test.cpp index a6c8b526d09..6749742b77b 100644 --- a/tests/unit/unittest/sqlitestatement-test.cpp +++ b/tests/unit/unittest/sqlitestatement-test.cpp @@ -946,4 +946,90 @@ TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForToValues) Sqlite::ColumnCountDoesNotMatch); } +TEST_F(SqliteStatement, ReadCallback) +{ + MockFunction callbackMock; + ReadStatement statement("SELECT name, value FROM test", database); + + EXPECT_CALL(callbackMock, Call(Eq("bar"), Eq(1))); + EXPECT_CALL(callbackMock, Call(Eq("foo"), Eq(2))); + EXPECT_CALL(callbackMock, Call(Eq("poo"), Eq(3))); + + statement.readCallback<2>(callbackMock.AsStdFunction()); +} + +TEST_F(SqliteStatement, ReadCallbackCalledWithArguments) +{ + MockFunction callbackMock; + ReadStatement statement("SELECT name, value FROM test WHERE value=?", database); + + EXPECT_CALL(callbackMock, Call(Eq("foo"), Eq(2))); + + statement.readCallback<2>(callbackMock.AsStdFunction(), 2); +} + +TEST_F(SqliteStatement, ReadCallbackAborts) +{ + MockFunction callbackMock; + ReadStatement statement("SELECT name, value FROM test ORDER BY name", database); + + EXPECT_CALL(callbackMock, Call(Eq("bar"), Eq(1))); + EXPECT_CALL(callbackMock, Call(Eq("foo"), Eq(2))).WillOnce(Return(Sqlite::CallbackControl::Abort)); + EXPECT_CALL(callbackMock, Call(Eq("poo"), Eq(3))).Times(0); + + statement.readCallback<2>(callbackMock.AsStdFunction()); +} + +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForReadCallback) +{ + MockFunction callbackMock; + SqliteTestStatement statement("SELECT name, number FROM test", database); + + ASSERT_THROW(statement.readCallback<1>(callbackMock.AsStdFunction()), + Sqlite::ColumnCountDoesNotMatch); +} + +TEST_F(SqliteStatement, ReadCallbackCallsResetAfterCallbacks) +{ + MockFunction callbackMock; + MockSqliteStatement mockStatement; + + EXPECT_CALL(mockStatement, reset()); + + mockStatement.readCallback<2>(callbackMock.AsStdFunction()); +} + +TEST_F(SqliteStatement, ReadCallbackCallsResetAfterCallbacksAborts) +{ + MockFunction callbackMock; + MockSqliteStatement mockStatement; + ON_CALL(callbackMock, Call(_, _)).WillByDefault(Return(Sqlite::CallbackControl::Abort)); + + EXPECT_CALL(mockStatement, reset()); + + mockStatement.readCallback<2>(callbackMock.AsStdFunction()); +} + +TEST_F(SqliteStatement, ReadCallbackThrowsForError) +{ + MockFunction callbackMock; + MockSqliteStatement mockStatement; + ON_CALL(mockStatement, next()).WillByDefault(Throw(Sqlite::StatementHasError(""))); + + ASSERT_THROW(mockStatement.readCallback<2>(callbackMock.AsStdFunction()), + Sqlite::StatementHasError); +} + +TEST_F(SqliteStatement, ReadCallbackCallsResetIfExceptionIsThrown) +{ + MockFunction callbackMock; + MockSqliteStatement mockStatement; + ON_CALL(mockStatement, next()).WillByDefault(Throw(Sqlite::StatementHasError(""))); + + EXPECT_CALL(mockStatement, reset()); + + EXPECT_THROW(mockStatement.readCallback<2>(callbackMock.AsStdFunction()), + Sqlite::StatementHasError); +} + } // namespace