'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); const { DatabaseSync } = require('node:sqlite'); const { suite, test } = require('node:test'); suite('named parameters', () => { test('throws on unknown named parameters', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); t.assert.throws(() => { const stmt = db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)'); stmt.run({ $k: 1, $unknown: 1 }); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter '\$unknown'/, }); }); test('bare named parameters are supported', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); stmt.run({ k: 1, v: 9 }); t.assert.deepStrictEqual( db.prepare('SELECT * FROM data').get(), { __proto__: null, key: 1, val: 9 }, ); }); test('duplicate bare named parameters are supported', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $k)'); stmt.run({ k: 1 }); t.assert.deepStrictEqual( db.prepare('SELECT * FROM data').get(), { __proto__: null, key: 1, val: 1 }, ); }); test('bare named parameters throw on ambiguous names', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO types (key, val) VALUES ($k, @k)'); t.assert.throws(() => { stmt.run({ k: 1 }); }, { code: 'ERR_INVALID_STATE', message: 'Cannot create bare named parameter \'k\' because of ' + 'conflicting names \'$k\' and \'@k\'.', }); }); }); suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => { test('unknown named parameter support can be toggled', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(true), undefined); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.deepStrictEqual( stmt.run(params), { changes: 1, lastInsertRowid: 1 }, ); t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(false), undefined); t.assert.throws(() => { stmt.run(params); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter '\$a'/, }); }); test('throws when input is not a boolean', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); t.assert.throws(() => { stmt.setAllowUnknownNamedParameters(); }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "enabled" argument must be a boolean/, }); }); test('throws if the statement is already finalized', (t) => { using db = new DatabaseSync(':memory:'); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); stmt.close(); t.assert.throws(() => { stmt.setAllowUnknownNamedParameters(true); }, { code: 'ERR_INVALID_STATE', message: /statement has been finalized/, }); }); }); suite('options.allowUnknownNamedParameters', () => { test('unknown named parameters are allowed when input is true', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare( 'INSERT INTO data (key, val) VALUES ($k, $v)', { allowUnknownNamedParameters: true } ); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.deepStrictEqual( stmt.run(params), { changes: 1, lastInsertRowid: 1 }, ); }); test('unknown named parameters throw when input is false', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare( 'INSERT INTO data (key, val) VALUES ($k, $v)', { allowUnknownNamedParameters: false } ); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.throws(() => { stmt.run(params); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter '\$a'/, }); }); test('unknown named parameters throws error by default', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.throws(() => { stmt.run(params); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter '\$a'/, }); }); test('throws when option is not a boolean', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); t.assert.throws(() => { db.prepare( 'INSERT INTO data (key, val) VALUES ($k, $v)', { allowUnknownNamedParameters: 'true' } ); }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "options\.allowUnknownNamedParameters" argument must be a boolean/, }); }); test('setAllowUnknownNamedParameters can override prepare option', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); const stmt = db.prepare( 'INSERT INTO data (key, val) VALUES ($k, $v)', { allowUnknownNamedParameters: true } ); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.deepStrictEqual( stmt.run(params), { changes: 1, lastInsertRowid: 1 }, ); t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(false), undefined); t.assert.throws(() => { stmt.run(params); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter '\$a'/, }); }); });