Skip to content

Commit

Permalink
Support re-traversing query results with cursor::begin().
Browse files Browse the repository at this point in the history
Benefit:

```
auto csr = db.execute(query);
for (auto const &row: csr) {}
for (auto const &row: csr) {} // reuse prepared sqlite3_stmt
```

* Use shared/weak_ptr<void> for monitering traverse session
* Prepared sqlite3_stmt is reused
* row_iter will be invalidate after starting new traverse
  • Loading branch information
yangacer committed Jun 19, 2019
1 parent 8064964 commit bd105f1
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 13 deletions.
15 changes: 14 additions & 1 deletion gtest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ TEST_F(DBTest, row_iter) {
using namespace sqlite3cpp;

auto c = basic_dataset().make_cursor();
EXPECT_EQ(c.begin(), c.end());

c.executescript("create table Empty (a);");
c.execute("select * from Empty");
Expand All @@ -207,7 +208,19 @@ TEST_F(DBTest, row_iter) {

EXPECT_NE(c.begin(), c.end());
EXPECT_EQ(++c.begin(), c.end());
EXPECT_EQ(c.begin(), c.end());
EXPECT_NE(c.begin(), c.end());

{ // iterator invalidation
auto beg1 = c.begin();
auto beg2 = beg1;
auto beg3 = c.begin(); // call begin again to invalid other iterators
EXPECT_EQ(beg1, c.end());
EXPECT_EQ(++beg1, c.end()) << "invalid row_iter can't advance";
EXPECT_EQ(beg2, c.end());
EXPECT_TRUE(beg3.is_valid());
EXPECT_FALSE(beg1.is_valid());
EXPECT_FALSE(beg2.is_valid());
}
}

TEST_F(DBTest, bind_null) {
Expand Down
39 changes: 29 additions & 10 deletions sqlite3cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@

namespace sqlite3cpp {

namespace detail {
// An tag type for row iter session
struct session {};
} // namespace detail

/**
* row_iter impl
*/
row_iter::row_iter(cursor &csr) noexcept : m_csr(&csr) {
if (!m_csr->get())
row_iter::row_iter(cursor &csr, std::weak_ptr<void> session) noexcept
: m_csr(&csr), m_session(std::move(session)) {
if (m_session.expired())
m_csr = nullptr;
else {
m_row.m_stmt = m_csr->get();
Expand All @@ -55,8 +61,9 @@ row_iter::row_iter(cursor &csr) noexcept : m_csr(&csr) {
}

row_iter &row_iter::operator++() {
m_csr->step();
if (!m_csr->get())
if (!m_session.expired())
m_csr->step();
if (m_session.expired())
m_csr = nullptr;
return *this;
}
Expand All @@ -66,13 +73,14 @@ row const &row_iter::operator*() const noexcept { return m_row; }
row const *row_iter::operator->() const noexcept { return &m_row; }

bool row_iter::operator==(row_iter const &i) const noexcept {
return m_csr == i.m_csr;
return m_session.lock() == i.m_session.lock();
}

bool row_iter::operator!=(row_iter const &i) const noexcept {
return !(*this == i);
}

bool row_iter::is_valid() const noexcept { return !m_session.expired(); }
/**
* cursor impl
*/
Expand All @@ -91,7 +99,7 @@ void cursor::step() {

switch (ec) {
case SQLITE_DONE:
m_stmt.reset();
m_session.reset();
break;
case SQLITE_ROW:
break;
Expand All @@ -101,10 +109,21 @@ void cursor::step() {
}

row_iter cursor::begin() noexcept {
return row_iter(*this);
}

row_iter cursor::end() noexcept { return row_iter(); }
if (!m_stmt)
return {};
// NOTE(acer): There is actually a redundant reset as we invoke
// |execute().begin()|. It's possible to be eliminated, though I keep it for
// ensuring non-query SQL can be executed right away after calling |execute()|.
// Besides, results of previous |step()| should be cached by sqlite3 s.t.
// performance penality would be minor.
m_session.reset((void *)new detail::session,
[](void *s) { delete (detail::session *)s; });
sqlite3_reset(m_stmt.get());
step();
return row_iter(*this, m_session);
}

row_iter cursor::end() noexcept { return {}; }
/**
* transaction impl
*/
Expand Down
6 changes: 4 additions & 2 deletions sqlite3cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,14 @@ struct SQLITE3CPP_EXPORT row_iter {
bool operator!=(row_iter const &i) const noexcept;
row const &operator*() const noexcept;
row const *operator->() const noexcept;

bool is_valid() const noexcept;
private:
friend struct cursor;
row_iter() noexcept {}
row_iter(cursor &csr) noexcept;
row_iter(cursor &csr, std::weak_ptr<void> session) noexcept;
cursor *m_csr = nullptr;
row m_row;
std::weak_ptr<void> m_session;
};

struct SQLITE3CPP_EXPORT cursor {
Expand Down Expand Up @@ -168,6 +169,7 @@ struct SQLITE3CPP_EXPORT cursor {
cursor(database const &db) noexcept;
sqlite3 *m_db;
std::unique_ptr<sqlite3_stmt, sqlite3_stmt_deleter> m_stmt;
std::shared_ptr<void> m_session;
};

struct SQLITE3CPP_EXPORT transaction {
Expand Down
2 changes: 2 additions & 0 deletions sqlite3cpp.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ std::tuple<Cols...> row::to() const {
*/
template <typename... Args>
cursor &cursor::execute(std::string const &sql, Args &&... args) {
// TODO: Support rebind params (which means step() error for not binded params
// should be ignored.
sqlite3_stmt *stmt = 0;
int ec = 0;

Expand Down

0 comments on commit bd105f1

Please sign in to comment.