-
Notifications
You must be signed in to change notification settings - Fork 38.7k
wallet, sqlite: Encapsulate SQLite statements in a RAII class #33033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33033. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. LLM Linter (✨ experimental)Possible places where named args for integral literals may be used (e.g.
2026-01-03 |
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
e1bf41a to
2c1edcc
Compare
2c1edcc to
b2c824a
Compare
w0xlt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me
ACK b2c824a
a8cf396 to
1829b35
Compare
rkrux
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Started reviewing; I'm not opposed to this introduction at the moment and I will get more clarity soon in the review.
Initial Concept ACK 1829b3512d7bf430ec44daa801eb8fadf46cd30b
These 3 commits from #33034 looks pretty generic and related enough. Can they be included in this PR?
- 818989093d9e6b8ee765c21630f7342b99af7fa0 "sqlite: Make SQLiteStatement::Bind a template function"
- 07cac74e1340c7e771850633dd9418ff3efa5bb7 "sqlite: Make SQLiteDatabase::Column an std::optional"
- ff47eea527e58071bdaff5c2acbc98ee97294021 "sqlite: Add additional blob types to SQLiteStatement::Column"
vasild
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach ACK 1829b3512d7bf430ec44daa801eb8fadf46cd30b
src/wallet/sqlite.cpp
Outdated
| int res = sqlite3_prepare_v2(m_db, stmt_text.c_str(), -1, &m_stmt, nullptr); | ||
| if (res != SQLITE_OK) { | ||
| throw std::runtime_error(strprintf( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an observation:
Some of the callers of sqlite3_prepare_v2() call sqlite3_finalize() on failure. The doc says that if prepare fails then *ppStmt is set to NULL and Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op.
So it is ok to omit the finalize call.
| ~SQLiteStatement() | ||
| { | ||
| Reset(); | ||
| sqlite3_finalize(m_stmt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the callers of sqlite3_finalize() used to check its return value. Here in the destructor it is too late to signal an error to the caller.
From: https://sqlite.org/c3ref/finalize.html
If the most recent evaluation of statement S failed, then sqlite3_finalize(S) returns the appropriate error code or extended error code
I am not sure what is the best way to handle this. Ignoring the return value does not look good.
Maybe assert that sqlite3_finalize() returns SQLITE_OK? And also somehow make sure that if "the most recent evaluation of statement S failed", then this is handled already, before the destructor is called. That would mean if sqlite3_step(), called from Step() fails (result is not SQLITE_ROW or SQLITE_DONE) then to call sqlite3_finalize() from inside Step()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe (not tested):
int Step()
{
const int ret = sqlite3_step(m_stmt);
if (ret == SQLITE_ROW || ret == SQLITE_DONE) {
return ret;
}
sqlite3_finalize(m_stmt);
m_stmt = nullptr;
// then the destructor can assert(sqlite3_finalize(m_stmt) == SQLITE_OK);
}Related to #33033 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the most recent evaluation of statement S failed, then sqlite3_finalize(S) returns the appropriate error code or extended error code
I am not sure what is the best way to handle this. Ignoring the return value does not look good.
This text reads to me as sqlite3_finalize propagating any previous errors, e.g. from sqlite3_step. Since any errors from that should already be handled, I think it's ok to ignore the result of finalize. I think the previous code is actually incorrect since the error result is not actually a result of finalize failing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was thinking the same. It is just super odd to ignore database errors (in general). I guess the sqlite API is odd here.
Edit: if you retouch, maybe change int Step() to [[nodiscard]] int Step() to hint the callers to not ignore the return value.
1829b35 to
0af7c2a
Compare
vasild
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 0af7c2a11819a21e11abf34b4229c5c21150d827
0af7c2a to
637770c
Compare
637770c to
71b7c84
Compare
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
71b7c84 to
c5a5d06
Compare
This class will be used to encapsulate a sqlite3_stmt
WriteKey and ExecStatement use the same code for the actual execution of the statement. This is refactored into a separate function, also called ExecStatement, and the original ExecStatement renamed to ExecEraseStatement as it is only used by the erase functions.
0fcc63f to
d283190
Compare
Instead of constructing all SQLiteStatements when SQLiteBatch is constructed, construct them once when they are needed before each read, write, or erase operation. Once constructed, the statement will persist for the lifetime of the SQLiteBatch to be reused across multiple statements.
d283190 to
1682818
Compare
SQLite statements are C objects which can be long lived, so having them be initialized in a constructor, and cleanup everything in a destructor, makes the sqlite code cleaner and easier to read. This also lets us have the various functions needed to interact with sqlite statements be member functions.
This will be particularly useful in future work which makes use of more complicated SQL statements.