-
-
Notifications
You must be signed in to change notification settings - Fork 36.4k
sqlite: check sqlite3_step() and sqlite3_reset() results #63319
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: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,6 +88,17 @@ inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate, | |
| } \ | ||
| } while (0) | ||
|
|
||
| #define RESET_OR_THROW(isolate, db, stmt, ret) \ | ||
| CHECK_ERROR_OR_THROW((isolate), (db), sqlite3_reset((stmt)), SQLITE_OK, (ret)) | ||
|
|
||
| // Surface deferred SQLite errors that sqlite3_reset() returns from the prior | ||
| // sqlite3_step(). Disables the safety-net reset guard via |needs_reset|. | ||
| #define RESET_AND_CHECK(isolate, db, stmt, needs_reset, ret) \ | ||
| do { \ | ||
| (needs_reset) = false; \ | ||
| RESET_OR_THROW((isolate), (db), (stmt), (ret)); \ | ||
| } while (0) | ||
|
|
||
| #define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \ | ||
| do { \ | ||
| if ((condition)) { \ | ||
|
|
@@ -2989,9 +3000,20 @@ MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env, | |
| bool use_big_ints) { | ||
| Isolate* isolate = env->isolate(); | ||
| EscapableHandleScope scope(isolate); | ||
| sqlite3_step(stmt); | ||
| int r = sqlite3_reset(stmt); | ||
| CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, MaybeLocal<Object>()); | ||
| bool needs_reset = true; | ||
| auto reset = OnScopeLeave([&]() { | ||
| if (needs_reset) sqlite3_reset(stmt); | ||
| }); | ||
|
|
||
| int step_r = sqlite3_step(stmt); | ||
| // SQLITE_ROW is accepted here (and discarded) so that run() can still be | ||
| // used on RETURNING/SELECT statements, matching prior behavior of | ||
| // ignoring the step result entirely. | ||
| if (step_r != SQLITE_DONE && step_r != SQLITE_ROW) { | ||
| THROW_ERR_SQLITE_ERROR(isolate, db); | ||
| return MaybeLocal<Object>(); | ||
| } | ||
| RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Object>()); | ||
|
|
||
| sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection()); | ||
| sqlite3_int64 changes = sqlite3_changes64(db->Connection()); | ||
|
|
@@ -3065,18 +3087,25 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, | |
| bool use_big_ints) { | ||
| Isolate* isolate = env->isolate(); | ||
| EscapableHandleScope scope(isolate); | ||
| auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt); }); | ||
| bool needs_reset = true; | ||
| auto reset = OnScopeLeave([&]() { | ||
| if (needs_reset) sqlite3_reset(stmt); | ||
| }); | ||
|
|
||
| int r = sqlite3_step(stmt); | ||
| if (r == SQLITE_DONE) return scope.Escape(Undefined(isolate)); | ||
| if (r == SQLITE_DONE) { | ||
| RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>()); | ||
| return scope.Escape(Undefined(isolate)); | ||
| } | ||
| if (r != SQLITE_ROW) { | ||
| THROW_ERR_SQLITE_ERROR(isolate, db); | ||
| return MaybeLocal<Value>(); | ||
| } | ||
|
|
||
| int num_cols = sqlite3_column_count(stmt); | ||
| if (num_cols == 0) { | ||
| return Undefined(isolate); | ||
| RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>()); | ||
| return scope.Escape(Undefined(isolate)); | ||
| } | ||
|
|
||
| LocalVector<Value> row_values(isolate); | ||
|
|
@@ -3085,9 +3114,9 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, | |
| return MaybeLocal<Value>(); | ||
| } | ||
|
|
||
| Local<Value> result; | ||
| if (return_arrays) { | ||
| return scope.Escape( | ||
| Array::New(isolate, row_values.data(), row_values.size())); | ||
| result = Array::New(isolate, row_values.data(), row_values.size()); | ||
| } else { | ||
| LocalVector<Name> keys(isolate); | ||
| keys.reserve(num_cols); | ||
|
|
@@ -3100,9 +3129,12 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, | |
| } | ||
|
|
||
| DCHECK_EQ(keys.size(), row_values.size()); | ||
| return scope.Escape(Object::New( | ||
| isolate, Null(isolate), keys.data(), row_values.data(), num_cols)); | ||
| result = Object::New( | ||
| isolate, Null(isolate), keys.data(), row_values.data(), num_cols); | ||
| } | ||
|
|
||
| RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After a That's arguably the more correct behavior, but it's a change on a path that currently succeeds, so it deserves a test and possibly a |
||
| return scope.Escape(result); | ||
| } | ||
|
|
||
| void StatementSync::All(const FunctionCallbackInfo<Value>& args) { | ||
|
|
@@ -3119,15 +3151,19 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) { | |
| return; | ||
| } | ||
|
|
||
| auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); | ||
|
|
||
| bool needs_reset = true; | ||
| auto reset = OnScopeLeave([&]() { | ||
| if (needs_reset) sqlite3_reset(stmt->statement_); | ||
| }); | ||
| Local<Value> result; | ||
| if (StatementExecutionHelper::All(env, | ||
| stmt->db_.get(), | ||
| stmt->statement_, | ||
| stmt->return_arrays_, | ||
| stmt->use_big_ints_) | ||
| .ToLocal(&result)) { | ||
| RESET_AND_CHECK( | ||
| isolate, stmt->db_.get(), stmt->statement_, needs_reset, void()); | ||
| args.GetReturnValue().Set(result); | ||
| } | ||
| } | ||
|
|
@@ -3566,14 +3602,19 @@ void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) { | |
| } | ||
| } | ||
|
|
||
| auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); | ||
| bool needs_reset = true; | ||
| auto reset = OnScopeLeave([&]() { | ||
| if (needs_reset) sqlite3_reset(stmt->statement_); | ||
| }); | ||
| Local<Value> result; | ||
| if (StatementExecutionHelper::All(env, | ||
| stmt->db_.get(), | ||
| stmt->statement_, | ||
| stmt->return_arrays_, | ||
| stmt->use_big_ints_) | ||
| .ToLocal(&result)) { | ||
| RESET_AND_CHECK( | ||
| isolate, stmt->db_.get(), stmt->statement_, needs_reset, void()); | ||
| args.GetReturnValue().Set(result); | ||
| } | ||
| } | ||
|
|
@@ -3800,7 +3841,10 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) { | |
| if (r != SQLITE_ROW) { | ||
| CHECK_ERROR_OR_THROW( | ||
| env->isolate(), iter->stmt_->db_.get(), r, SQLITE_DONE, void()); | ||
| sqlite3_reset(iter->stmt_->statement_); | ||
| RESET_OR_THROW(env->isolate(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same shape: this early return skips the Setting const it = stmt.iterate();
while (!it.next().done);
it.next(); // yields row 1 again |
||
| iter->stmt_->db_.get(), | ||
| iter->stmt_->statement_, | ||
| void()); | ||
| iter->done_ = true; | ||
| MaybeLocal<Value> values[] = {Boolean::New(isolate, true), Null(isolate)}; | ||
| Local<Object> result; | ||
|
|
@@ -3853,6 +3897,10 @@ void StatementSyncIterator::Return(const FunctionCallbackInfo<Value>& args) { | |
| env, iter->stmt_->IsFinalized(), "statement has been finalized"); | ||
| Isolate* isolate = env->isolate(); | ||
|
|
||
| // Unlike Next(), the reset result is intentionally ignored here: Return() | ||
| // is invoked by the language during abrupt completion (e.g. a `throw` | ||
| // inside a `for...of` body), and throwing on a deferred SQLite error | ||
| // would discard the caller's already-pending exception. | ||
| sqlite3_reset(iter->stmt_->statement_); | ||
| iter->done_ = true; | ||
|
|
||
|
|
||
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.
Nit: accepting
SQLITE_ROWhere is right (run()on aRETURNING/SELECTstatement should step once and discard) and matches the previous behavior of ignoring the step result entirely. A short comment would keep someone from "tightening" this to!= SQLITE_DONElater.