Skip to content

Commit dca874e

Browse files
committed
sqlite: add ability to interrupt statements
By encapsulating sqlite3_exec into its own standalone method and introducing the 'SQliteExecHandler' class, we enable the ability to test db statements execution failures within the unit test framework. This is used in the following-up commit to exercise a deadlock and improve our wallet db error handling code. Moreover, the future encapsulation of other sqlite functions within this class will contribute to minimize the impact of any future API changes.
1 parent fdf9f66 commit dca874e

File tree

2 files changed

+20
-3
lines changed

2 files changed

+20
-3
lines changed

src/wallet/sqlite.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ void SQLiteDatabase::Close()
377377
m_db = nullptr;
378378
}
379379

380+
int SQliteExecHandler::Exec(SQLiteDatabase& database, const std::string& statement)
381+
{
382+
return sqlite3_exec(database.m_db, statement.data(), nullptr, nullptr, nullptr);
383+
}
384+
380385
std::unique_ptr<DatabaseBatch> SQLiteDatabase::MakeBatch(bool flush_on_close)
381386
{
382387
// We ignore flush_on_close because we don't do manual flushing for SQLite
@@ -607,7 +612,7 @@ std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewPrefixCursor(Span<const std::
607612
bool SQLiteBatch::TxnBegin()
608613
{
609614
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) == 0) return false;
610-
int res = sqlite3_exec(m_database.m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
615+
int res = Assert(m_exec_handler)->Exec(m_database, "BEGIN TRANSACTION");
611616
if (res != SQLITE_OK) {
612617
LogPrintf("SQLiteBatch: Failed to begin the transaction\n");
613618
}
@@ -617,7 +622,7 @@ bool SQLiteBatch::TxnBegin()
617622
bool SQLiteBatch::TxnCommit()
618623
{
619624
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) != 0) return false;
620-
int res = sqlite3_exec(m_database.m_db, "COMMIT TRANSACTION", nullptr, nullptr, nullptr);
625+
int res = Assert(m_exec_handler)->Exec(m_database, "COMMIT TRANSACTION");
621626
if (res != SQLITE_OK) {
622627
LogPrintf("SQLiteBatch: Failed to commit the transaction\n");
623628
}
@@ -627,7 +632,7 @@ bool SQLiteBatch::TxnCommit()
627632
bool SQLiteBatch::TxnAbort()
628633
{
629634
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) != 0) return false;
630-
int res = sqlite3_exec(m_database.m_db, "ROLLBACK TRANSACTION", nullptr, nullptr, nullptr);
635+
int res = Assert(m_exec_handler)->Exec(m_database, "ROLLBACK TRANSACTION");
631636
if (res != SQLITE_OK) {
632637
LogPrintf("SQLiteBatch: Failed to abort the transaction\n");
633638
}

src/wallet/sqlite.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,21 @@ class SQLiteCursor : public DatabaseCursor
3636
Status Next(DataStream& key, DataStream& value) override;
3737
};
3838

39+
/** Class responsible for executing SQL statements in SQLite databases.
40+
* Methods are virtual so they can be overridden by unit tests testing unusual database conditions. */
41+
class SQliteExecHandler
42+
{
43+
public:
44+
virtual ~SQliteExecHandler() {}
45+
virtual int Exec(SQLiteDatabase& database, const std::string& statement);
46+
};
47+
3948
/** RAII class that provides access to a WalletDatabase */
4049
class SQLiteBatch : public DatabaseBatch
4150
{
4251
private:
4352
SQLiteDatabase& m_database;
53+
std::unique_ptr<SQliteExecHandler> m_exec_handler{std::make_unique<SQliteExecHandler>()};
4454

4555
sqlite3_stmt* m_read_stmt{nullptr};
4656
sqlite3_stmt* m_insert_stmt{nullptr};
@@ -61,6 +71,8 @@ class SQLiteBatch : public DatabaseBatch
6171
explicit SQLiteBatch(SQLiteDatabase& database);
6272
~SQLiteBatch() override { Close(); }
6373

74+
void SetExecHandler(std::unique_ptr<SQliteExecHandler>&& handler) { m_exec_handler = std::move(handler); }
75+
6476
/* No-op. See comment on SQLiteDatabase::Flush */
6577
void Flush() override {}
6678

0 commit comments

Comments
 (0)