Files
node/test/parallel/test-sqlite-serialize.js
Guilherme Araújo f2b698f1cf sqlite: rename DatabaseSync and StatementSync
Rename the DatabaseSync and StatementSync classes to Database and
Statement, and the internal DatabaseSyncLimits helper to
DatabaseLimits. The old names are kept as aliases of the new classes
and are Documentation-only deprecated (DEP0210, DEP0211).

Assisted-by: Claude Code
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65988
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
2026-09-26 00:03:45 +00:00

323 lines
9.7 KiB
JavaScript

'use strict';
const { skipIfSQLiteMissing } = require('../common');
skipIfSQLiteMissing();
const { Database } = require('node:sqlite');
const { suite, test } = require('node:test');
suite('Database.prototype.serialize()', () => {
test('returns a Uint8Array with the SQLite header', (t) => {
const db = new Database(':memory:');
const buf = db.serialize();
t.assert.ok(buf instanceof Uint8Array);
t.assert.ok(buf.length > 0);
const header = new TextDecoder().decode(buf.slice(0, 15));
t.assert.strictEqual(header, 'SQLite format 3');
db.close();
});
test('serializes an empty database', (t) => {
const db = new Database(':memory:');
const buf = db.serialize();
t.assert.ok(buf instanceof Uint8Array);
t.assert.ok(buf.length > 0);
db.close();
});
test('serializes a database with data', (t) => {
const db = new Database(':memory:');
db.exec('CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT)');
db.exec("INSERT INTO t VALUES (1, 'hello')");
db.exec("INSERT INTO t VALUES (2, 'world')");
const buf = db.serialize();
t.assert.ok(buf.length > 0);
db.close();
});
test('throws if the database is not open', (t) => {
const db = new Database(':memory:');
db.close();
t.assert.throws(() => {
db.serialize();
}, {
code: 'ERR_INVALID_STATE',
message: /database is not open/,
});
});
test('throws if dbName is not a string', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.serialize(123);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "dbName" argument must be a string/,
});
db.close();
});
test('accepts a schema name argument', (t) => {
const db = new Database(':memory:');
const buf = db.serialize('main');
t.assert.ok(buf instanceof Uint8Array);
t.assert.ok(buf.length > 0);
db.close();
});
test('serializes an attached schema when dbName is provided', (t) => {
const db = new Database(':memory:');
db.exec("ATTACH DATABASE ':memory:' AS aux");
db.exec('CREATE TABLE aux.t(value TEXT)');
db.exec("INSERT INTO aux.t VALUES ('from aux')");
const buf = db.serialize('aux');
db.close();
const clone = new Database(':memory:');
clone.deserialize(buf);
const row = clone.prepare('SELECT value FROM t').get();
t.assert.strictEqual(row.value, 'from aux');
clone.close();
});
});
suite('Database.prototype.deserialize()', () => {
test('loads a serialized database', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT)');
db1.exec("INSERT INTO t VALUES (1, 'hello')");
db1.exec("INSERT INTO t VALUES (2, 'world')");
const buf = db1.serialize();
db1.close();
const db2 = new Database(':memory:');
db2.deserialize(buf);
const rows = db2.prepare('SELECT * FROM t ORDER BY id').all();
t.assert.strictEqual(rows.length, 2);
t.assert.strictEqual(rows[0].name, 'hello');
t.assert.strictEqual(rows[1].name, 'world');
db2.close();
});
test('replaces existing data in the connection', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE src(val TEXT)');
db1.exec("INSERT INTO src VALUES ('from source')");
const buf = db1.serialize();
db1.close();
const db2 = new Database(':memory:');
db2.exec('CREATE TABLE old(x INTEGER)');
db2.exec('INSERT INTO old VALUES (999)');
db2.deserialize(buf);
t.assert.throws(() => {
db2.prepare('SELECT * FROM old').all();
}, /no such table: old/);
const rows = db2.prepare('SELECT * FROM src').all();
t.assert.strictEqual(rows.length, 1);
t.assert.strictEqual(rows[0].val, 'from source');
db2.close();
});
test('finalizes existing prepared statements before replacing the database',
(t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE replacement(value TEXT)');
db1.exec("INSERT INTO replacement VALUES ('new')");
const buf = db1.serialize();
db1.close();
const db2 = new Database(':memory:');
db2.exec('CREATE TABLE original(value TEXT)');
db2.exec("INSERT INTO original VALUES ('old')");
const stmt = db2.prepare('SELECT value FROM original');
t.assert.strictEqual(stmt.get().value, 'old');
db2.deserialize(buf);
t.assert.throws(() => {
stmt.get();
}, /statement has been finalized/);
const row = db2.prepare('SELECT value FROM replacement').get();
t.assert.strictEqual(row.value, 'new');
db2.close();
});
test('deserialized database is writable by default', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE t(id INTEGER PRIMARY KEY)');
const buf = db1.serialize();
db1.close();
const db2 = new Database(':memory:');
db2.deserialize(buf);
db2.exec('INSERT INTO t VALUES (1)');
const rows = db2.prepare('SELECT * FROM t').all();
t.assert.strictEqual(rows.length, 1);
db2.close();
});
test('round-trip serialize then deserialize preserves data', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE t(a TEXT, b REAL, c BLOB)');
db1.prepare('INSERT INTO t VALUES (?, ?, ?)').run(
'text', 3.14, new Uint8Array([1, 2, 3])
);
const buf = db1.serialize();
const db2 = new Database(':memory:');
db2.deserialize(buf);
const row = db2.prepare('SELECT * FROM t').get();
t.assert.strictEqual(row.a, 'text');
t.assert.strictEqual(row.b, 3.14);
t.assert.deepStrictEqual(row.c, new Uint8Array([1, 2, 3]));
db1.close();
db2.close();
});
test('throws if the database is not open', (t) => {
const db = new Database(':memory:');
db.close();
t.assert.throws(() => {
db.deserialize(new Uint8Array(0));
}, {
code: 'ERR_INVALID_STATE',
message: /database is not open/,
});
});
test('throws if called while in a callback', (t) => {
const source = new Database(':memory:');
const serialized = source.serialize();
source.close();
const db = new Database(':memory:');
t.after(() => db.close());
db.function('deserialize_database', () => db.deserialize(serialized));
const stmt = db.prepare('SELECT deserialize_database()');
t.assert.throws(() => stmt.get(), {
code: 'ERR_INVALID_STATE',
message: 'database cannot be deserialized while in a callback',
});
t.assert.strictEqual(db.isOpen, true);
});
test('throws if buffer argument is not a Uint8Array', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.deserialize('not a buffer');
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "buffer" argument must be a Uint8Array/,
});
db.close();
});
test('throws if buffer is empty', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.deserialize(new Uint8Array(0));
}, {
code: 'ERR_INVALID_ARG_VALUE',
message: /The "buffer" argument must not be empty/,
});
db.close();
});
test('throws if options is not an object', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.deserialize(new Uint8Array(1), 'bad');
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options" argument must be an object/,
});
db.close();
});
test('throws if options.dbName is not a string', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.deserialize(new Uint8Array(1), { dbName: 1 });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.dbName" argument must be a string/,
});
db.close();
});
test('accepts a Buffer as input', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE t(x INTEGER)');
db1.exec('INSERT INTO t VALUES (42)');
const buf = Buffer.from(db1.serialize());
db1.close();
const db2 = new Database(':memory:');
db2.deserialize(buf);
const row = db2.prepare('SELECT * FROM t').get();
t.assert.strictEqual(row.x, 42);
db2.close();
});
test('multiple deserialize calls on the same connection', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE a(x)');
db1.exec("INSERT INTO a VALUES ('first')");
const buf1 = db1.serialize();
db1.close();
const db2 = new Database(':memory:');
db2.exec('CREATE TABLE b(x)');
db2.exec("INSERT INTO b VALUES ('second')");
const buf2 = db2.serialize();
db2.close();
const db3 = new Database(':memory:');
db3.deserialize(buf1);
t.assert.strictEqual(
db3.prepare('SELECT x FROM a').get().x, 'first'
);
db3.deserialize(buf2);
t.assert.throws(() => {
db3.prepare('SELECT * FROM a').all();
}, /no such table: a/);
t.assert.strictEqual(
db3.prepare('SELECT x FROM b').get().x, 'second'
);
db3.close();
});
test('loads into an attached schema when options.dbName is provided', (t) => {
const db1 = new Database(':memory:');
db1.exec("ATTACH DATABASE ':memory:' AS aux");
db1.exec('CREATE TABLE aux.t(value TEXT)');
db1.exec("INSERT INTO aux.t VALUES ('from aux')");
const buf = db1.serialize('aux');
db1.close();
const db2 = new Database(':memory:');
db2.exec('CREATE TABLE main_t(value TEXT)');
db2.exec("INSERT INTO main_t VALUES ('from main')");
db2.exec("ATTACH DATABASE ':memory:' AS aux");
db2.deserialize(buf, { dbName: 'aux' });
t.assert.strictEqual(
db2.prepare('SELECT value FROM main_t').get().value,
'from main',
);
t.assert.strictEqual(
db2.prepare('SELECT value FROM aux.t').get().value,
'from aux',
);
db2.close();
});
});