Skip to content

Commit a430473

Browse files
committed
sqlite: handle stmt invalidation
1 parent 94b1f66 commit a430473

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

src/node_sqlite.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,6 +2544,7 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
25442544
THROW_AND_RETURN_ON_BAD_STATE(
25452545
env, stmt->IsFinalized(), "statement has been finalized");
25462546
Isolate* isolate = env->isolate();
2547+
stmt->reset_generation_++;
25472548
int r = sqlite3_reset(stmt->statement_);
25482549
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
25492550

@@ -2570,6 +2571,7 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
25702571
Environment* env = Environment::GetCurrent(args);
25712572
THROW_AND_RETURN_ON_BAD_STATE(
25722573
env, stmt->IsFinalized(), "statement has been finalized");
2574+
stmt->reset_generation_++;
25732575
int r = sqlite3_reset(stmt->statement_);
25742576
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
25752577

@@ -2593,6 +2595,7 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
25932595
Environment* env = Environment::GetCurrent(args);
25942596
THROW_AND_RETURN_ON_BAD_STATE(
25952597
env, stmt->IsFinalized(), "statement has been finalized");
2598+
stmt->reset_generation_++;
25962599
int r = sqlite3_reset(stmt->statement_);
25972600
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
25982601

@@ -2617,6 +2620,7 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
26172620
Environment* env = Environment::GetCurrent(args);
26182621
THROW_AND_RETURN_ON_BAD_STATE(
26192622
env, stmt->IsFinalized(), "statement has been finalized");
2623+
stmt->reset_generation_++;
26202624
int r = sqlite3_reset(stmt->statement_);
26212625
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
26222626

@@ -3162,6 +3166,7 @@ StatementSyncIterator::StatementSyncIterator(Environment* env,
31623166
: BaseObject(env, object), stmt_(std::move(stmt)) {
31633167
MakeWeak();
31643168
done_ = false;
3169+
statement_reset_generation_ = stmt_->reset_generation_;
31653170
}
31663171

31673172
StatementSyncIterator::~StatementSyncIterator() {}
@@ -3220,6 +3225,11 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
32203225
return;
32213226
}
32223227

3228+
THROW_AND_RETURN_ON_BAD_STATE(
3229+
env,
3230+
iter->statement_reset_generation_ != iter->stmt_->reset_generation_,
3231+
"iterator was invalidated");
3232+
32233233
int r = sqlite3_step(iter->stmt_->statement_);
32243234
if (r != SQLITE_ROW) {
32253235
CHECK_ERROR_OR_THROW(

src/node_sqlite.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ class StatementSync : public BaseObject {
242242
bool use_big_ints_;
243243
bool allow_bare_named_params_;
244244
bool allow_unknown_named_params_;
245+
uint64_t reset_generation_ = 0;
245246
std::optional<std::map<std::string, std::string>> bare_named_params_;
246247
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
247248
bool BindValue(const v8::Local<v8::Value>& value, const int index);
@@ -272,6 +273,7 @@ class StatementSyncIterator : public BaseObject {
272273
~StatementSyncIterator() override;
273274
BaseObjectPtr<StatementSync> stmt_;
274275
bool done_;
276+
uint64_t statement_reset_generation_;
275277
};
276278

277279
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)