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;