diff --git a/src/analytics/sql/statement-guard.ts b/src/analytics/sql/statement-guard.ts index 2255b8f..c9ba31c 100644 --- a/src/analytics/sql/statement-guard.ts +++ b/src/analytics/sql/statement-guard.ts @@ -38,6 +38,15 @@ * table reference is checked positively against the tenant's catalog. * * Outside string literals the statement must be ASCII. Homoglyph and * zero-width tricks against keyword matching stop being expressible. + * * The FROM/JOIN scan is CONSERVATIVE where it is unsure, and that costs + * something worth naming: ClickHouse's `ARRAY JOIN arr` is a legitimate + * construct whose target is a COLUMN, not a table, and this guard refuses + * it because the token after JOIN is neither a CTE nor a qualified object. + * A false refusal an analyst can see and report is the right side of that + * trade; a false ADMISSION is not. Supporting it means teaching the scan + * which JOIN flavours take a non-table target, which is the direction that + * grows holes, so it stays refused until someone needs it and can add it + * WITH a corpus entry proving the narrowing did not open anything else. * * Comments and string literals are removed BEFORE any keyword scan, using * ClickHouse's own lexical rules (`--` and `#` to end of line, `/* *\/` * non-nesting, `'` with backslash and doubled-quote escapes, `"` and diff --git a/tests/analytics/duckdb-provider.test.ts b/tests/analytics/duckdb-provider.test.ts index d6ddcc1..9039821 100644 --- a/tests/analytics/duckdb-provider.test.ts +++ b/tests/analytics/duckdb-provider.test.ts @@ -217,6 +217,12 @@ describe('BI-P014 — the query builder has no caller-SQL surface', () => { }); it("escapes a literal by DOUBLING the quote, which is DuckDB's only escape", () => { + // MEASURED on DuckDB 1.5.5 rather than assumed, because the whole escaping + // strategy rests on it: `SELECT length('a\\b')` returns 4, so a backslash + // is a literal character; and `SELECT 'a\''` is a PARSER ERROR + // ("unterminated quoted string"), which is what a non-escaping backslash + // produces. A dialect where `\'` closed the literal would make doubling + // insufficient. expect(escapeLiteral("O'Brien")).toBe("O''Brien"); // A backslash is NOT an escape inside a DuckDB single-quoted string, so it // must survive untouched rather than being "helpfully" doubled.