forked from qt-creator/qt-creator
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<std::tuple<long long, std::string>>()) {
values.push_back(value);
if (value == Tuple{"foo", 23.3, 2})
break;
}
for (auto [key, value] :
statement.rangeWithTransaction<std::tuple<long long, std::string>>()) {
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 <tim.jenssen@qt.io>
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "sqliteblob.h"
|
||||
#include "sqliteexception.h"
|
||||
#include "sqlitetransaction.h"
|
||||
#include "sqlitevalue.h"
|
||||
|
||||
#include <utils/smallstringvector.h>
|
||||
@@ -37,6 +38,7 @@
|
||||
#include <utils/span.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
@@ -163,13 +165,14 @@ extern template SQLITE_EXPORT Utils::PathString BaseStatement::fetchValue<Utils:
|
||||
template<typename BaseStatement, int ResultCount>
|
||||
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<typename... ValueType>
|
||||
void write(const ValueType&... values)
|
||||
{
|
||||
Resetter resetter{*this};
|
||||
Resetter resetter{this};
|
||||
bindValuesByIndex(1, values...);
|
||||
BaseStatement::next();
|
||||
resetter.reset();
|
||||
@@ -194,7 +197,7 @@ public:
|
||||
template<typename ResultType>
|
||||
std::vector<ResultType> values(std::size_t reserveSize)
|
||||
{
|
||||
Resetter resetter{*this};
|
||||
Resetter resetter{this};
|
||||
std::vector<ResultType> resultValues;
|
||||
resultValues.reserve(std::max(reserveSize, m_maximumResultCount));
|
||||
|
||||
@@ -211,7 +214,7 @@ public:
|
||||
template<typename ResultType, typename... QueryTypes>
|
||||
auto values(std::size_t reserveSize, const QueryTypes &...queryValues)
|
||||
{
|
||||
Resetter resetter{*this};
|
||||
Resetter resetter{this};
|
||||
std::vector<ResultType> resultValues;
|
||||
resultValues.reserve(std::max(reserveSize, m_maximumResultCount));
|
||||
|
||||
@@ -230,13 +233,13 @@ public:
|
||||
template<typename ResultType, typename... QueryTypes>
|
||||
auto value(const QueryTypes &...queryValues)
|
||||
{
|
||||
Resetter resetter{*this};
|
||||
Resetter resetter{this};
|
||||
Utils::optional<ResultType> resultValue;
|
||||
|
||||
bindValues(queryValues...);
|
||||
|
||||
if (BaseStatement::next())
|
||||
resultValue = assignValue<Utils::optional<ResultType>>();
|
||||
resultValue = createOptionalValue<Utils::optional<ResultType>>();
|
||||
|
||||
resetter.reset();
|
||||
|
||||
@@ -258,7 +261,7 @@ public:
|
||||
template<typename Callable, typename... QueryTypes>
|
||||
void readCallback(Callable &&callable, const QueryTypes &...queryValues)
|
||||
{
|
||||
Resetter resetter{*this};
|
||||
Resetter resetter{this};
|
||||
|
||||
bindValues(queryValues...);
|
||||
|
||||
@@ -275,7 +278,7 @@ public:
|
||||
template<typename Container, typename... QueryTypes>
|
||||
void readTo(Container &container, const QueryTypes &...queryValues)
|
||||
{
|
||||
Resetter resetter{*this};
|
||||
Resetter resetter{this};
|
||||
|
||||
bindValues(queryValues...);
|
||||
|
||||
@@ -285,39 +288,187 @@ public:
|
||||
resetter.reset();
|
||||
}
|
||||
|
||||
template<typename ResultType, typename... QueryTypes>
|
||||
auto range(const QueryTypes &...queryValues)
|
||||
{
|
||||
return SqliteResultRange<ResultType>{*this, queryValues...};
|
||||
}
|
||||
|
||||
template<typename ResultType, typename... QueryTypes>
|
||||
auto rangeWithTransaction(const QueryTypes &...queryValues)
|
||||
{
|
||||
return SqliteResultRangeWithTransaction<ResultType>{*this, queryValues...};
|
||||
}
|
||||
|
||||
template<typename ResultType>
|
||||
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<ResultType>(); }
|
||||
|
||||
private:
|
||||
StatementImplementation &m_statement;
|
||||
bool m_hasNext = false;
|
||||
};
|
||||
|
||||
using value_type = ResultType;
|
||||
using iterator = SqliteResultIteratator;
|
||||
using const_iterator = iterator;
|
||||
|
||||
template<typename... QueryTypes>
|
||||
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<typename ResultType>
|
||||
class SqliteResultRange : public BaseSqliteResultRange<ResultType>
|
||||
{
|
||||
public:
|
||||
template<typename... QueryTypes>
|
||||
SqliteResultRange(StatementImplementation &statement, const QueryTypes &...queryValues)
|
||||
: BaseSqliteResultRange<ResultType>{statement}
|
||||
, resetter{&statement}
|
||||
|
||||
{
|
||||
statement.bindValues(queryValues...);
|
||||
}
|
||||
|
||||
~SqliteResultRange()
|
||||
{
|
||||
if (!std::uncaught_exceptions())
|
||||
resetter.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
Resetter resetter;
|
||||
};
|
||||
|
||||
template<typename ResultType>
|
||||
class SqliteResultRangeWithTransaction : public BaseSqliteResultRange<ResultType>
|
||||
{
|
||||
public:
|
||||
template<typename... QueryTypes>
|
||||
SqliteResultRangeWithTransaction(StatementImplementation &statement,
|
||||
const QueryTypes &...queryValues)
|
||||
: BaseSqliteResultRange<ResultType>{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<int, ResultCount>{});
|
||||
}
|
||||
|
||||
template <typename ResultOptionalType,
|
||||
int... ColumnIndices>
|
||||
ResultOptionalType assignValue(std::integer_sequence<int, ColumnIndices...>)
|
||||
template<typename ResultOptionalType, int... ColumnIndices>
|
||||
ResultOptionalType createOptionalValue(std::integer_sequence<int, ColumnIndices...>)
|
||||
{
|
||||
return ResultOptionalType(Utils::in_place, ValueGetter(*this, ColumnIndices)...);
|
||||
}
|
||||
|
||||
template<typename ResultOptionalType>
|
||||
ResultOptionalType assignValue()
|
||||
ResultOptionalType createOptionalValue()
|
||||
{
|
||||
return assignValue<ResultOptionalType>(std::make_integer_sequence<int, ResultCount>{});
|
||||
return createOptionalValue<ResultOptionalType>(std::make_integer_sequence<int, ResultCount>{});
|
||||
}
|
||||
|
||||
template<typename ResultType, int... ColumnIndices>
|
||||
ResultType createValue(std::integer_sequence<int, ColumnIndices...>)
|
||||
{
|
||||
return ResultType{ValueGetter(*this, ColumnIndices)...};
|
||||
}
|
||||
|
||||
template<typename ResultType>
|
||||
ResultType createValue()
|
||||
{
|
||||
return createValue<ResultType>(std::make_integer_sequence<int, ResultCount>{});
|
||||
}
|
||||
|
||||
template<typename Callable, int... ColumnIndices>
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
Base::checkColumnCount(ResultCount);
|
||||
}
|
||||
|
||||
using Base::range;
|
||||
using Base::rangeWithTransaction;
|
||||
using Base::readCallback;
|
||||
using Base::readTo;
|
||||
using Base::toValue;
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <googletest.h>
|
||||
#include "googletest.h"
|
||||
#include "sqlitedatabasemock.h"
|
||||
|
||||
#include <sqlitebasestatement.h>
|
||||
|
||||
@@ -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<int ResultCount = 1>
|
||||
class MockSqliteStatement
|
||||
: public Sqlite::StatementImplementation<NiceMock<BaseMockSqliteStatement>, ResultCount>
|
||||
{
|
||||
using Base = Sqlite::StatementImplementation<NiceMock<BaseMockSqliteStatement>, ResultCount>;
|
||||
|
||||
public:
|
||||
explicit MockSqliteStatement() {}
|
||||
explicit MockSqliteStatement(SqliteDatabaseMock &databaseMock)
|
||||
: Base{databaseMock}
|
||||
{}
|
||||
|
||||
protected:
|
||||
void checkIsWritableStatement();
|
||||
|
||||
@@ -70,4 +70,3 @@ public:
|
||||
|
||||
MOCK_METHOD(void, setAttachedTables, (const Utils::SmallStringVector &tables), (override));
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "googletest.h"
|
||||
#include "mocksqlitestatement.h"
|
||||
#include "sqlitedatabasemock.h"
|
||||
#include "sqliteteststatement.h"
|
||||
|
||||
#include <sqliteblob.h>
|
||||
@@ -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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test", database);
|
||||
|
||||
auto range = statement.range<Tuple>();
|
||||
std::vector<Tuple> 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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test", database);
|
||||
|
||||
auto range = statement.rangeWithTransaction<Tuple>();
|
||||
std::vector<Tuple> 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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test", database);
|
||||
std::vector<Tuple> values;
|
||||
|
||||
for (auto value : statement.range<Tuple>())
|
||||
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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test", database);
|
||||
std::vector<Tuple> values;
|
||||
|
||||
for (auto value : statement.rangeWithTransaction<Tuple>())
|
||||
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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database);
|
||||
std::vector<Tuple> values;
|
||||
|
||||
for (auto value : statement.range<Tuple>()) {
|
||||
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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database);
|
||||
std::vector<Tuple> values;
|
||||
|
||||
for (auto value : statement.rangeWithTransaction<Tuple>()) {
|
||||
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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database);
|
||||
std::vector<Tuple> values;
|
||||
|
||||
for (auto value : statement.range<Tuple>()) {
|
||||
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<Utils::SmallString, double, int>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test ORDER BY name", database);
|
||||
std::vector<Tuple> values;
|
||||
|
||||
for (auto value : statement.rangeWithTransaction<Tuple>()) {
|
||||
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<Utils::SmallStringView>();
|
||||
std::vector<Utils::SmallString> 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<Utils::SmallStringView>();
|
||||
std::vector<Utils::SmallString> 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<FooValue>();
|
||||
std::vector<FooValue> 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<FooValue>();
|
||||
std::vector<FooValue> 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<Output>();
|
||||
std::vector<Output> 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<Output>();
|
||||
std::vector<Output> 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<Utils::SmallString>(3, 40);
|
||||
|
||||
auto range = statement.range<Utils::SmallStringView>(40);
|
||||
std::vector<Utils::SmallString> 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<Utils::SmallString>(3, 40);
|
||||
|
||||
auto range = statement.rangeWithTransaction<Utils::SmallStringView>(40);
|
||||
std::vector<Utils::SmallString> values{range.begin(), range.end()};
|
||||
|
||||
ASSERT_THAT(values, ElementsAre("poo"));
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndMultipleQueryValue)
|
||||
{
|
||||
using Tuple = std::tuple<Utils::SmallString, Utils::SmallString, long long>;
|
||||
@@ -730,6 +931,30 @@ TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndMultipleQueryValue)
|
||||
ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1}));
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetRangeForMultipleOutputValuesAndMultipleQueryValue)
|
||||
{
|
||||
using Tuple = std::tuple<Utils::SmallString, Utils::SmallString, long long>;
|
||||
ReadStatement<3> statement(
|
||||
"SELECT name, number, value FROM test WHERE name=? AND number=? AND value=?", database);
|
||||
|
||||
auto range = statement.range<Tuple>("bar", "blah", 1);
|
||||
std::vector<Tuple> values{range.begin(), range.end()};
|
||||
|
||||
ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1}));
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetRangeWithTransactionForMultipleOutputValuesAndMultipleQueryValue)
|
||||
{
|
||||
using Tuple = std::tuple<Utils::SmallString, Utils::SmallString, long long>;
|
||||
ReadStatement<3> statement(
|
||||
"SELECT name, number, value FROM test WHERE name=? AND number=? AND value=?", database);
|
||||
|
||||
auto range = statement.rangeWithTransaction<Tuple>("bar", "blah", 1);
|
||||
std::vector<Tuple> values{range.begin(), range.end()};
|
||||
|
||||
ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1}));
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, CallGetValuesForMultipleOutputValuesAndMultipleQueryValueMultipleTimes)
|
||||
{
|
||||
using Tuple = std::tuple<Utils::SmallString, Utils::SmallString, long long>;
|
||||
@@ -742,6 +967,39 @@ TEST_F(SqliteStatement, CallGetValuesForMultipleOutputValuesAndMultipleQueryValu
|
||||
ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1}));
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, CallGetRangeForMultipleOutputValuesAndMultipleQueryValueMultipleTimes)
|
||||
{
|
||||
using Tuple = std::tuple<Utils::SmallString, Utils::SmallString, long long>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test WHERE name=? AND number=?",
|
||||
database);
|
||||
{
|
||||
auto range = statement.range<Tuple>("bar", "blah");
|
||||
std::vector<Tuple> values1{range.begin(), range.end()};
|
||||
}
|
||||
|
||||
auto range2 = statement.range<Tuple>("bar", "blah");
|
||||
std::vector<Tuple> values{range2.begin(), range2.end()};
|
||||
|
||||
ASSERT_THAT(values, ElementsAre(Tuple{"bar", "blah", 1}));
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement,
|
||||
CallGetRangeWithTransactionForMultipleOutputValuesAndMultipleQueryValueMultipleTimes)
|
||||
{
|
||||
using Tuple = std::tuple<Utils::SmallString, Utils::SmallString, long long>;
|
||||
ReadStatement<3> statement("SELECT name, number, value FROM test WHERE name=? AND number=?",
|
||||
database);
|
||||
{
|
||||
auto range = statement.rangeWithTransaction<Tuple>("bar", "blah");
|
||||
std::vector<Tuple> values1{range.begin(), range.end()};
|
||||
}
|
||||
|
||||
auto range2 = statement.rangeWithTransaction<Tuple>("bar", "blah");
|
||||
std::vector<Tuple> 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<int>(3);
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetRangeWithoutArgumentsCallsReset)
|
||||
{
|
||||
MockSqliteStatement mockStatement;
|
||||
|
||||
EXPECT_CALL(mockStatement, reset());
|
||||
|
||||
mockStatement.range<int>();
|
||||
}
|
||||
|
||||
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<int>();
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetValuesWithoutArgumentsCallsResetIfExceptionIsThrown)
|
||||
{
|
||||
MockSqliteStatement mockStatement;
|
||||
@@ -861,6 +1143,38 @@ TEST_F(SqliteStatement, GetValuesWithoutArgumentsCallsResetIfExceptionIsThrown)
|
||||
EXPECT_THROW(mockStatement.values<int>(3), Sqlite::StatementHasError);
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetRangeWithoutArgumentsCallsResetIfExceptionIsThrown)
|
||||
{
|
||||
MockSqliteStatement mockStatement;
|
||||
ON_CALL(mockStatement, next()).WillByDefault(Throw(Sqlite::StatementHasError("")));
|
||||
auto range = mockStatement.range<int>();
|
||||
|
||||
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<int>();
|
||||
range.begin();
|
||||
},
|
||||
Sqlite::StatementHasError);
|
||||
}
|
||||
|
||||
TEST_F(SqliteStatement, GetValuesWithSimpleArgumentsCallsReset)
|
||||
{
|
||||
MockSqliteStatement mockStatement;
|
||||
|
||||
Reference in New Issue
Block a user