Files
node/test/parallel/test-sqlite-limits.js
Guilherme Araújo 5ebe0ebf65 sqlite: restore connection state on reopen
close() destroys the connection but keeps the DatabaseSync object, and
open() did not replay the state held on it.

An authorizer set with setAuthorizer() was not reinstalled, silently
dropping a deny-all policy. Limits written through db.limits.* reverted
to the constructor values. Extension loading was re-enabled from the
constructor ceiling rather than the current setting, so the connection
flag contradicted a previous enableLoadExtension(false), though
loadExtension() itself stayed blocked by its own check.

Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
Assisted-by: Claude Code
PR-URL: https://github.com/nodejs/node/pull/66042
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-10-01 05:39:07 +00:00

320 lines
8.5 KiB
JavaScript

'use strict';
const { skipIfSQLiteMissing } = require('../common');
skipIfSQLiteMissing();
const { Database } = require('node:sqlite');
const { suite, test } = require('node:test');
suite('Database limits', () => {
test('limits object has expected properties with positive values', (t) => {
const db = new Database(':memory:');
const expectedProperties = [
'length',
'sqlLength',
'column',
'exprDepth',
'compoundSelect',
'vdbeOp',
'functionArg',
'attach',
'likePatternLength',
'variableNumber',
'triggerDepth',
];
for (const prop of expectedProperties) {
t.assert.strictEqual(typeof db.limits[prop], 'number',
`${prop} should be a number`);
t.assert.ok(db.limits[prop] > 0,
`${prop} should be positive`);
}
});
test('constructor accepts limits option', (t) => {
const db = new Database(':memory:', {
limits: {
length: 500000,
sqlLength: 50000,
column: 100,
exprDepth: 50,
compoundSelect: 10,
vdbeOp: 10000,
functionArg: 8,
attach: 5,
likePatternLength: 1000,
variableNumber: 100,
triggerDepth: 5,
}
});
t.assert.strictEqual(db.limits.length, 500000);
t.assert.strictEqual(db.limits.sqlLength, 50000);
t.assert.strictEqual(db.limits.column, 100);
t.assert.strictEqual(db.limits.exprDepth, 50);
t.assert.strictEqual(db.limits.compoundSelect, 10);
t.assert.strictEqual(db.limits.vdbeOp, 10000);
t.assert.strictEqual(db.limits.functionArg, 8);
t.assert.strictEqual(db.limits.attach, 5);
t.assert.strictEqual(db.limits.likePatternLength, 1000);
t.assert.strictEqual(db.limits.variableNumber, 100);
t.assert.strictEqual(db.limits.triggerDepth, 5);
});
test('getter returns current limit value', (t) => {
const db = new Database(':memory:');
t.assert.strictEqual(typeof db.limits.length, 'number');
t.assert.ok(db.limits.length > 0);
t.assert.strictEqual(typeof db.limits.sqlLength, 'number');
t.assert.ok(db.limits.sqlLength > 0);
});
test('setter modifies limit value', (t) => {
const db = new Database(':memory:');
db.limits.length = 100000;
t.assert.strictEqual(db.limits.length, 100000);
db.limits.sqlLength = 50000;
t.assert.strictEqual(db.limits.sqlLength, 50000);
db.limits.column = 50;
t.assert.strictEqual(db.limits.column, 50);
});
test('Infinity resets limit to maximum', (t) => {
const db = new Database(':memory:');
const originalLength = db.limits.length;
// Set to a lower value
db.limits.length = 100;
t.assert.strictEqual(db.limits.length, 100);
// Reset to maximum using Infinity
db.limits.length = Infinity;
t.assert.strictEqual(db.limits.length, originalLength);
});
test('throws on invalid argument type', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.limits.length = 'invalid';
}, {
name: 'TypeError',
message: /Limit value must be a non-negative integer or Infinity/,
});
});
test('throws on negative value', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.limits.length = -1;
}, {
name: 'RangeError',
message: /Limit value must be non-negative/,
});
});
test('throws on null value', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.limits.length = null;
}, {
name: 'TypeError',
message: /Limit value must be a non-negative integer or Infinity/,
});
});
test('throws on negative Infinity', (t) => {
const db = new Database(':memory:');
t.assert.throws(() => {
db.limits.length = -Infinity;
}, {
name: 'TypeError',
message: /Limit value must be a non-negative integer or Infinity/,
});
});
test('throws on getter access after close', (t) => {
const db = new Database(':memory:');
db.close();
t.assert.throws(() => {
return db.limits.length;
}, {
code: 'ERR_INVALID_STATE',
message: /database is not open/,
});
});
test('throws on setter access after close', (t) => {
const db = new Database(':memory:');
db.close();
t.assert.throws(() => {
db.limits.length = 100;
}, {
code: 'ERR_INVALID_STATE',
message: /database is not open/,
});
});
test('limits object is enumerable', (t) => {
const db = new Database(':memory:');
const keys = Object.keys(db.limits);
t.assert.ok(keys.includes('length'));
t.assert.ok(keys.includes('sqlLength'));
t.assert.ok(keys.includes('column'));
t.assert.ok(keys.includes('exprDepth'));
t.assert.ok(keys.includes('compoundSelect'));
t.assert.ok(keys.includes('vdbeOp'));
t.assert.ok(keys.includes('functionArg'));
t.assert.ok(keys.includes('attach'));
t.assert.ok(keys.includes('likePatternLength'));
t.assert.ok(keys.includes('variableNumber'));
t.assert.ok(keys.includes('triggerDepth'));
});
test('throws on invalid limits option type', (t) => {
t.assert.throws(() => {
new Database(':memory:', { limits: 'invalid' });
}, {
name: 'TypeError',
message: /options\.limits.*must be an object/,
});
});
test('throws on invalid limit value type in constructor', (t) => {
t.assert.throws(() => {
new Database(':memory:', { limits: { length: 'invalid' } });
}, {
name: 'TypeError',
message: /options\.limits\.length.*must be an integer/,
});
});
test('throws on negative limit value in constructor', (t) => {
t.assert.throws(() => {
new Database(':memory:', { limits: { length: -100 } });
}, {
name: 'RangeError',
message: /options\.limits\.length.*must be non-negative/,
});
});
test('throws on Infinity limit value in constructor', (t) => {
t.assert.throws(() => {
new Database(':memory:', { limits: { length: Infinity } });
}, {
name: 'TypeError',
message: /options\.limits\.length.*must be an integer/,
});
});
test('partial limits in constructor', (t) => {
const db = new Database(':memory:', {
limits: {
length: 100000,
}
});
t.assert.strictEqual(db.limits.length, 100000);
t.assert.strictEqual(typeof db.limits.sqlLength, 'number');
});
test('throws when exceeding column limit', (t) => {
const db = new Database(':memory:', {
limits: {
column: 10,
}
});
db.exec('CREATE TABLE t1 (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)');
t.assert.throws(() => {
db.exec('CREATE TABLE t2 (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)');
}, {
message: /too many columns/,
});
});
test('throws when exceeding attach limit', (t) => {
const db = new Database(':memory:', {
limits: {
attach: 0,
}
});
t.assert.throws(() => {
db.exec("ATTACH DATABASE ':memory:' AS db1");
}, {
message: /too many attached databases/,
});
});
test('throws when exceeding variable number limit', (t) => {
const db = new Database(':memory:', {
limits: {
variableNumber: 2,
}
});
t.assert.throws(() => {
const stmt = db.prepare('SELECT ?, ?, ?');
stmt.all(1, 2, 3);
}, {
message: /too many SQL variables/,
});
});
test('throws when exceeding compound select limit', (t) => {
const db = new Database(':memory:', {
limits: {
compoundSelect: 1,
}
});
t.assert.throws(() => {
db.exec('SELECT 1 UNION SELECT 2 UNION SELECT 3');
}, {
message: /too many terms in compound SELECT/,
});
});
test('throws when exceeding function arg limit', (t) => {
const db = new Database(':memory:', {
limits: {
functionArg: 2,
}
});
t.assert.throws(() => {
db.exec('SELECT max(1, 2, 3)');
}, {
message: /too many arguments on function max/,
});
});
test('setter applies limit to SQLite immediately', (t) => {
const db = new Database(':memory:');
db.limits.attach = 0;
t.assert.throws(() => {
db.exec("ATTACH DATABASE ':memory:' AS db1");
}, {
message: /too many attached databases/,
});
});
test('limits set at runtime survive close() and open()', (t) => {
const db = new Database(':memory:');
db.limits.attach = 0;
db.close();
db.open();
t.assert.strictEqual(db.limits.attach, 0);
t.assert.throws(() => {
db.exec("ATTACH DATABASE ':memory:' AS db1");
}, {
message: /too many attached databases/,
});
});
});