wallet: Add GetPrefixCursor to DatabaseBatch

In order to get records beginning with a prefix, we will need a cursor
specifically for that prefix. So add a GetPrefixCursor function and
DatabaseCursor classes for dealing with those prefixes.

Tested on each supported db engine.

1) Write two different key->value elements to db.
2) Create a new prefix cursor and walk-through every returned element,
   verifying that it gets parsed properly.
3) Try to move the cursor outside the filtered range: expect failure
   and flag complete=true.

Co-Authored-By: Ryan Ofsky <ryan@ofsky.org>
Co-Authored-By: furszy <matiasfurszyfer@protonmail.com>
This commit is contained in:
Andrew Chow
2022-04-11 17:11:37 -04:00
committed by Andrew Chow
parent 1d858b055d
commit ba616b932c
10 changed files with 239 additions and 15 deletions

View File

@@ -9,6 +9,7 @@
#include <logging.h>
#include <sync.h>
#include <util/fs_helpers.h>
#include <util/check.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <wallet/db.h>
@@ -515,6 +516,7 @@ DatabaseCursor::Status SQLiteCursor::Next(DataStream& key, DataStream& value)
SQLiteCursor::~SQLiteCursor()
{
sqlite3_clear_bindings(m_cursor_stmt);
sqlite3_reset(m_cursor_stmt);
int res = sqlite3_finalize(m_cursor_stmt);
if (res != SQLITE_OK) {
@@ -538,6 +540,48 @@ std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewCursor()
return cursor;
}
std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewPrefixCursor(Span<const std::byte> prefix)
{
if (!m_database.m_db) return nullptr;
// To get just the records we want, the SQL statement does a comparison of the binary data
// where the data must be greater than or equal to the prefix, and less than
// the prefix incremented by one (when interpreted as an integer)
std::vector<std::byte> start_range(prefix.begin(), prefix.end());
std::vector<std::byte> end_range(prefix.begin(), prefix.end());
auto it = end_range.rbegin();
for (; it != end_range.rend(); ++it) {
if (*it == std::byte(std::numeric_limits<unsigned char>::max())) {
*it = std::byte(0);
continue;
}
*it = std::byte(std::to_integer<unsigned char>(*it) + 1);
break;
}
if (it == end_range.rend()) {
// If the prefix is all 0xff bytes, clear end_range as we won't need it
end_range.clear();
}
auto cursor = std::make_unique<SQLiteCursor>(start_range, end_range);
if (!cursor) return nullptr;
const char* stmt_text = end_range.empty() ? "SELECT key, value FROM main WHERE key >= ?" :
"SELECT key, value FROM main WHERE key >= ? AND key < ?";
int res = sqlite3_prepare_v2(m_database.m_db, stmt_text, -1, &cursor->m_cursor_stmt, nullptr);
if (res != SQLITE_OK) {
throw std::runtime_error(strprintf(
"SQLiteDatabase: Failed to setup cursor SQL statement: %s\n", sqlite3_errstr(res)));
}
if (!BindBlobToStatement(cursor->m_cursor_stmt, 1, cursor->m_prefix_range_start, "prefix_start")) return nullptr;
if (!end_range.empty()) {
if (!BindBlobToStatement(cursor->m_cursor_stmt, 2, cursor->m_prefix_range_end, "prefix_end")) return nullptr;
}
return cursor;
}
bool SQLiteBatch::TxnBegin()
{
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) == 0) return false;