diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index fb229e19fd6aef..153abdd8ac90c6 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2544,6 +2544,7 @@ void StatementSync::All(const FunctionCallbackInfo& args) { THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); Isolate* isolate = env->isolate(); + stmt->reset_generation_++; int r = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void()); @@ -2570,6 +2571,7 @@ void StatementSync::Iterate(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); + stmt->reset_generation_++; int r = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); @@ -2593,6 +2595,7 @@ void StatementSync::Get(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); + stmt->reset_generation_++; int r = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); @@ -2617,6 +2620,7 @@ void StatementSync::Run(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); + stmt->reset_generation_++; int r = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); @@ -3162,6 +3166,7 @@ StatementSyncIterator::StatementSyncIterator(Environment* env, : BaseObject(env, object), stmt_(std::move(stmt)) { MakeWeak(); done_ = false; + statement_reset_generation_ = stmt_->reset_generation_; } StatementSyncIterator::~StatementSyncIterator() {} @@ -3220,6 +3225,11 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo& args) { return; } + THROW_AND_RETURN_ON_BAD_STATE( + env, + iter->statement_reset_generation_ != iter->stmt_->reset_generation_, + "iterator was invalidated"); + int r = sqlite3_step(iter->stmt_->statement_); if (r != SQLITE_ROW) { CHECK_ERROR_OR_THROW( diff --git a/src/node_sqlite.h b/src/node_sqlite.h index bd61fcd6ebcd4d..8b22c5762c5cf6 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -242,6 +242,7 @@ class StatementSync : public BaseObject { bool use_big_ints_; bool allow_bare_named_params_; bool allow_unknown_named_params_; + uint64_t reset_generation_ = 0; std::optional> bare_named_params_; bool BindParams(const v8::FunctionCallbackInfo& args); bool BindValue(const v8::Local& value, const int index); @@ -272,6 +273,7 @@ class StatementSyncIterator : public BaseObject { ~StatementSyncIterator() override; BaseObjectPtr stmt_; bool done_; + uint64_t statement_reset_generation_; }; using Sqlite3ChangesetGenFunc = int (*)(sqlite3_session*, int*, void**); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index 62e95363f1c46a..aa7a3a73ae6649 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -171,6 +171,61 @@ suite('StatementSync.prototype.iterate()', () => { { __proto__: null, done: true, value: null }, ); }); + + test('iterator is invalidated when statement is reset by get/all/run/iterate', (t) => { + const db = new DatabaseSync(':memory:'); + db.exec('CREATE TABLE test (value INTEGER NOT NULL)'); + for (let i = 0; i < 5; i++) { + db.prepare('INSERT INTO test (value) VALUES (?)').run(i); + } + const stmt = db.prepare('SELECT * FROM test'); + + // Invalidated by stmt.get() + let it = stmt.iterate(); + it.next(); + stmt.get(); + t.assert.throws(() => { it.next(); }, { + code: 'ERR_INVALID_STATE', + message: /iterator was invalidated/, + }); + + // Invalidated by stmt.all() + it = stmt.iterate(); + it.next(); + stmt.all(); + t.assert.throws(() => { it.next(); }, { + code: 'ERR_INVALID_STATE', + message: /iterator was invalidated/, + }); + + // Invalidated by stmt.run() + it = stmt.iterate(); + it.next(); + stmt.run(); + t.assert.throws(() => { it.next(); }, { + code: 'ERR_INVALID_STATE', + message: /iterator was invalidated/, + }); + + // Invalidated by a new stmt.iterate() + it = stmt.iterate(); + it.next(); + const it2 = stmt.iterate(); + t.assert.throws(() => { it.next(); }, { + code: 'ERR_INVALID_STATE', + message: /iterator was invalidated/, + }); + + // New iterator works fine + t.assert.strictEqual(it2.next().done, false); + + // Reset on a different statement does NOT invalidate this iterator + const stmt2 = db.prepare('SELECT * FROM test'); + it = stmt.iterate(); + it.next(); + stmt2.get(); + it.next(); + }); }); suite('StatementSync.prototype.run()', () => {