From f69314b6aeac47fe79bb40b7cd291515abcb7e28 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Wed, 21 Apr 2021 14:31:36 +0200 Subject: [PATCH] Sqlite: Add range getter Sometimes it is handy to return a range so you can break in a for range loop. So you can now write: for (auto [key, value] : statement.range>()) { values.push_back(value); if (value == Tuple{"foo", 23.3, 2}) break; } for (auto [key, value] : statement.rangeWithTransaction>()) { values.push_back(value); if (value == Tuple{"foo", 23.3, 2}) break; } It will only step over the statement to the break. So you don't fetch not used values anymore. The second version will add a transaction around the range. But be careful. The range is view to the statement. Holding it longer or reusing it can lead to strange behavior because the state of the statement is changing. Change-Id: I90191f7da5a015c7d8077d5bc428655a2b53246c Reviewed-by: Tim Jenssen --- src/libs/sqlite/sqlitebasestatement.h | 204 ++++++++++-- src/libs/sqlite/sqlitereadstatement.h | 2 + tests/unit/unittest/mocksqlitestatement.h | 20 +- tests/unit/unittest/sqlitedatabasemock.h | 1 - tests/unit/unittest/sqlitestatement-test.cpp | 314 +++++++++++++++++++ 5 files changed, 517 insertions(+), 24 deletions(-) diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index 3823a65b946..019a7a44f3e 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -29,6 +29,7 @@ #include "sqliteblob.h" #include "sqliteexception.h" +#include "sqlitetransaction.h" #include "sqlitevalue.h" #include @@ -37,6 +38,7 @@ #include #include +#include #include #include #include @@ -163,13 +165,14 @@ extern template SQLITE_EXPORT Utils::PathString BaseStatement::fetchValue class StatementImplementation : public BaseStatement { + struct Resetter; public: using BaseStatement::BaseStatement; void execute() { - Resetter resetter{*this}; + Resetter resetter{this}; BaseStatement::next(); resetter.reset(); } @@ -185,7 +188,7 @@ public: template void write(const ValueType&... values) { - Resetter resetter{*this}; + Resetter resetter{this}; bindValuesByIndex(1, values...); BaseStatement::next(); resetter.reset(); @@ -194,7 +197,7 @@ public: template std::vector values(std::size_t reserveSize) { - Resetter resetter{*this}; + Resetter resetter{this}; std::vector resultValues; resultValues.reserve(std::max(reserveSize, m_maximumResultCount)); @@ -211,7 +214,7 @@ public: template auto values(std::size_t reserveSize, const QueryTypes &...queryValues) { - Resetter resetter{*this}; + Resetter resetter{this}; std::vector resultValues; resultValues.reserve(std::max(reserveSize, m_maximumResultCount)); @@ -230,13 +233,13 @@ public: template auto value(const QueryTypes &...queryValues) { - Resetter resetter{*this}; + Resetter resetter{this}; Utils::optional resultValue; bindValues(queryValues...); if (BaseStatement::next()) - resultValue = assignValue>(); + resultValue = createOptionalValue>(); resetter.reset(); @@ -258,7 +261,7 @@ public: template void readCallback(Callable &&callable, const QueryTypes &...queryValues) { - Resetter resetter{*this}; + Resetter resetter{this}; bindValues(queryValues...); @@ -275,7 +278,7 @@ public: template void readTo(Container &container, const QueryTypes &...queryValues) { - Resetter resetter{*this}; + Resetter resetter{this}; bindValues(queryValues...); @@ -285,39 +288,187 @@ public: resetter.reset(); } + template + auto range(const QueryTypes &...queryValues) + { + return SqliteResultRange{*this, queryValues...}; + } + + template + auto rangeWithTransaction(const QueryTypes &...queryValues) + { + return SqliteResultRangeWithTransaction{*this, queryValues...}; + } + + template + class BaseSqliteResultRange + { + public: + class SqliteResultIteratator + { + public: + using iterator_category = std::input_iterator_tag; + using difference_type = int; + using value_type = ResultType; + using pointer = ResultType *; + using reference = ResultType &; + + SqliteResultIteratator(StatementImplementation &statement) + : m_statement{statement} + , m_hasNext{m_statement.next()} + {} + + SqliteResultIteratator(StatementImplementation &statement, bool hasNext) + : m_statement{statement} + , m_hasNext{hasNext} + {} + + SqliteResultIteratator &operator++() + { + m_hasNext = m_statement.next(); + return *this; + } + + void operator++(int) { m_hasNext = m_statement.next(); } + + friend bool operator==(const SqliteResultIteratator &first, + const SqliteResultIteratator &second) + { + return first.m_hasNext == second.m_hasNext; + } + + friend bool operator!=(const SqliteResultIteratator &first, + const SqliteResultIteratator &second) + { + return !(first == second); + } + + value_type operator*() const { return m_statement.createValue(); } + + private: + StatementImplementation &m_statement; + bool m_hasNext = false; + }; + + using value_type = ResultType; + using iterator = SqliteResultIteratator; + using const_iterator = iterator; + + template + BaseSqliteResultRange(StatementImplementation &statement, const QueryTypes &...queryValues) + : m_statement{statement} + { + statement.bindValues(queryValues...); + } + + BaseSqliteResultRange(BaseSqliteResultRange &) = delete; + BaseSqliteResultRange &operator=(BaseSqliteResultRange &) = delete; + + BaseSqliteResultRange(BaseSqliteResultRange &&other) + : m_statement{std::move(other.resetter)} + {} + BaseSqliteResultRange &operator=(BaseSqliteResultRange &&) = delete; + + iterator begin() & { return iterator{m_statement}; } + iterator end() & { return iterator{m_statement, false}; } + + const_iterator begin() const & { return iterator{m_statement}; } + const_iterator end() const & { return iterator{m_statement, false}; } + + private: + StatementImplementation &m_statement; + }; + + template + class SqliteResultRange : public BaseSqliteResultRange + { + public: + template + SqliteResultRange(StatementImplementation &statement, const QueryTypes &...queryValues) + : BaseSqliteResultRange{statement} + , resetter{&statement} + + { + statement.bindValues(queryValues...); + } + + ~SqliteResultRange() + { + if (!std::uncaught_exceptions()) + resetter.reset(); + } + + private: + Resetter resetter; + }; + + template + class SqliteResultRangeWithTransaction : public BaseSqliteResultRange + { + public: + template + SqliteResultRangeWithTransaction(StatementImplementation &statement, + const QueryTypes &...queryValues) + : BaseSqliteResultRange{statement} + , m_transaction{statement.database()} + , resetter{&statement} + { + statement.bindValues(queryValues...); + } + + ~SqliteResultRangeWithTransaction() + { + if (!std::uncaught_exceptions()) { + resetter.reset(); + m_transaction.commit(); + } + } + + private: + DeferredTransaction m_transaction; + Resetter resetter; + }; + protected: ~StatementImplementation() = default; private: struct Resetter { - Resetter(StatementImplementation &statement) + Resetter(StatementImplementation *statement) : statement(statement) {} + Resetter(Resetter &) = delete; + Resetter &operator=(Resetter &) = delete; + + Resetter(Resetter &&other) + : statement{std::exchange(other.statement, nullptr)} + {} + void reset() { try { - statement.reset(); + if (statement) + statement->reset(); } catch (...) { - shouldReset = false; + statement = nullptr; throw; } - shouldReset = false; + statement = nullptr; } ~Resetter() noexcept { try { - if (shouldReset) - statement.reset(); + if (statement) + statement->reset(); } catch (...) { } } - StatementImplementation &statement; - bool shouldReset = true; + StatementImplementation *statement; }; struct ValueGetter @@ -356,17 +507,28 @@ private: emplaceBackValues(container, std::make_integer_sequence{}); } - template - ResultOptionalType assignValue(std::integer_sequence) + template + ResultOptionalType createOptionalValue(std::integer_sequence) { return ResultOptionalType(Utils::in_place, ValueGetter(*this, ColumnIndices)...); } template - ResultOptionalType assignValue() + ResultOptionalType createOptionalValue() { - return assignValue(std::make_integer_sequence{}); + return createOptionalValue(std::make_integer_sequence{}); + } + + template + ResultType createValue(std::integer_sequence) + { + return ResultType{ValueGetter(*this, ColumnIndices)...}; + } + + template + ResultType createValue() + { + return createValue(std::make_integer_sequence{}); } template diff --git a/src/libs/sqlite/sqlitereadstatement.h b/src/libs/sqlite/sqlitereadstatement.h index eb9c4e5cc5c..281a7a3fa61 100644 --- a/src/libs/sqlite/sqlitereadstatement.h +++ b/src/libs/sqlite/sqlitereadstatement.h @@ -42,6 +42,8 @@ public: Base::checkColumnCount(ResultCount); } + using Base::range; + using Base::rangeWithTransaction; using Base::readCallback; using Base::readTo; using Base::toValue; diff --git a/tests/unit/unittest/mocksqlitestatement.h b/tests/unit/unittest/mocksqlitestatement.h index ff556a9a4d6..1f0da729d2b 100644 --- a/tests/unit/unittest/mocksqlitestatement.h +++ b/tests/unit/unittest/mocksqlitestatement.h @@ -25,7 +25,8 @@ #pragma once -#include +#include "googletest.h" +#include "sqlitedatabasemock.h" #include @@ -33,7 +34,12 @@ class BaseMockSqliteStatement { public: - MOCK_METHOD0(next, bool ()); + BaseMockSqliteStatement() = default; + BaseMockSqliteStatement(SqliteDatabaseMock &databaseMock) + : m_databaseMock{&databaseMock} + {} + + MOCK_METHOD0(next, bool()); MOCK_METHOD0(step, void ()); MOCK_METHOD0(reset, void ()); @@ -60,6 +66,11 @@ public: MOCK_METHOD1(checkColumnCount, void(int)); MOCK_CONST_METHOD0(isReadOnlyStatement, bool()); + + SqliteDatabaseMock &database() { return *m_databaseMock; } + +private: + SqliteDatabaseMock *m_databaseMock = nullptr; }; template<> @@ -102,8 +113,13 @@ template class MockSqliteStatement : public Sqlite::StatementImplementation, ResultCount> { + using Base = Sqlite::StatementImplementation, ResultCount>; + public: explicit MockSqliteStatement() {} + explicit MockSqliteStatement(SqliteDatabaseMock &databaseMock) + : Base{databaseMock} + {} protected: void checkIsWritableStatement(); diff --git a/tests/unit/unittest/sqlitedatabasemock.h b/tests/unit/unittest/sqlitedatabasemock.h index d65dd3f0faf..28c373f4c4d 100644 --- a/tests/unit/unittest/sqlitedatabasemock.h +++ b/tests/unit/unittest/sqlitedatabasemock.h @@ -70,4 +70,3 @@ public: MOCK_METHOD(void, setAttachedTables, (const Utils::SmallStringVector &tables), (override)); }; - diff --git a/tests/unit/unittest/sqlitestatement-test.cpp b/tests/unit/unittest/sqlitestatement-test.cpp index c9ffafbb0a4..10e5c609ae5 100644 --- a/tests/unit/unittest/sqlitestatement-test.cpp +++ b/tests/unit/unittest/sqlitestatement-test.cpp @@ -25,6 +25,7 @@ #include "googletest.h" #include "mocksqlitestatement.h" +#include "sqlitedatabasemock.h" #include "sqliteteststatement.h" #include @@ -662,6 +663,116 @@ TEST_F(SqliteStatement, GetTupleValuesWithoutArguments) UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2}, Tuple{"poo", 40.0, 3})); } +TEST_F(SqliteStatement, GetTupleRangeWithoutArguments) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test", database); + + auto range = statement.range(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, + UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2}, Tuple{"poo", 40.0, 3})); +} + +TEST_F(SqliteStatement, GetTupleRangeWithTransactionWithoutArguments) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test", database); + + auto range = statement.rangeWithTransaction(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, + UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2}, Tuple{"poo", 40.0, 3})); +} + +TEST_F(SqliteStatement, GetTupleRangeInForRangeLoop) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test", database); + std::vector values; + + for (auto value : statement.range()) + values.push_back(value); + + ASSERT_THAT(values, + UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2}, Tuple{"poo", 40.0, 3})); +} + +TEST_F(SqliteStatement, GetTupleRangeWithTransactionInForRangeLoop) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test", database); + std::vector values; + + for (auto value : statement.rangeWithTransaction()) + values.push_back(value); + + ASSERT_THAT(values, + UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2}, Tuple{"poo", 40.0, 3})); +} + +TEST_F(SqliteStatement, GetTupleRangeInForRangeLoopWithBreak) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database); + std::vector values; + + for (auto value : statement.range()) { + values.push_back(value); + if (value == Tuple{"foo", 23.3, 2}) + break; + } + + ASSERT_THAT(values, UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2})); +} + +TEST_F(SqliteStatement, GetTupleRangeWithTransactionInForRangeLoopWithBreak) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database); + std::vector values; + + for (auto value : statement.rangeWithTransaction()) { + values.push_back(value); + if (value == Tuple{"foo", 23.3, 2}) + break; + } + + ASSERT_THAT(values, UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2})); +} + +TEST_F(SqliteStatement, GetTupleRangeInForRangeLoopWithContinue) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database); + std::vector values; + + for (auto value : statement.range()) { + if (value == Tuple{"foo", 23.3, 2}) + continue; + values.push_back(value); + } + + ASSERT_THAT(values, UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"poo", 40.0, 3})); +} + +TEST_F(SqliteStatement, GetTupleRangeWithTransactionInForRangeLoopWithContinue) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database); + std::vector values; + + for (auto value : statement.rangeWithTransaction()) { + if (value == Tuple{"foo", 23.3, 2}) + continue; + values.push_back(value); + } + + ASSERT_THAT(values, UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"poo", 40.0, 3})); +} + TEST_F(SqliteStatement, GetSingleValuesWithoutArguments) { ReadStatement<1> statement("SELECT name FROM test", database); @@ -671,6 +782,26 @@ TEST_F(SqliteStatement, GetSingleValuesWithoutArguments) ASSERT_THAT(values, UnorderedElementsAre("bar", "foo", "poo")); } +TEST_F(SqliteStatement, GetSingleRangeWithoutArguments) +{ + ReadStatement<1> statement("SELECT name FROM test", database); + + auto range = statement.range(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, UnorderedElementsAre("bar", "foo", "poo")); +} + +TEST_F(SqliteStatement, GetSingleRangeWithTransactionWithoutArguments) +{ + ReadStatement<1> statement("SELECT name FROM test", database); + + auto range = statement.rangeWithTransaction(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, UnorderedElementsAre("bar", "foo", "poo")); +} + class FooValue { public: @@ -697,6 +828,28 @@ TEST_F(SqliteStatement, GetSingleSqliteValuesWithoutArguments) ASSERT_THAT(values, UnorderedElementsAre(Eq("blah"), Eq(23.3), Eq(40), IsNull())); } +TEST_F(SqliteStatement, GetSingleSqliteRangeWithoutArguments) +{ + ReadStatement<1> statement("SELECT number FROM test", database); + database.execute("INSERT INTO test VALUES (NULL, NULL, NULL)"); + + auto range = statement.range(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, UnorderedElementsAre(Eq("blah"), Eq(23.3), Eq(40), IsNull())); +} + +TEST_F(SqliteStatement, GetSingleSqliteRangeWithTransactionWithoutArguments) +{ + ReadStatement<1> statement("SELECT number FROM test", database); + database.execute("INSERT INTO test VALUES (NULL, NULL, NULL)"); + + auto range = statement.rangeWithTransaction(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, UnorderedElementsAre(Eq("blah"), Eq(23.3), Eq(40), IsNull())); +} + TEST_F(SqliteStatement, GetStructValuesWithoutArguments) { ReadStatement<3> statement("SELECT name, number, value FROM test", database); @@ -709,6 +862,32 @@ TEST_F(SqliteStatement, GetStructValuesWithoutArguments) Output{"poo", "40", 3})); } +TEST_F(SqliteStatement, GetStructRangeWithoutArguments) +{ + ReadStatement<3> statement("SELECT name, number, value FROM test", database); + + auto range = statement.range(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, + UnorderedElementsAre(Output{"bar", "blah", 1}, + Output{"foo", "23.3", 2}, + Output{"poo", "40", 3})); +} + +TEST_F(SqliteStatement, GetStructRangeWithTransactionWithoutArguments) +{ + ReadStatement<3> statement("SELECT name, number, value FROM test", database); + + auto range = statement.rangeWithTransaction(); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, + UnorderedElementsAre(Output{"bar", "blah", 1}, + Output{"foo", "23.3", 2}, + Output{"poo", "40", 3})); +} + TEST_F(SqliteStatement, GetValuesForSingleOutputWithBindingMultipleTimes) { ReadStatement<1> statement("SELECT name FROM test WHERE number=?", database); @@ -719,6 +898,28 @@ TEST_F(SqliteStatement, GetValuesForSingleOutputWithBindingMultipleTimes) ASSERT_THAT(values, ElementsAre("poo")); } +TEST_F(SqliteStatement, GetRangeForSingleOutputWithBindingMultipleTimes) +{ + ReadStatement<1> statement("SELECT name FROM test WHERE number=?", database); + statement.values(3, 40); + + auto range = statement.range(40); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, ElementsAre("poo")); +} + +TEST_F(SqliteStatement, GetRangeWithTransactionForSingleOutputWithBindingMultipleTimes) +{ + ReadStatement<1> statement("SELECT name FROM test WHERE number=?", database); + statement.values(3, 40); + + auto range = statement.rangeWithTransaction(40); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, ElementsAre("poo")); +} + TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndMultipleQueryValue) { using Tuple = std::tuple; @@ -730,6 +931,30 @@ TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndMultipleQueryValue) ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1})); } +TEST_F(SqliteStatement, GetRangeForMultipleOutputValuesAndMultipleQueryValue) +{ + using Tuple = std::tuple; + ReadStatement<3> statement( + "SELECT name, number, value FROM test WHERE name=? AND number=? AND value=?", database); + + auto range = statement.range("bar", "blah", 1); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1})); +} + +TEST_F(SqliteStatement, GetRangeWithTransactionForMultipleOutputValuesAndMultipleQueryValue) +{ + using Tuple = std::tuple; + ReadStatement<3> statement( + "SELECT name, number, value FROM test WHERE name=? AND number=? AND value=?", database); + + auto range = statement.rangeWithTransaction("bar", "blah", 1); + std::vector values{range.begin(), range.end()}; + + ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1})); +} + TEST_F(SqliteStatement, CallGetValuesForMultipleOutputValuesAndMultipleQueryValueMultipleTimes) { using Tuple = std::tuple; @@ -742,6 +967,39 @@ TEST_F(SqliteStatement, CallGetValuesForMultipleOutputValuesAndMultipleQueryValu ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1})); } +TEST_F(SqliteStatement, CallGetRangeForMultipleOutputValuesAndMultipleQueryValueMultipleTimes) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test WHERE name=? AND number=?", + database); + { + auto range = statement.range("bar", "blah"); + std::vector values1{range.begin(), range.end()}; + } + + auto range2 = statement.range("bar", "blah"); + std::vector values{range2.begin(), range2.end()}; + + ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1})); +} + +TEST_F(SqliteStatement, + CallGetRangeWithTransactionForMultipleOutputValuesAndMultipleQueryValueMultipleTimes) +{ + using Tuple = std::tuple; + ReadStatement<3> statement("SELECT name, number, value FROM test WHERE name=? AND number=?", + database); + { + auto range = statement.rangeWithTransaction("bar", "blah"); + std::vector values1{range.begin(), range.end()}; + } + + auto range2 = statement.rangeWithTransaction("bar", "blah"); + std::vector values{range2.begin(), range2.end()}; + + ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1})); +} + TEST_F(SqliteStatement, GetStructOutputValuesAndMultipleQueryValue) { ReadStatement<3> statement( @@ -851,6 +1109,30 @@ TEST_F(SqliteStatement, GetValuesWithoutArgumentsCallsReset) mockStatement.values(3); } +TEST_F(SqliteStatement, GetRangeWithoutArgumentsCallsReset) +{ + MockSqliteStatement mockStatement; + + EXPECT_CALL(mockStatement, reset()); + + mockStatement.range(); +} + +TEST_F(SqliteStatement, GetRangeWithTransactionWithoutArgumentsCalls) +{ + InSequence s; + SqliteDatabaseMock databaseMock; + MockSqliteStatement mockStatement{databaseMock}; + + EXPECT_CALL(databaseMock, lock()); + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(mockStatement, reset()); + EXPECT_CALL(databaseMock, commit()); + EXPECT_CALL(databaseMock, unlock()); + + mockStatement.rangeWithTransaction(); +} + TEST_F(SqliteStatement, GetValuesWithoutArgumentsCallsResetIfExceptionIsThrown) { MockSqliteStatement mockStatement; @@ -861,6 +1143,38 @@ TEST_F(SqliteStatement, GetValuesWithoutArgumentsCallsResetIfExceptionIsThrown) EXPECT_THROW(mockStatement.values(3), Sqlite::StatementHasError); } +TEST_F(SqliteStatement, GetRangeWithoutArgumentsCallsResetIfExceptionIsThrown) +{ + MockSqliteStatement mockStatement; + ON_CALL(mockStatement, next()).WillByDefault(Throw(Sqlite::StatementHasError(""))); + auto range = mockStatement.range(); + + EXPECT_CALL(mockStatement, reset()); + + EXPECT_THROW(range.begin(), Sqlite::StatementHasError); +} + +TEST_F(SqliteStatement, GetRangeWithTransactionWithoutArgumentsCallsResetIfExceptionIsThrown) +{ + InSequence s; + SqliteDatabaseMock databaseMock; + MockSqliteStatement mockStatement{databaseMock}; + ON_CALL(mockStatement, next()).WillByDefault(Throw(Sqlite::StatementHasError(""))); + + EXPECT_CALL(databaseMock, lock()); + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(mockStatement, reset()); + EXPECT_CALL(databaseMock, rollback()); + EXPECT_CALL(databaseMock, unlock()); + + EXPECT_THROW( + { + auto range = mockStatement.rangeWithTransaction(); + range.begin(); + }, + Sqlite::StatementHasError); +} + TEST_F(SqliteStatement, GetValuesWithSimpleArgumentsCallsReset) { MockSqliteStatement mockStatement;