diff --git a/test/parallel/test-sqlite-data-types.js b/test/parallel/test-sqlite-data-types.js new file mode 100644 index 00000000000000..582d5bd611edf4 --- /dev/null +++ b/test/parallel/test-sqlite-data-types.js @@ -0,0 +1,157 @@ +// Flags: --experimental-sqlite +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const { join } = require('node:path'); +const { DatabaseSync } = require('node:sqlite'); +const { suite, test } = require('node:test'); +let cnt = 0; + +tmpdir.refresh(); + +function nextDb() { + return join(tmpdir.path, `database-${cnt++}.db`); +} + +suite('data binding and mapping', () => { + test('supported data types', (t) => { + const u8a = new TextEncoder().encode('a☃b☃c'); + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE types( + key INTEGER PRIMARY KEY, + int INTEGER, + double REAL, + text TEXT, + buf BLOB + ) STRICT; + `); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO types (key, int, double, text, buf) ' + + 'VALUES (?, ?, ?, ?, ?)'); + t.assert.deepStrictEqual( + stmt.run(1, 42, 3.14159, 'foo', u8a), + { changes: 1, lastInsertRowid: 1 }, + ); + t.assert.deepStrictEqual( + stmt.run(2, null, null, null, null), + { changes: 1, lastInsertRowid: 2 } + ); + t.assert.deepStrictEqual( + stmt.run(3, Number(8), Number(2.718), String('bar'), Buffer.from('x☃y☃')), + { changes: 1, lastInsertRowid: 3 }, + ); + t.assert.deepStrictEqual( + stmt.run(4, 99n, 0xf, '', new Uint8Array()), + { changes: 1, lastInsertRowid: 4 }, + ); + + const query = db.prepare('SELECT * FROM types WHERE key = ?'); + t.assert.deepStrictEqual(query.get(1), { + key: 1, + int: 42, + double: 3.14159, + text: 'foo', + buf: u8a, + }); + t.assert.deepStrictEqual(query.get(2), { + key: 2, + int: null, + double: null, + text: null, + buf: null, + }); + t.assert.deepStrictEqual(query.get(3), { + key: 3, + int: 8, + double: 2.718, + text: 'bar', + buf: new TextEncoder().encode('x☃y☃'), + }); + t.assert.deepStrictEqual(query.get(4), { + key: 4, + int: 99, + double: 0xf, + text: '', + buf: new Uint8Array(), + }); + }); + + test('unsupported data types', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + + [ + undefined, + () => {}, + Symbol(), + /foo/, + Promise.resolve(), + new Map(), + new Set(), + ].forEach((val) => { + t.assert.throws(() => { + db.prepare('INSERT INTO types (key, val) VALUES (?, ?)').run(1, val); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /Provided value cannot be bound to SQLite parameter 2/, + }); + }); + + t.assert.throws(() => { + const stmt = db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)'); + stmt.run({ $k: 1, $v: () => {} }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /Provided value cannot be bound to SQLite parameter 2/, + }); + }); + + test('throws when binding a BigInt that is too large', (t) => { + const max = 9223372036854775807n; // Largest 64-bit signed integer value. + const db = new DatabaseSync(nextDb()); + 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 (?, ?)'); + t.assert.deepStrictEqual( + stmt.run(1, max), + { changes: 1, lastInsertRowid: 1 }, + ); + t.assert.throws(() => { + stmt.run(1, max + 1n); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: /BigInt value is too large to bind/, + }); + }); + + test('statements are unbound on each call', (t) => { + const db = new DatabaseSync(nextDb()); + 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 (?, ?)'); + t.assert.deepStrictEqual( + stmt.run(1, 5), + { changes: 1, lastInsertRowid: 1 }, + ); + t.assert.deepStrictEqual( + stmt.run(), + { changes: 1, lastInsertRowid: 2 }, + ); + t.assert.deepStrictEqual( + db.prepare('SELECT * FROM data ORDER BY key').all(), + [{ key: 1, val: 5 }, { key: 2, val: null }], + ); + }); +}); diff --git a/test/parallel/test-sqlite-database-sync.js b/test/parallel/test-sqlite-database-sync.js new file mode 100644 index 00000000000000..1bc409926ae446 --- /dev/null +++ b/test/parallel/test-sqlite-database-sync.js @@ -0,0 +1,185 @@ +// Flags: --experimental-sqlite +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const { existsSync } = require('node:fs'); +const { join } = require('node:path'); +const { DatabaseSync, StatementSync } = require('node:sqlite'); +const { suite, test } = require('node:test'); +let cnt = 0; + +tmpdir.refresh(); + +function nextDb() { + return join(tmpdir.path, `database-${cnt++}.db`); +} + +suite('DatabaseSync() constructor', () => { + test('throws if called without new', (t) => { + t.assert.throws(() => { + DatabaseSync(); + }, { + code: 'ERR_CONSTRUCT_CALL_REQUIRED', + message: /Cannot call constructor without `new`/, + }); + }); + + test('throws if database path is not a string', (t) => { + t.assert.throws(() => { + new DatabaseSync(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "path" argument must be a string/, + }); + }); + + test('throws if options is provided but is not an object', (t) => { + t.assert.throws(() => { + new DatabaseSync('foo', null); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be an object/, + }); + }); + + test('throws if options.open is provided but is not a boolean', (t) => { + t.assert.throws(() => { + new DatabaseSync('foo', { open: 5 }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.open" argument must be a boolean/, + }); + }); +}); + +suite('DatabaseSync.prototype.open()', () => { + test('opens a database connection', (t) => { + const dbPath = nextDb(); + const db = new DatabaseSync(dbPath, { open: false }); + t.after(() => { db.close(); }); + + t.assert.strictEqual(existsSync(dbPath), false); + t.assert.strictEqual(db.open(), undefined); + t.assert.strictEqual(existsSync(dbPath), true); + }); + + test('throws if database is already open', (t) => { + const db = new DatabaseSync(nextDb(), { open: false }); + t.after(() => { db.close(); }); + + db.open(); + t.assert.throws(() => { + db.open(); + }, { + code: 'ERR_INVALID_STATE', + message: /database is already open/, + }); + }); +}); + +suite('DatabaseSync.prototype.close()', () => { + test('closes an open database connection', (t) => { + const db = new DatabaseSync(nextDb()); + + t.assert.strictEqual(db.close(), undefined); + }); + + test('throws if database is not open', (t) => { + const db = new DatabaseSync(nextDb(), { open: false }); + + t.assert.throws(() => { + db.close(); + }, { + code: 'ERR_INVALID_STATE', + message: /database is not open/, + }); + }); +}); + +suite('DatabaseSync.prototype.prepare()', () => { + test('returns a prepared statement', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const stmt = db.prepare('CREATE TABLE webstorage(key TEXT)'); + t.assert.ok(stmt instanceof StatementSync); + }); + + test('throws if database is not open', (t) => { + const db = new DatabaseSync(nextDb(), { open: false }); + + t.assert.throws(() => { + db.prepare(); + }, { + code: 'ERR_INVALID_STATE', + message: /database is not open/, + }); + }); + + test('throws if sql is not a string', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + + t.assert.throws(() => { + db.prepare(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "sql" argument must be a string/, + }); + }); +}); + +suite('DatabaseSync.prototype.exec()', () => { + test('executes SQL', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const result = db.exec(` + CREATE TABLE data( + key INTEGER PRIMARY KEY, + val INTEGER + ) STRICT; + INSERT INTO data (key, val) VALUES (1, 2); + INSERT INTO data (key, val) VALUES (8, 9); + `); + t.assert.strictEqual(result, undefined); + const stmt = db.prepare('SELECT * FROM data ORDER BY key'); + t.assert.deepStrictEqual(stmt.all(), [ + { key: 1, val: 2 }, + { key: 8, val: 9 }, + ]); + }); + + test('reports errors from SQLite', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + + t.assert.throws(() => { + db.exec('CREATE TABLEEEE'); + }, { + code: 'ERR_SQLITE_ERROR', + message: /syntax error/, + }); + }); + + test('throws if database is not open', (t) => { + const db = new DatabaseSync(nextDb(), { open: false }); + + t.assert.throws(() => { + db.exec(); + }, { + code: 'ERR_INVALID_STATE', + message: /database is not open/, + }); + }); + + test('throws if sql is not a string', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + + t.assert.throws(() => { + db.exec(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "sql" argument must be a string/, + }); + }); +}); diff --git a/test/parallel/test-sqlite-named-parameters.js b/test/parallel/test-sqlite-named-parameters.js new file mode 100644 index 00000000000000..27857111953d27 --- /dev/null +++ b/test/parallel/test-sqlite-named-parameters.js @@ -0,0 +1,80 @@ +// Flags: --experimental-sqlite +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const { join } = require('node:path'); +const { DatabaseSync } = require('node:sqlite'); +const { suite, test } = require('node:test'); +let cnt = 0; + +tmpdir.refresh(); + +function nextDb() { + return join(tmpdir.path, `database-${cnt++}.db`); +} + +suite('named parameters', () => { + test('throws on unknown named parameters', (t) => { + const db = new DatabaseSync(nextDb()); + 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(nextDb()); + 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(), + { key: 1, val: 9 }, + ); + }); + + test('duplicate bare named parameters are supported', (t) => { + const db = new DatabaseSync(nextDb()); + 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(), + { key: 1, val: 1 }, + ); + }); + + test('bare named parameters throw on ambiguous names', (t) => { + const db = new DatabaseSync(nextDb()); + 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\'.', + }); + }); +}); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js new file mode 100644 index 00000000000000..7a4069678af966 --- /dev/null +++ b/test/parallel/test-sqlite-statement-sync.js @@ -0,0 +1,273 @@ +// Flags: --experimental-sqlite +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const { join } = require('node:path'); +const { DatabaseSync, StatementSync } = require('node:sqlite'); +const { suite, test } = require('node:test'); +let cnt = 0; + +tmpdir.refresh(); + +function nextDb() { + return join(tmpdir.path, `database-${cnt++}.db`); +} + +suite('StatementSync() constructor', () => { + test('StatementSync cannot be constructed directly', (t) => { + t.assert.throws(() => { + new StatementSync(); + }, { + code: 'ERR_ILLEGAL_CONSTRUCTOR', + message: /Illegal constructor/, + }); + }); +}); + +suite('StatementSync.prototype.get()', () => { + test('executes a query and returns undefined on no results', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); + t.assert.strictEqual(stmt.get(), undefined); + stmt = db.prepare('SELECT * FROM storage'); + t.assert.strictEqual(stmt.get(), undefined); + }); + + test('executes a query and returns the first result', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); + t.assert.strictEqual(stmt.get(), undefined); + stmt = db.prepare('INSERT INTO storage (key, val) VALUES (?, ?)'); + t.assert.strictEqual(stmt.get('key1', 'val1'), undefined); + t.assert.strictEqual(stmt.get('key2', 'val2'), undefined); + stmt = db.prepare('SELECT * FROM storage ORDER BY key'); + t.assert.deepStrictEqual(stmt.get(), { key: 'key1', val: 'val1' }); + }); +}); + +suite('StatementSync.prototype.all()', () => { + test('executes a query and returns an empty array on no results', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); + t.assert.deepStrictEqual(stmt.all(), []); + }); + + test('executes a query and returns all results', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); + t.assert.deepStrictEqual(stmt.run(), { changes: 0, lastInsertRowid: 0 }); + stmt = db.prepare('INSERT INTO storage (key, val) VALUES (?, ?)'); + t.assert.deepStrictEqual( + stmt.run('key1', 'val1'), + { changes: 1, lastInsertRowid: 1 }, + ); + t.assert.deepStrictEqual( + stmt.run('key2', 'val2'), + { changes: 1, lastInsertRowid: 2 }, + ); + stmt = db.prepare('SELECT * FROM storage ORDER BY key'); + t.assert.deepStrictEqual(stmt.all(), [ + { key: 'key1', val: 'val1' }, + { key: 'key2', val: 'val2' }, + ]); + }); +}); + +suite('StatementSync.prototype.run()', () => { + test('executes a query and returns change metadata', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE storage(key TEXT, val TEXT); + INSERT INTO storage (key, val) VALUES ('foo', 'bar'); + `); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('SELECT * FROM storage'); + t.assert.deepStrictEqual(stmt.run(), { changes: 1, lastInsertRowid: 1 }); + }); + + test('SQLite throws when trying to bind too many parameters', (t) => { + const db = new DatabaseSync(nextDb()); + 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 (?, ?)'); + t.assert.throws(() => { + stmt.run(1, 2, 3); + }, { + code: 'ERR_SQLITE_ERROR', + message: 'column index out of range', + errcode: 25, + errstr: 'column index out of range', + }); + }); + + test('SQLite defaults to NULL for unbound parameters', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES (?, ?)'); + t.assert.throws(() => { + stmt.run(1); + }, { + code: 'ERR_SQLITE_ERROR', + message: 'NOT NULL constraint failed: data.val', + errcode: 1299, + errstr: 'constraint failed', + }); + }); +}); + +suite('StatementSync.prototype.sourceSQL()', () => { + test('returns input SQL', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const sql = 'INSERT INTO types (key, val) VALUES ($k, $v)'; + const stmt = db.prepare(sql); + t.assert.strictEqual(stmt.sourceSQL(), sql); + }); +}); + +suite('StatementSync.prototype.expandedSQL()', () => { + test('returns expanded SQL', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const sql = 'INSERT INTO types (key, val) VALUES ($k, ?)'; + const expanded = 'INSERT INTO types (key, val) VALUES (\'33\', \'42\')'; + const stmt = db.prepare(sql); + t.assert.deepStrictEqual( + stmt.run({ $k: '33' }, '42'), + { changes: 1, lastInsertRowid: 33 }, + ); + t.assert.strictEqual(stmt.expandedSQL(), expanded); + }); +}); + +suite('StatementSync.prototype.setReadBigInts()', () => { + test('BigInts support can be toggled', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; + INSERT INTO data (key, val) VALUES (1, 42); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare('SELECT val FROM data'); + t.assert.deepStrictEqual(query.get(), { val: 42 }); + t.assert.strictEqual(query.setReadBigInts(true), undefined); + t.assert.deepStrictEqual(query.get(), { val: 42n }); + t.assert.strictEqual(query.setReadBigInts(false), undefined); + t.assert.deepStrictEqual(query.get(), { val: 42 }); + + const insert = db.prepare('INSERT INTO data (key) VALUES (?)'); + t.assert.deepStrictEqual( + insert.run(10), + { changes: 1, lastInsertRowid: 10 }, + ); + t.assert.strictEqual(insert.setReadBigInts(true), undefined); + t.assert.deepStrictEqual( + insert.run(20), + { changes: 1n, lastInsertRowid: 20n }, + ); + t.assert.strictEqual(insert.setReadBigInts(false), undefined); + t.assert.deepStrictEqual( + insert.run(30), + { changes: 1, lastInsertRowid: 30 }, + ); + }); + + test('throws when input is not a boolean', (t) => { + const db = new DatabaseSync(nextDb()); + 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, $v)'); + t.assert.throws(() => { + stmt.setReadBigInts(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "readBigInts" argument must be a boolean/, + }); + }); + + test('BigInt is required for reading large integers', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const bad = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); + t.assert.throws(() => { + bad.get(); + }, { + code: 'ERR_OUT_OF_RANGE', + message: /^The value of column 0 is too large.*: 9007199254740992$/, + }); + const good = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); + good.setReadBigInts(true); + t.assert.deepStrictEqual(good.get(), { + [`${Number.MAX_SAFE_INTEGER} + 1`]: 2n ** 53n, + }); + }); +}); + +suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { + test('bare named parameter support can be toggled', (t) => { + const db = new DatabaseSync(nextDb()); + 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.deepStrictEqual( + stmt.run({ k: 1, v: 2 }), + { changes: 1, lastInsertRowid: 1 }, + ); + t.assert.strictEqual(stmt.setAllowBareNamedParameters(false), undefined); + t.assert.throws(() => { + stmt.run({ k: 2, v: 4 }); + }, { + code: 'ERR_INVALID_STATE', + message: /Unknown named parameter 'k'/, + }); + t.assert.strictEqual(stmt.setAllowBareNamedParameters(true), undefined); + t.assert.deepStrictEqual( + stmt.run({ k: 3, v: 6 }), + { changes: 1, lastInsertRowid: 3 }, + ); + }); + + test('throws when input is not a boolean', (t) => { + const db = new DatabaseSync(nextDb()); + 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.setAllowBareNamedParameters(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "allowBareNamedParameters" argument must be a boolean/, + }); + }); +}); diff --git a/test/parallel/test-sqlite-transactions.js b/test/parallel/test-sqlite-transactions.js new file mode 100644 index 00000000000000..a37e635541bbf2 --- /dev/null +++ b/test/parallel/test-sqlite-transactions.js @@ -0,0 +1,67 @@ +// Flags: --experimental-sqlite +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const { join } = require('node:path'); +const { DatabaseSync } = require('node:sqlite'); +const { suite, test } = require('node:test'); +let cnt = 0; + +tmpdir.refresh(); + +function nextDb() { + return join(tmpdir.path, `database-${cnt++}.db`); +} + +suite('manual transactions', () => { + test('a transaction is committed', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data( + key INTEGER PRIMARY KEY + ) STRICT; + `); + t.assert.strictEqual(setup, undefined); + t.assert.deepStrictEqual( + db.prepare('BEGIN').run(), + { changes: 0, lastInsertRowid: 0 }, + ); + t.assert.deepStrictEqual( + db.prepare('INSERT INTO data (key) VALUES (100)').run(), + { changes: 1, lastInsertRowid: 100 }, + ); + t.assert.deepStrictEqual( + db.prepare('COMMIT').run(), + { changes: 1, lastInsertRowid: 100 }, + ); + t.assert.deepStrictEqual( + db.prepare('SELECT * FROM data').all(), + [{ key: 100 }], + ); + }); + + test('a transaction is rolled back', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data( + key INTEGER PRIMARY KEY + ) STRICT; + `); + t.assert.strictEqual(setup, undefined); + t.assert.deepStrictEqual( + db.prepare('BEGIN').run(), + { changes: 0, lastInsertRowid: 0 }, + ); + t.assert.deepStrictEqual( + db.prepare('INSERT INTO data (key) VALUES (100)').run(), + { changes: 1, lastInsertRowid: 100 }, + ); + t.assert.deepStrictEqual( + db.prepare('ROLLBACK').run(), + { changes: 1, lastInsertRowid: 100 }, + ); + t.assert.deepStrictEqual(db.prepare('SELECT * FROM data').all(), []); + }); +}); diff --git a/test/parallel/test-sqlite.js b/test/parallel/test-sqlite.js index 3d899063f9c967..8acabb96fceab4 100644 --- a/test/parallel/test-sqlite.js +++ b/test/parallel/test-sqlite.js @@ -2,9 +2,8 @@ 'use strict'; const { spawnPromisified } = require('../common'); const tmpdir = require('../common/tmpdir'); -const { existsSync } = require('node:fs'); const { join } = require('node:path'); -const { DatabaseSync, StatementSync } = require('node:sqlite'); +const { DatabaseSync } = require('node:sqlite'); const { suite, test } = require('node:test'); let cnt = 0; @@ -42,668 +41,9 @@ suite('accessing the node:sqlite module', () => { }); }); -suite('DatabaseSync() constructor', () => { - test('throws if called without new', (t) => { - t.assert.throws(() => { - DatabaseSync(); - }, { - code: 'ERR_CONSTRUCT_CALL_REQUIRED', - message: /Cannot call constructor without `new`/, - }); - }); - - test('throws if database path is not a string', (t) => { - t.assert.throws(() => { - new DatabaseSync(); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "path" argument must be a string/, - }); - }); - - test('throws if options is provided but is not an object', (t) => { - t.assert.throws(() => { - new DatabaseSync('foo', null); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "options" argument must be an object/, - }); - }); - - test('throws if options.open is provided but is not a boolean', (t) => { - t.assert.throws(() => { - new DatabaseSync('foo', { open: 5 }); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "options\.open" argument must be a boolean/, - }); - }); -}); - -suite('DatabaseSync.prototype.open()', () => { - test('opens a database connection', (t) => { - const dbPath = nextDb(); - const db = new DatabaseSync(dbPath, { open: false }); - - t.assert.strictEqual(existsSync(dbPath), false); - t.assert.strictEqual(db.open(), undefined); - t.assert.strictEqual(existsSync(dbPath), true); - }); - - test('throws if database is already open', (t) => { - const db = new DatabaseSync(nextDb(), { open: false }); - - db.open(); - t.assert.throws(() => { - db.open(); - }, { - code: 'ERR_INVALID_STATE', - message: /database is already open/, - }); - }); -}); - -suite('DatabaseSync.prototype.close()', () => { - test('closes an open database connection', (t) => { - const db = new DatabaseSync(nextDb()); - - t.assert.strictEqual(db.close(), undefined); - }); - - test('throws if database is not open', (t) => { - const db = new DatabaseSync(nextDb(), { open: false }); - - t.assert.throws(() => { - db.close(); - }, { - code: 'ERR_INVALID_STATE', - message: /database is not open/, - }); - }); -}); - -suite('DatabaseSync.prototype.prepare()', () => { - test('returns a prepared statement', (t) => { - const db = new DatabaseSync(nextDb()); - const stmt = db.prepare('CREATE TABLE webstorage(key TEXT)'); - t.assert.ok(stmt instanceof StatementSync); - }); - - test('throws if database is not open', (t) => { - const db = new DatabaseSync(nextDb(), { open: false }); - - t.assert.throws(() => { - db.prepare(); - }, { - code: 'ERR_INVALID_STATE', - message: /database is not open/, - }); - }); - - test('throws if sql is not a string', (t) => { - const db = new DatabaseSync(nextDb()); - - t.assert.throws(() => { - db.prepare(); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "sql" argument must be a string/, - }); - }); -}); - -suite('DatabaseSync.prototype.exec()', () => { - test('executes SQL', (t) => { - const db = new DatabaseSync(nextDb()); - const result = db.exec(` - CREATE TABLE data( - key INTEGER PRIMARY KEY, - val INTEGER - ) STRICT; - INSERT INTO data (key, val) VALUES (1, 2); - INSERT INTO data (key, val) VALUES (8, 9); - `); - t.assert.strictEqual(result, undefined); - const stmt = db.prepare('SELECT * FROM data ORDER BY key'); - t.assert.deepStrictEqual(stmt.all(), [ - { key: 1, val: 2 }, - { key: 8, val: 9 }, - ]); - }); - - test('reports errors from SQLite', (t) => { - const db = new DatabaseSync(nextDb()); - - t.assert.throws(() => { - db.exec('CREATE TABLEEEE'); - }, { - code: 'ERR_SQLITE_ERROR', - message: /syntax error/, - }); - }); - - test('throws if database is not open', (t) => { - const db = new DatabaseSync(nextDb(), { open: false }); - - t.assert.throws(() => { - db.exec(); - }, { - code: 'ERR_INVALID_STATE', - message: /database is not open/, - }); - }); - - test('throws if sql is not a string', (t) => { - const db = new DatabaseSync(nextDb()); - - t.assert.throws(() => { - db.exec(); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "sql" argument must be a string/, - }); - }); -}); - -suite('StatementSync() constructor', () => { - test('StatementSync cannot be constructed directly', (t) => { - t.assert.throws(() => { - new StatementSync(); - }, { - code: 'ERR_ILLEGAL_CONSTRUCTOR', - message: /Illegal constructor/, - }); - }); -}); - -suite('StatementSync.prototype.get()', () => { - test('executes a query and returns undefined on no results', (t) => { - const db = new DatabaseSync(nextDb()); - let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); - t.assert.strictEqual(stmt.get(), undefined); - stmt = db.prepare('SELECT * FROM storage'); - t.assert.strictEqual(stmt.get(), undefined); - }); - - test('executes a query and returns the first result', (t) => { - const db = new DatabaseSync(nextDb()); - let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); - t.assert.strictEqual(stmt.get(), undefined); - stmt = db.prepare('INSERT INTO storage (key, val) VALUES (?, ?)'); - t.assert.strictEqual(stmt.get('key1', 'val1'), undefined); - t.assert.strictEqual(stmt.get('key2', 'val2'), undefined); - stmt = db.prepare('SELECT * FROM storage ORDER BY key'); - t.assert.deepStrictEqual(stmt.get(), { key: 'key1', val: 'val1' }); - }); -}); - -suite('StatementSync.prototype.all()', () => { - test('executes a query and returns an empty array on no results', (t) => { - const db = new DatabaseSync(nextDb()); - const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); - t.assert.deepStrictEqual(stmt.all(), []); - }); - - test('executes a query and returns all results', (t) => { - const db = new DatabaseSync(nextDb()); - let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); - t.assert.deepStrictEqual(stmt.run(), { changes: 0, lastInsertRowid: 0 }); - stmt = db.prepare('INSERT INTO storage (key, val) VALUES (?, ?)'); - t.assert.deepStrictEqual( - stmt.run('key1', 'val1'), - { changes: 1, lastInsertRowid: 1 }, - ); - t.assert.deepStrictEqual( - stmt.run('key2', 'val2'), - { changes: 1, lastInsertRowid: 2 }, - ); - stmt = db.prepare('SELECT * FROM storage ORDER BY key'); - t.assert.deepStrictEqual(stmt.all(), [ - { key: 'key1', val: 'val1' }, - { key: 'key2', val: 'val2' }, - ]); - }); -}); - -suite('StatementSync.prototype.run()', () => { - test('executes a query and returns change metadata', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec(` - CREATE TABLE storage(key TEXT, val TEXT); - INSERT INTO storage (key, val) VALUES ('foo', 'bar'); - `); - t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('SELECT * FROM storage'); - t.assert.deepStrictEqual(stmt.run(), { changes: 1, lastInsertRowid: 1 }); - }); - - test('SQLite throws when trying to bind too many parameters', (t) => { - const db = new DatabaseSync(nextDb()); - 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 (?, ?)'); - t.assert.throws(() => { - stmt.run(1, 2, 3); - }, { - code: 'ERR_SQLITE_ERROR', - message: 'column index out of range', - errcode: 25, - errstr: 'column index out of range', - }); - }); - - test('SQLite defaults to NULL for unbound parameters', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec( - 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' - ); - t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES (?, ?)'); - t.assert.throws(() => { - stmt.run(1); - }, { - code: 'ERR_SQLITE_ERROR', - message: 'NOT NULL constraint failed: data.val', - errcode: 1299, - errstr: 'constraint failed', - }); - }); -}); - -suite('StatementSync.prototype.sourceSQL()', () => { - test('returns input SQL', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec( - 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' - ); - t.assert.strictEqual(setup, undefined); - const sql = 'INSERT INTO types (key, val) VALUES ($k, $v)'; - const stmt = db.prepare(sql); - t.assert.strictEqual(stmt.sourceSQL(), sql); - }); -}); - -suite('StatementSync.prototype.expandedSQL()', () => { - test('returns expanded SQL', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec( - 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' - ); - t.assert.strictEqual(setup, undefined); - const sql = 'INSERT INTO types (key, val) VALUES ($k, ?)'; - const expanded = 'INSERT INTO types (key, val) VALUES (\'33\', \'42\')'; - const stmt = db.prepare(sql); - t.assert.deepStrictEqual( - stmt.run({ $k: '33' }, '42'), - { changes: 1, lastInsertRowid: 33 }, - ); - t.assert.strictEqual(stmt.expandedSQL(), expanded); - }); -}); - -suite('StatementSync.prototype.setReadBigInts()', () => { - test('BigInts support can be toggled', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec(` - CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; - INSERT INTO data (key, val) VALUES (1, 42); - `); - t.assert.strictEqual(setup, undefined); - - const query = db.prepare('SELECT val FROM data'); - t.assert.deepStrictEqual(query.get(), { val: 42 }); - t.assert.strictEqual(query.setReadBigInts(true), undefined); - t.assert.deepStrictEqual(query.get(), { val: 42n }); - t.assert.strictEqual(query.setReadBigInts(false), undefined); - t.assert.deepStrictEqual(query.get(), { val: 42 }); - - const insert = db.prepare('INSERT INTO data (key) VALUES (?)'); - t.assert.deepStrictEqual( - insert.run(10), - { changes: 1, lastInsertRowid: 10 }, - ); - t.assert.strictEqual(insert.setReadBigInts(true), undefined); - t.assert.deepStrictEqual( - insert.run(20), - { changes: 1n, lastInsertRowid: 20n }, - ); - t.assert.strictEqual(insert.setReadBigInts(false), undefined); - t.assert.deepStrictEqual( - insert.run(30), - { changes: 1, lastInsertRowid: 30 }, - ); - }); - - test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); - 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, $v)'); - t.assert.throws(() => { - stmt.setReadBigInts(); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "readBigInts" argument must be a boolean/, - }); - }); - - test('BigInt is required for reading large integers', (t) => { - const db = new DatabaseSync(nextDb()); - const bad = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); - t.assert.throws(() => { - bad.get(); - }, { - code: 'ERR_OUT_OF_RANGE', - message: /^The value of column 0 is too large.*: 9007199254740992$/, - }); - const good = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); - good.setReadBigInts(true); - t.assert.deepStrictEqual(good.get(), { - [`${Number.MAX_SAFE_INTEGER} + 1`]: 2n ** 53n, - }); - }); -}); - -suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { - test('bare named parameter support can be toggled', (t) => { - const db = new DatabaseSync(nextDb()); - 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.deepStrictEqual( - stmt.run({ k: 1, v: 2 }), - { changes: 1, lastInsertRowid: 1 }, - ); - t.assert.strictEqual(stmt.setAllowBareNamedParameters(false), undefined); - t.assert.throws(() => { - stmt.run({ k: 2, v: 4 }); - }, { - code: 'ERR_INVALID_STATE', - message: /Unknown named parameter 'k'/, - }); - t.assert.strictEqual(stmt.setAllowBareNamedParameters(true), undefined); - t.assert.deepStrictEqual( - stmt.run({ k: 3, v: 6 }), - { changes: 1, lastInsertRowid: 3 }, - ); - }); - - test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); - 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.setAllowBareNamedParameters(); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /The "allowBareNamedParameters" argument must be a boolean/, - }); - }); -}); - -suite('data binding and mapping', () => { - test('supported data types', (t) => { - const u8a = new TextEncoder().encode('a☃b☃c'); - const db = new DatabaseSync(nextDb()); - const setup = db.exec(` - CREATE TABLE types( - key INTEGER PRIMARY KEY, - int INTEGER, - double REAL, - text TEXT, - buf BLOB - ) STRICT; - `); - t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO types (key, int, double, text, buf) ' + - 'VALUES (?, ?, ?, ?, ?)'); - t.assert.deepStrictEqual( - stmt.run(1, 42, 3.14159, 'foo', u8a), - { changes: 1, lastInsertRowid: 1 }, - ); - t.assert.deepStrictEqual( - stmt.run(2, null, null, null, null), - { changes: 1, lastInsertRowid: 2 } - ); - t.assert.deepStrictEqual( - stmt.run(3, Number(8), Number(2.718), String('bar'), Buffer.from('x☃y☃')), - { changes: 1, lastInsertRowid: 3 }, - ); - t.assert.deepStrictEqual( - stmt.run(4, 99n, 0xf, '', new Uint8Array()), - { changes: 1, lastInsertRowid: 4 }, - ); - - const query = db.prepare('SELECT * FROM types WHERE key = ?'); - t.assert.deepStrictEqual(query.get(1), { - key: 1, - int: 42, - double: 3.14159, - text: 'foo', - buf: u8a, - }); - t.assert.deepStrictEqual(query.get(2), { - key: 2, - int: null, - double: null, - text: null, - buf: null, - }); - t.assert.deepStrictEqual(query.get(3), { - key: 3, - int: 8, - double: 2.718, - text: 'bar', - buf: new TextEncoder().encode('x☃y☃'), - }); - t.assert.deepStrictEqual(query.get(4), { - key: 4, - int: 99, - double: 0xf, - text: '', - buf: new Uint8Array(), - }); - }); - - test('unsupported data types', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec( - 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' - ); - t.assert.strictEqual(setup, undefined); - - [ - undefined, - () => {}, - Symbol(), - /foo/, - Promise.resolve(), - new Map(), - new Set(), - ].forEach((val) => { - t.assert.throws(() => { - db.prepare('INSERT INTO types (key, val) VALUES (?, ?)').run(1, val); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /Provided value cannot be bound to SQLite parameter 2/, - }); - }); - - t.assert.throws(() => { - const stmt = db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)'); - stmt.run({ $k: 1, $v: () => {} }); - }, { - code: 'ERR_INVALID_ARG_TYPE', - message: /Provided value cannot be bound to SQLite parameter 2/, - }); - }); - - test('throws when binding a BigInt that is too large', (t) => { - const max = 9223372036854775807n; // Largest 64-bit signed integer value. - const db = new DatabaseSync(nextDb()); - 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 (?, ?)'); - t.assert.deepStrictEqual( - stmt.run(1, max), - { changes: 1, lastInsertRowid: 1 }, - ); - t.assert.throws(() => { - stmt.run(1, max + 1n); - }, { - code: 'ERR_INVALID_ARG_VALUE', - message: /BigInt value is too large to bind/, - }); - }); - - test('statements are unbound on each call', (t) => { - const db = new DatabaseSync(nextDb()); - 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 (?, ?)'); - t.assert.deepStrictEqual( - stmt.run(1, 5), - { changes: 1, lastInsertRowid: 1 }, - ); - t.assert.deepStrictEqual( - stmt.run(), - { changes: 1, lastInsertRowid: 2 }, - ); - t.assert.deepStrictEqual( - db.prepare('SELECT * FROM data ORDER BY key').all(), - [{ key: 1, val: 5 }, { key: 2, val: null }], - ); - }); -}); - -suite('manual transactions', () => { - test('a transaction is committed', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec(` - CREATE TABLE data( - key INTEGER PRIMARY KEY - ) STRICT; - `); - t.assert.strictEqual(setup, undefined); - t.assert.deepStrictEqual( - db.prepare('BEGIN').run(), - { changes: 0, lastInsertRowid: 0 }, - ); - t.assert.deepStrictEqual( - db.prepare('INSERT INTO data (key) VALUES (100)').run(), - { changes: 1, lastInsertRowid: 100 }, - ); - t.assert.deepStrictEqual( - db.prepare('COMMIT').run(), - { changes: 1, lastInsertRowid: 100 }, - ); - t.assert.deepStrictEqual( - db.prepare('SELECT * FROM data').all(), - [{ key: 100 }], - ); - }); - - test('a transaction is rolled back', (t) => { - const db = new DatabaseSync(nextDb()); - const setup = db.exec(` - CREATE TABLE data( - key INTEGER PRIMARY KEY - ) STRICT; - `); - t.assert.strictEqual(setup, undefined); - t.assert.deepStrictEqual( - db.prepare('BEGIN').run(), - { changes: 0, lastInsertRowid: 0 }, - ); - t.assert.deepStrictEqual( - db.prepare('INSERT INTO data (key) VALUES (100)').run(), - { changes: 1, lastInsertRowid: 100 }, - ); - t.assert.deepStrictEqual( - db.prepare('ROLLBACK').run(), - { changes: 1, lastInsertRowid: 100 }, - ); - t.assert.deepStrictEqual(db.prepare('SELECT * FROM data').all(), []); - }); -}); - -suite('named parameters', () => { - test('throws on unknown named parameters', (t) => { - const db = new DatabaseSync(nextDb()); - 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(nextDb()); - 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(), - { key: 1, val: 9 }, - ); - }); - - test('duplicate bare named parameters are supported', (t) => { - const db = new DatabaseSync(nextDb()); - 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(), - { key: 1, val: 1 }, - ); - }); - - test('bare named parameters throw on ambiguous names', (t) => { - const db = new DatabaseSync(nextDb()); - 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\'.', - }); - }); -}); - test('ERR_SQLITE_ERROR is thrown for errors originating from SQLite', (t) => { const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE test( key INTEGER PRIMARY KEY @@ -747,6 +87,7 @@ test('in-memory databases are supported', (t) => { test('PRAGMAs are supported', (t) => { const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); t.assert.deepStrictEqual( db.prepare('PRAGMA journal_mode = WAL').get(), { journal_mode: 'wal' },