Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3266,6 +3266,7 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
THROW_AND_RETURN_IF_STEPPING(env, stmt);
Isolate* isolate = env->isolate();
SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
int r = stmt->ResetStatement();
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());

Expand Down Expand Up @@ -3298,6 +3299,7 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
env, stmt->IsFinalized(), "statement has been finalized");
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
THROW_AND_RETURN_IF_STEPPING(env, stmt);
SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
int r = stmt->ResetStatement();
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());

Expand All @@ -3323,6 +3325,7 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
env, stmt->IsFinalized(), "statement has been finalized");
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
THROW_AND_RETURN_IF_STEPPING(env, stmt);
SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
int r = stmt->ResetStatement();
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());

Expand All @@ -3349,6 +3352,7 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
env, stmt->IsFinalized(), "statement has been finalized");
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
THROW_AND_RETURN_IF_STEPPING(env, stmt);
SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
int r = stmt->ResetStatement();
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());

Expand Down
63 changes: 63 additions & 0 deletions test/parallel/test-sqlite-statement-binding-reentry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';

const { skipIfSQLiteMissing, mustCall } = require('../common');
skipIfSQLiteMissing();
const assert = require('node:assert');
const { test } = require('node:test');
const { DatabaseSync } = require('node:sqlite');

const reentryError = {
code: 'ERR_INVALID_STATE',
message: 'statement is already being executed',
};

// Binding a named parameter reads properties off the supplied object, so a
// getter runs JavaScript after the statement has been reset but before it is
// stepped. Reentering the same statement there resets it a second time and
// hands out a second iterator over one virtual machine.
for (const method of ['all', 'get', 'run', 'iterate']) {
test(`${method}() reentry during parameter binding is rejected`, () => {
const db = new DatabaseSync(':memory:');
db.exec(`
CREATE TABLE data (value INTEGER);
INSERT INTO data VALUES (1), (2), (3);
`);

let statement;
const invoke = (params) => (method === 'iterate' ?
[...statement.iterate(params)] :
statement[method](params));
const reenter = mustCall(() => {
assert.throws(() => invoke({ $min: 2 }), reentryError);
return 1;
});
const params = { get $min() { return reenter(); } };

statement = db.prepare('SELECT value FROM data WHERE value >= $min');
invoke(params);
});
}

test('two iterators cannot share one virtual machine', () => {
const db = new DatabaseSync(':memory:');
db.exec(`
CREATE TABLE data (value INTEGER);
INSERT INTO data VALUES (1), (2), (3);
`);

const statement = db.prepare('SELECT value FROM data WHERE value >= $min');
let inner;
const reenter = mustCall(() => {
assert.throws(() => {
inner = statement.iterate({ $min: 1 });
}, reentryError);
return 1;
});
const params = { get $min() { return reenter(); } };

assert.deepStrictEqual(
[...statement.iterate(params)].map((row) => row.value),
[1, 2, 3],
);
assert.strictEqual(inner, undefined);
});
Loading