'use strict'; const { skipIfSQLiteMissing, mustCall } = require('../common'); skipIfSQLiteMissing(); const assert = require('node:assert'); const { test } = require('node:test'); const { Database } = 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 Database(':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 Database(':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); });