| // Flags: --experimental-sqlite |
| '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 { suite, test } = require('node:test'); |
| let cnt = 0; |
| |
| tmpdir.refresh(); |
| |
| function nextDb() { |
| return join(tmpdir.path, `database-${cnt++}.db`); |
| } |
| |
| suite('accessing the node:sqlite module', () => { |
| test('cannot be accessed without the node: scheme', (t) => { |
| t.assert.throws(() => { |
| require('sqlite'); |
| }, { |
| code: 'MODULE_NOT_FOUND', |
| message: /Cannot find module 'sqlite'/, |
| }); |
| }); |
| |
| test('cannot be accessed without --experimental-sqlite flag', async (t) => { |
| const { |
| stdout, |
| stderr, |
| code, |
| signal, |
| } = await spawnPromisified(process.execPath, [ |
| '-e', |
| 'require("node:sqlite")', |
| ]); |
| |
| t.assert.strictEqual(stdout, ''); |
| t.assert.match(stderr, /No such built-in module: node:sqlite/); |
| t.assert.notStrictEqual(code, 0); |
| t.assert.strictEqual(signal, null); |
| }); |
| }); |
| |
| 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()); |
| const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); |
| 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/, |
| }); |
| }); |
| }); |
| |
| 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()); |
| const setup = db.exec(` |
| CREATE TABLE test( |
| key INTEGER PRIMARY KEY |
| ) STRICT; |
| `); |
| t.assert.strictEqual(setup, undefined); |
| const stmt = db.prepare('INSERT INTO test (key) VALUES (?)'); |
| t.assert.deepStrictEqual(stmt.run(1), { changes: 1, lastInsertRowid: 1 }); |
| t.assert.throws(() => { |
| stmt.run(1); |
| }, { |
| code: 'ERR_SQLITE_ERROR', |
| message: 'UNIQUE constraint failed: test.key', |
| errcode: 1555, |
| errstr: 'constraint failed', |
| }); |
| }); |
| |
| test('in-memory databases are supported', (t) => { |
| const db1 = new DatabaseSync(':memory:'); |
| const db2 = new DatabaseSync(':memory:'); |
| const setup1 = db1.exec(` |
| CREATE TABLE data(key INTEGER PRIMARY KEY); |
| INSERT INTO data (key) VALUES (1); |
| `); |
| const setup2 = db2.exec(` |
| CREATE TABLE data(key INTEGER PRIMARY KEY); |
| INSERT INTO data (key) VALUES (1); |
| `); |
| t.assert.strictEqual(setup1, undefined); |
| t.assert.strictEqual(setup2, undefined); |
| t.assert.deepStrictEqual( |
| db1.prepare('SELECT * FROM data').all(), |
| [{ key: 1 }] |
| ); |
| t.assert.deepStrictEqual( |
| db2.prepare('SELECT * FROM data').all(), |
| [{ key: 1 }] |
| ); |
| }); |
| |
| test('PRAGMAs are supported', (t) => { |
| const db = new DatabaseSync(nextDb()); |
| t.assert.deepStrictEqual( |
| db.prepare('PRAGMA journal_mode = WAL').get(), |
| { journal_mode: 'wal' }, |
| ); |
| t.assert.deepStrictEqual( |
| db.prepare('PRAGMA journal_mode').get(), |
| { journal_mode: 'wal' }, |
| ); |
| }); |