mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
fix(plugins): hold exec scripts to the time budget between statements
The time budget covered query() and tx() only. exec() without bound args handed the plugin's script to Database#exec, which runs any number of statements in one call with nothing checked between them, so a long script held the host loop as long as it liked. An exec script now runs one statement at a time. sql-script.ts cuts it after every semicolon outside a string, a quoted identifier or a comment; a piece SQLite reports as incomplete input (a trigger body cut at its inner semicolon) is joined with the next until it prepares. The clock is read after every statement, and past the budget the call throws. As with Database#exec, the statements before that point stay applied, and an error is SQLite's own; a transaction the script opened itself is rolled back so the connection is not left inside it. exec() with bound args is one statement and stays as it was. A single statement cannot be bounded at all on the host thread, a write such as INSERT ... SELECT over a cross join included, and the SDK README and the wiki now say so next to the cross-join read. migrate() keeps running unbudgeted on purpose: a migration stopped part of the way is rolled back and fails the same way on every later start, leaving the plugin unable to update its schema. The ctx.db rows of the SDK README and the plugin wiki pages describe the budget for scripts, and the lines touched there and in plugin-sdk's db comment lose their em dashes.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* splitAfterSemicolons: where a plugin's exec() script is cut into statements, so
|
||||
* the time budget can be read between them (plugin-data.service.ts).
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { splitAfterSemicolons } from '../../../src/nest/plugins/host/sql-script';
|
||||
|
||||
describe('splitAfterSemicolons', () => {
|
||||
it('SQLSCRIPT-001 cuts after each statement and keeps every character', () => {
|
||||
const sql = 'CREATE TABLE a (x); INSERT INTO a VALUES (1);\nSELECT 1';
|
||||
const pieces = splitAfterSemicolons(sql);
|
||||
expect(pieces).toEqual(['CREATE TABLE a (x);', ' INSERT INTO a VALUES (1);', '\nSELECT 1']);
|
||||
expect(pieces.join('')).toBe(sql);
|
||||
});
|
||||
|
||||
it('SQLSCRIPT-002 leaves the semicolons in strings, quoted identifiers and comments alone', () => {
|
||||
expect(splitAfterSemicolons("INSERT INTO t VALUES ('a;b', 'it''s;');")).toEqual(["INSERT INTO t VALUES ('a;b', 'it''s;');"]);
|
||||
expect(splitAfterSemicolons('SELECT "x;y", `p;q`, [r;s] FROM t;')).toEqual(['SELECT "x;y", `p;q`, [r;s] FROM t;']);
|
||||
expect(splitAfterSemicolons('SELECT 1; -- one; two\nSELECT 2;')).toEqual(['SELECT 1;', ' -- one; two\nSELECT 2;']);
|
||||
expect(splitAfterSemicolons('SELECT 1 /* a; b */;')).toEqual(['SELECT 1 /* a; b */;']);
|
||||
});
|
||||
|
||||
it('SQLSCRIPT-003 cuts inside a trigger body, which the caller joins back up', () => {
|
||||
expect(splitAfterSemicolons('CREATE TRIGGER t AFTER INSERT ON a BEGIN SELECT 1; END;')).toEqual([
|
||||
'CREATE TRIGGER t AFTER INSERT ON a BEGIN SELECT 1;',
|
||||
' END;',
|
||||
]);
|
||||
});
|
||||
|
||||
it('SQLSCRIPT-004 runs an unterminated string, identifier or comment to the end', () => {
|
||||
expect(splitAfterSemicolons("SELECT 'open; still")).toEqual(["SELECT 'open; still"]);
|
||||
expect(splitAfterSemicolons('SELECT [open; still')).toEqual(['SELECT [open; still']);
|
||||
expect(splitAfterSemicolons('SELECT 1 -- trailing; comment')).toEqual(['SELECT 1 -- trailing; comment']);
|
||||
expect(splitAfterSemicolons('SELECT 1 /* open; comment')).toEqual(['SELECT 1 /* open; comment']);
|
||||
});
|
||||
|
||||
it('SQLSCRIPT-005 gives nothing for an empty script and a piece per stray semicolon', () => {
|
||||
expect(splitAfterSemicolons('')).toEqual([]);
|
||||
expect(splitAfterSemicolons(';;')).toEqual([';', ';']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user