0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-03 09:56:38 -05:00

wallet: Introduce DatabaseCursor RAII class for managing cursor

Instead of having DatabaseBatch deal with opening and closing database
cursors, have a separate RAII class that deals with those.

For now, DatabaseBatch manages DatabaseCursor, but this will change
later.
This commit is contained in:
Andrew Chow 2022-04-11 15:14:24 -04:00 committed by Andrew Chow
parent 69efbc011b
commit 7a198bba0a
6 changed files with 108 additions and 44 deletions

View file

@ -8,6 +8,7 @@
#include <wallet/bdb.h> #include <wallet/bdb.h>
#include <wallet/db.h> #include <wallet/db.h>
#include <util/check.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <util/translation.h> #include <util/translation.h>
@ -307,7 +308,7 @@ BerkeleyDatabase::~BerkeleyDatabase()
} }
} }
BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr), m_cursor(nullptr), m_database(database) BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr), m_database(database)
{ {
database.AddRef(); database.AddRef();
database.Open(); database.Open();
@ -656,16 +657,18 @@ void BerkeleyDatabase::ReloadDbEnv()
env->ReloadDbEnv(); env->ReloadDbEnv();
} }
bool BerkeleyBatch::StartCursor() BerkeleyCursor::BerkeleyCursor(BerkeleyDatabase& database)
{ {
assert(!m_cursor); if (!database.m_db.get()) {
if (!pdb) throw std::runtime_error(STR_INTERNAL_BUG("BerkeleyDatabase does not exist"));
return false; }
int ret = pdb->cursor(nullptr, &m_cursor, 0); int ret = database.m_db->cursor(nullptr, &m_cursor, 0);
return ret == 0; if (ret != 0) {
throw std::runtime_error(STR_INTERNAL_BUG(strprintf("BDB Cursor could not be created. Returned %d", ret).c_str()));
}
} }
bool BerkeleyBatch::ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) bool BerkeleyCursor::Next(CDataStream& ssKey, CDataStream& ssValue, bool& complete)
{ {
complete = false; complete = false;
if (m_cursor == nullptr) return false; if (m_cursor == nullptr) return false;
@ -691,13 +694,19 @@ bool BerkeleyBatch::ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool&
return true; return true;
} }
void BerkeleyBatch::CloseCursor() BerkeleyCursor::~BerkeleyCursor()
{ {
if (!m_cursor) return; if (!m_cursor) return;
m_cursor->close(); m_cursor->close();
m_cursor = nullptr; m_cursor = nullptr;
} }
std::unique_ptr<DatabaseCursor> BerkeleyBatch::GetNewCursor()
{
if (!pdb) return nullptr;
return std::make_unique<BerkeleyCursor>(m_database);
}
bool BerkeleyBatch::TxnBegin() bool BerkeleyBatch::TxnBegin()
{ {
if (!pdb || activeTxn) if (!pdb || activeTxn)

View file

@ -185,6 +185,18 @@ public:
operator Dbt*(); operator Dbt*();
}; };
class BerkeleyCursor : public DatabaseCursor
{
private:
Dbc* m_cursor;
public:
explicit BerkeleyCursor(BerkeleyDatabase& database);
~BerkeleyCursor() override;
bool Next(CDataStream& key, CDataStream& value, bool& complete) override;
};
/** RAII class that provides access to a Berkeley database */ /** RAII class that provides access to a Berkeley database */
class BerkeleyBatch : public DatabaseBatch class BerkeleyBatch : public DatabaseBatch
{ {
@ -198,7 +210,6 @@ protected:
Db* pdb; Db* pdb;
std::string strFile; std::string strFile;
DbTxn* activeTxn; DbTxn* activeTxn;
Dbc* m_cursor;
bool fReadOnly; bool fReadOnly;
bool fFlushOnClose; bool fFlushOnClose;
BerkeleyEnvironment *env; BerkeleyEnvironment *env;
@ -214,9 +225,7 @@ public:
void Flush() override; void Flush() override;
void Close() override; void Close() override;
bool StartCursor() override; std::unique_ptr<DatabaseCursor> GetNewCursor() override;
bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override;
void CloseCursor() override;
bool TxnBegin() override; bool TxnBegin() override;
bool TxnCommit() override; bool TxnCommit() override;
bool TxnAbort() override; bool TxnAbort() override;

View file

@ -22,10 +22,24 @@ struct bilingual_str;
namespace wallet { namespace wallet {
void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename); void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename);
class DatabaseCursor
{
public:
explicit DatabaseCursor() {}
virtual ~DatabaseCursor() {}
DatabaseCursor(const DatabaseCursor&) = delete;
DatabaseCursor& operator=(const DatabaseCursor&) = delete;
virtual bool Next(CDataStream& key, CDataStream& value, bool& complete) { return false; }
};
/** RAII class that provides access to a WalletDatabase */ /** RAII class that provides access to a WalletDatabase */
class DatabaseBatch class DatabaseBatch
{ {
private: private:
std::unique_ptr<DatabaseCursor> m_cursor;
virtual bool ReadKey(CDataStream&& key, CDataStream& value) = 0; virtual bool ReadKey(CDataStream&& key, CDataStream& value) = 0;
virtual bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) = 0; virtual bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) = 0;
virtual bool EraseKey(CDataStream&& key) = 0; virtual bool EraseKey(CDataStream&& key) = 0;
@ -92,9 +106,21 @@ public:
return HasKey(std::move(ssKey)); return HasKey(std::move(ssKey));
} }
virtual bool StartCursor() = 0; virtual std::unique_ptr<DatabaseCursor> GetNewCursor() = 0;
virtual bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) = 0; bool StartCursor()
virtual void CloseCursor() = 0; {
m_cursor = GetNewCursor();
return m_cursor != nullptr;
}
bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete)
{
if (!m_cursor) return false;
return m_cursor->Next(ssKey, ssValue, complete);
}
void CloseCursor()
{
m_cursor.reset();
}
virtual bool TxnBegin() = 0; virtual bool TxnBegin() = 0;
virtual bool TxnCommit() = 0; virtual bool TxnCommit() = 0;
virtual bool TxnAbort() = 0; virtual bool TxnAbort() = 0;
@ -156,6 +182,11 @@ public:
virtual std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) = 0; virtual std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) = 0;
}; };
class DummyCursor : public DatabaseCursor
{
bool Next(CDataStream& key, CDataStream& value, bool& complete) override { return false; }
};
/** RAII class that provides access to a DummyDatabase. Never fails. */ /** RAII class that provides access to a DummyDatabase. Never fails. */
class DummyBatch : public DatabaseBatch class DummyBatch : public DatabaseBatch
{ {
@ -169,9 +200,7 @@ public:
void Flush() override {} void Flush() override {}
void Close() override {} void Close() override {}
bool StartCursor() override { return true; } std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<DummyCursor>(); }
bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return true; }
void CloseCursor() override {}
bool TxnBegin() override { return true; } bool TxnBegin() override { return true; }
bool TxnCommit() override { return true; } bool TxnCommit() override { return true; }
bool TxnAbort() override { return true; } bool TxnAbort() override { return true; }

View file

@ -125,7 +125,6 @@ void SQLiteBatch::SetupSQLStatements()
{&m_insert_stmt, "INSERT INTO main VALUES(?, ?)"}, {&m_insert_stmt, "INSERT INTO main VALUES(?, ?)"},
{&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"}, {&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"},
{&m_delete_stmt, "DELETE FROM main WHERE key = ?"}, {&m_delete_stmt, "DELETE FROM main WHERE key = ?"},
{&m_cursor_stmt, "SELECT key, value FROM main"},
}; };
for (const auto& [stmt_prepared, stmt_text] : statements) { for (const auto& [stmt_prepared, stmt_text] : statements) {
@ -374,7 +373,6 @@ void SQLiteBatch::Close()
{&m_insert_stmt, "insert"}, {&m_insert_stmt, "insert"},
{&m_overwrite_stmt, "overwrite"}, {&m_overwrite_stmt, "overwrite"},
{&m_delete_stmt, "delete"}, {&m_delete_stmt, "delete"},
{&m_cursor_stmt, "cursor"},
}; };
for (const auto& [stmt_prepared, stmt_description] : statements) { for (const auto& [stmt_prepared, stmt_description] : statements) {
@ -472,27 +470,17 @@ bool SQLiteBatch::HasKey(CDataStream&& key)
return res == SQLITE_ROW; return res == SQLITE_ROW;
} }
bool SQLiteBatch::StartCursor() bool SQLiteCursor::Next(CDataStream& key, CDataStream& value, bool& complete)
{
assert(!m_cursor_init);
if (!m_database.m_db) return false;
m_cursor_init = true;
return true;
}
bool SQLiteBatch::ReadAtCursor(CDataStream& key, CDataStream& value, bool& complete)
{ {
complete = false; complete = false;
if (!m_cursor_init) return false;
int res = sqlite3_step(m_cursor_stmt); int res = sqlite3_step(m_cursor_stmt);
if (res == SQLITE_DONE) { if (res == SQLITE_DONE) {
complete = true; complete = true;
return true; return true;
} }
if (res != SQLITE_ROW) { if (res != SQLITE_ROW) {
LogPrintf("SQLiteBatch::ReadAtCursor: Unable to execute cursor step: %s\n", sqlite3_errstr(res)); LogPrintf("%s: Unable to execute cursor step: %s\n", __func__, sqlite3_errstr(res));
return false; return false;
} }
@ -506,10 +494,29 @@ bool SQLiteBatch::ReadAtCursor(CDataStream& key, CDataStream& value, bool& compl
return true; return true;
} }
void SQLiteBatch::CloseCursor() SQLiteCursor::~SQLiteCursor()
{ {
sqlite3_reset(m_cursor_stmt); sqlite3_reset(m_cursor_stmt);
m_cursor_init = false; int res = sqlite3_finalize(m_cursor_stmt);
if (res != SQLITE_OK) {
LogPrintf("%s: cursor closed but could not finalize cursor statement: %s\n",
__func__, sqlite3_errstr(res));
}
}
std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewCursor()
{
if (!m_database.m_db) return nullptr;
auto cursor = std::make_unique<SQLiteCursor>();
const char* stmt_text = "SELECT key, value FROM main";
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(
"%s: Failed to setup cursor SQL statement: %s\n", __func__, sqlite3_errstr(res)));
}
return cursor;
} }
bool SQLiteBatch::TxnBegin() bool SQLiteBatch::TxnBegin()

View file

@ -14,19 +14,27 @@ struct bilingual_str;
namespace wallet { namespace wallet {
class SQLiteDatabase; class SQLiteDatabase;
class SQLiteCursor : public DatabaseCursor
{
public:
sqlite3_stmt* m_cursor_stmt{nullptr};
explicit SQLiteCursor() {}
~SQLiteCursor() override;
bool Next(CDataStream& key, CDataStream& value, bool& complete) override;
};
/** RAII class that provides access to a WalletDatabase */ /** RAII class that provides access to a WalletDatabase */
class SQLiteBatch : public DatabaseBatch class SQLiteBatch : public DatabaseBatch
{ {
private: private:
SQLiteDatabase& m_database; SQLiteDatabase& m_database;
bool m_cursor_init = false;
sqlite3_stmt* m_read_stmt{nullptr}; sqlite3_stmt* m_read_stmt{nullptr};
sqlite3_stmt* m_insert_stmt{nullptr}; sqlite3_stmt* m_insert_stmt{nullptr};
sqlite3_stmt* m_overwrite_stmt{nullptr}; sqlite3_stmt* m_overwrite_stmt{nullptr};
sqlite3_stmt* m_delete_stmt{nullptr}; sqlite3_stmt* m_delete_stmt{nullptr};
sqlite3_stmt* m_cursor_stmt{nullptr};
void SetupSQLStatements(); void SetupSQLStatements();
@ -44,9 +52,7 @@ public:
void Close() override; void Close() override;
bool StartCursor() override; std::unique_ptr<DatabaseCursor> GetNewCursor() override;
bool ReadAtCursor(CDataStream& key, CDataStream& value, bool& complete) override;
void CloseCursor() override;
bool TxnBegin() override; bool TxnBegin() override;
bool TxnCommit() override; bool TxnCommit() override;
bool TxnAbort() override; bool TxnAbort() override;

View file

@ -867,6 +867,12 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
TestUnloadWallet(std::move(wallet)); TestUnloadWallet(std::move(wallet));
} }
class FailCursor : public DatabaseCursor
{
public:
bool Next(CDataStream& key, CDataStream& value, bool& complete) override { return false; }
};
/** RAII class that provides access to a FailDatabase. Which fails if needed. */ /** RAII class that provides access to a FailDatabase. Which fails if needed. */
class FailBatch : public DatabaseBatch class FailBatch : public DatabaseBatch
{ {
@ -882,9 +888,7 @@ public:
void Flush() override {} void Flush() override {}
void Close() override {} void Close() override {}
bool StartCursor() override { return true; } std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<FailCursor>(); }
bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return false; }
void CloseCursor() override {}
bool TxnBegin() override { return false; } bool TxnBegin() override { return false; }
bool TxnCommit() override { return false; } bool TxnCommit() override { return false; }
bool TxnAbort() override { return false; } bool TxnAbort() override { return false; }