Skip to content

Commit 88be44d

Browse files
authored
sqlite: handle stmt invalidation
PR-URL: #61877 Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
1 parent cb6ff03 commit 88be44d

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

src/node_sqlite.cc

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,11 @@ inline bool StatementSync::IsFinalized() {
24212421
return statement_ == nullptr;
24222422
}
24232423

2424+
inline int StatementSync::ResetStatement() {
2425+
reset_generation_++;
2426+
return sqlite3_reset(statement_);
2427+
}
2428+
24242429
bool StatementSync::BindParams(const FunctionCallbackInfo<Value>& args) {
24252430
int r = sqlite3_clear_bindings(statement_);
24262431
CHECK_ERROR_OR_THROW(env()->isolate(), db_.get(), r, SQLITE_OK, false);
@@ -2812,7 +2817,7 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
28122817
THROW_AND_RETURN_ON_BAD_STATE(
28132818
env, stmt->IsFinalized(), "statement has been finalized");
28142819
Isolate* isolate = env->isolate();
2815-
int r = sqlite3_reset(stmt->statement_);
2820+
int r = stmt->ResetStatement();
28162821
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
28172822

28182823
if (!stmt->BindParams(args)) {
@@ -2838,7 +2843,7 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
28382843
Environment* env = Environment::GetCurrent(args);
28392844
THROW_AND_RETURN_ON_BAD_STATE(
28402845
env, stmt->IsFinalized(), "statement has been finalized");
2841-
int r = sqlite3_reset(stmt->statement_);
2846+
int r = stmt->ResetStatement();
28422847
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
28432848

28442849
if (!stmt->BindParams(args)) {
@@ -2861,7 +2866,7 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
28612866
Environment* env = Environment::GetCurrent(args);
28622867
THROW_AND_RETURN_ON_BAD_STATE(
28632868
env, stmt->IsFinalized(), "statement has been finalized");
2864-
int r = sqlite3_reset(stmt->statement_);
2869+
int r = stmt->ResetStatement();
28652870
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
28662871

28672872
if (!stmt->BindParams(args)) {
@@ -2885,7 +2890,7 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
28852890
Environment* env = Environment::GetCurrent(args);
28862891
THROW_AND_RETURN_ON_BAD_STATE(
28872892
env, stmt->IsFinalized(), "statement has been finalized");
2888-
int r = sqlite3_reset(stmt->statement_);
2893+
int r = stmt->ResetStatement();
28892894
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
28902895

28912896
if (!stmt->BindParams(args)) {
@@ -3430,6 +3435,7 @@ StatementSyncIterator::StatementSyncIterator(Environment* env,
34303435
: BaseObject(env, object), stmt_(std::move(stmt)) {
34313436
MakeWeak();
34323437
done_ = false;
3438+
statement_reset_generation_ = stmt_->reset_generation_;
34333439
}
34343440

34353441
StatementSyncIterator::~StatementSyncIterator() {}
@@ -3488,6 +3494,11 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
34883494
return;
34893495
}
34903496

3497+
THROW_AND_RETURN_ON_BAD_STATE(
3498+
env,
3499+
iter->statement_reset_generation_ != iter->stmt_->reset_generation_,
3500+
"iterator was invalidated");
3501+
34913502
int r = sqlite3_step(iter->stmt_->statement_);
34923503
if (r != SQLITE_ROW) {
34933504
CHECK_ERROR_OR_THROW(

src/node_sqlite.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ class StatementSync : public BaseObject {
291291
bool use_big_ints_;
292292
bool allow_bare_named_params_;
293293
bool allow_unknown_named_params_;
294+
uint64_t reset_generation_ = 0;
294295
std::optional<std::map<std::string, std::string>> bare_named_params_;
296+
inline int ResetStatement();
295297
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
296298
bool BindValue(const v8::Local<v8::Value>& value, const int index);
297299

@@ -321,6 +323,7 @@ class StatementSyncIterator : public BaseObject {
321323
~StatementSyncIterator() override;
322324
BaseObjectPtr<StatementSync> stmt_;
323325
bool done_;
326+
uint64_t statement_reset_generation_;
324327
};
325328

326329
using Sqlite3ChangesetGenFunc = int (*)(sqlite3_session*, int*, void**);

test/parallel/test-sqlite-statement-sync.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,61 @@ suite('StatementSync.prototype.iterate()', () => {
171171
{ __proto__: null, done: true, value: null },
172172
);
173173
});
174+
175+
test('iterator is invalidated when statement is reset by get/all/run/iterate', (t) => {
176+
const db = new DatabaseSync(':memory:');
177+
db.exec('CREATE TABLE test (value INTEGER NOT NULL)');
178+
for (let i = 0; i < 5; i++) {
179+
db.prepare('INSERT INTO test (value) VALUES (?)').run(i);
180+
}
181+
const stmt = db.prepare('SELECT * FROM test');
182+
183+
// Invalidated by stmt.get()
184+
let it = stmt.iterate();
185+
it.next();
186+
stmt.get();
187+
t.assert.throws(() => { it.next(); }, {
188+
code: 'ERR_INVALID_STATE',
189+
message: /iterator was invalidated/,
190+
});
191+
192+
// Invalidated by stmt.all()
193+
it = stmt.iterate();
194+
it.next();
195+
stmt.all();
196+
t.assert.throws(() => { it.next(); }, {
197+
code: 'ERR_INVALID_STATE',
198+
message: /iterator was invalidated/,
199+
});
200+
201+
// Invalidated by stmt.run()
202+
it = stmt.iterate();
203+
it.next();
204+
stmt.run();
205+
t.assert.throws(() => { it.next(); }, {
206+
code: 'ERR_INVALID_STATE',
207+
message: /iterator was invalidated/,
208+
});
209+
210+
// Invalidated by a new stmt.iterate()
211+
it = stmt.iterate();
212+
it.next();
213+
const it2 = stmt.iterate();
214+
t.assert.throws(() => { it.next(); }, {
215+
code: 'ERR_INVALID_STATE',
216+
message: /iterator was invalidated/,
217+
});
218+
219+
// New iterator works fine
220+
t.assert.strictEqual(it2.next().done, false);
221+
222+
// Reset on a different statement does NOT invalidate this iterator
223+
const stmt2 = db.prepare('SELECT * FROM test');
224+
it = stmt.iterate();
225+
it.next();
226+
stmt2.get();
227+
it.next();
228+
});
174229
});
175230

176231
suite('StatementSync.prototype.run()', () => {

0 commit comments

Comments
 (0)