Skip to content

Commit 19bbded

Browse files
authored
feat(vnext): unify built-in relation dialect policy (#189)
## Summary - add one package-owned relation dialect runtime for PostgreSQL, DuckDB, BigQuery, and Dremio - unify identifier decoding, CTE equality/prefix rules, query-path decoding, role-aware rendering, reserved words, and CTE/query recognizer policy - authenticate each runtime through the opaque built-in dialect handle while keeping callbacks private - add hostile-input bounds and fail-closed behavior, exact dialect corpora, production-backed recognizer tests, and bundle budgets ## Correctness and performance - changed production coverage: 97.10% statements, 96.18% branches, 100% functions, 97.05% lines - exact packed core: 15,028 gzip / 47,141 raw bytes under 16 KiB / 52 KiB limits - exact packed worker output: 125,937 gzip / 572,982 raw bytes under 128 KiB / 570 KiB limits - comparator hot paths use bounded allocation-free ASCII loops; private path scratch arrays are not copied or frozen ## Verification - 1,646 tests passed, 1 governed expected failure - all five TypeScript configurations passed - oxlint and test-integrity checks passed - browser suite passed in Chromium - exact tarball package smoke and SSR import passed - worker placement, strict CSP, lazy loading, and parser isolation passed - query-site and CTE benchmarks passed - exact head 6e80f2f approved independently by three adversarial reviewers Part of #169. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Unifies the relation dialect runtime across PostgreSQL, DuckDB, BigQuery, and Dremio with a single, package-owned policy for identifiers, CTEs, query paths, and role-aware rendering. Tightens the public API and enforces strict size budgets and hostile-input boundaries. Part of #169. - **New Features** - Added a unified `relation-dialect` runtime for completion, CTE layout, query-site policy, reserved words, and rendering across all built-ins. - Introduced fail-closed decoding with hostile-input bounds and exact corpora; added adversarial/production-backed tests. - Enforced bundle budgets (core ≤16 KiB gzip/52 KiB raw; worker ≤128 KiB gzip/570 KiB raw) and a smoke check to prevent dialect policy leaks. - **Refactors** - Public dialect handles (`bigQueryDialect()`, `dremioDialect()`, `duckdbDialect()`, `postgresDialect()`) now expose only `displayName` and `id`; callbacks are private and authenticated in `session`. - Migrated `cte-layout` and `query-site` to `..._SQL_RELATION_DIALECT` constants; added `MAX_CTE_QUOTED_IDENTIFIER_LENGTH`. - Simplified and hardened tests by removing ad-hoc dialects; updated worker placement and docs to report core/worker sizes. <sup>Written for commit 6e80f2f. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/189?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
1 parent d6ed03a commit 19bbded

16 files changed

Lines changed: 3339 additions & 401 deletions

scripts/package-smoke.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,22 @@ if (
296296
) {
297297
throw new Error("vNext package exports are incomplete");
298298
}
299+
for (const dialect of [
300+
vnext.bigQueryDialect(),
301+
vnext.dremioDialect(),
302+
vnext.duckdbDialect(),
303+
vnext.postgresDialect(),
304+
]) {
305+
if (
306+
Object.keys(dialect).join(",") !== "displayName,id" ||
307+
"decodeIdentifier" in dialect ||
308+
"grammar" in dialect ||
309+
"relationDialect" in dialect ||
310+
"renderRelationPath" in dialect
311+
) {
312+
throw new Error("vNext dialect implementation policy leaked publicly");
313+
}
314+
}
299315
if (!commonKeywords.keywords || !duckdbKeywords.keywords) {
300316
throw new Error("Keyword data exports are incomplete");
301317
}

scripts/worker-placement.mjs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ const PARSER_MARKERS = [
3535
"tableList",
3636
];
3737
const BIGQUERY_GZIP_LIMIT = 50 * 1024;
38+
const CORE_TOTAL_GZIP_LIMIT = 16 * 1024;
39+
const CORE_TOTAL_RAW_LIMIT = 52 * 1024;
3840
const POSTGRESQL_GZIP_LIMIT = 68 * 1024;
39-
const WORKER_TOTAL_GZIP_LIMIT = 120 * 1024;
41+
const WORKER_TOTAL_GZIP_LIMIT = 128 * 1024;
4042
const WORKER_TOTAL_RAW_LIMIT = 570 * 1024;
4143
const MIME_TYPES = new Map([
4244
[".css", "text/css; charset=utf-8"],
@@ -485,6 +487,27 @@ function verifyCoreExcludesParser(coreDirectory) {
485487
return new Set(moduleIds).size;
486488
}
487489

490+
function verifyCoreSize(coreDirectory) {
491+
const report = bundleReport(coreDirectory);
492+
if (report.gzipBytes > CORE_TOTAL_GZIP_LIMIT) {
493+
throw new Error(
494+
`Core output exceeded ${CORE_TOTAL_GZIP_LIMIT} gzip bytes: ${report.gzipBytes}`,
495+
);
496+
}
497+
if (report.rawBytes > CORE_TOTAL_RAW_LIMIT) {
498+
throw new Error(
499+
`Core output exceeded ${CORE_TOTAL_RAW_LIMIT} raw bytes: ${report.rawBytes}`,
500+
);
501+
}
502+
return {
503+
...report,
504+
limits: {
505+
gzipBytes: CORE_TOTAL_GZIP_LIMIT,
506+
rawBytes: CORE_TOTAL_RAW_LIMIT,
507+
},
508+
};
509+
}
510+
488511
function verifySsrImport(fixtureDirectory) {
489512
const source = `
490513
Object.defineProperty(globalThis, "window", {
@@ -751,6 +774,7 @@ try {
751774
const coreDirectory = join(fixtureDirectory, "core-dist");
752775
const workersDirectory = join(fixtureDirectory, "workers-dist");
753776
const coreModuleCount = verifyCoreExcludesParser(coreDirectory);
777+
const coreBundle = verifyCoreSize(coreDirectory);
754778
const chromiumResult = await runChromium(
755779
fixtureDirectory,
756780
workersDirectory,
@@ -763,7 +787,7 @@ try {
763787
}
764788
const report = {
765789
bundles: {
766-
core: bundleReport(coreDirectory),
790+
core: coreBundle,
767791
workers: workerBundles,
768792
},
769793
evidence: {

src/vnext/__tests__/cte-layout.bench.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,16 @@ import {
44
MAX_CTE_DECLARATIONS,
55
MAX_CTE_DEPTH,
66
MAX_CTE_FRAMES,
7-
type SqlCteLayoutDialect,
87
visibleSqlCtesAt,
98
} from "../cte-layout.js";
10-
import { DUCKDB_SQL_LEXICAL_PROFILE } from "../lexical.js";
9+
import { DUCKDB_SQL_RELATION_DIALECT } from "../relation-dialect.js";
1110
import { createIdentitySqlSource } from "../source.js";
1211
import {
1312
buildSqlStatementIndex,
1413
type ExactSqlStatementSlot,
1514
} from "../statement-index.js";
1615

17-
const dialect: SqlCteLayoutDialect = {
18-
classifyIdentifierToken: (rawIdentifier, quoted) => ({
19-
status: "identifier",
20-
value: {
21-
component: { quoted, value: rawIdentifier },
22-
},
23-
}),
24-
compareCteIdentifiers: (left, right) =>
25-
left.value.toLowerCase() === right.value.toLowerCase()
26-
? "equal"
27-
: "distinct",
28-
grammar: {
29-
declaredColumns: true,
30-
materialization: true,
31-
maximumDeclarationsPerFrame: MAX_CTE_DECLARATIONS,
32-
recursive: true,
33-
},
34-
lexicalProfile: DUCKDB_SQL_LEXICAL_PROFILE,
35-
};
16+
const dialect = DUCKDB_SQL_RELATION_DIALECT.cteLayout;
3617

3718
function fixture(text: string): {
3819
readonly source: ReturnType<typeof createIdentitySqlSource>;

src/vnext/__tests__/cte-layout.test.ts

Lines changed: 11 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
MAX_CTE_DEPTH,
66
MAX_CTE_FRAMES,
77
MAX_CTE_IDENTIFIER_LENGTH,
8+
MAX_CTE_QUOTED_IDENTIFIER_LENGTH,
89
MAX_CTE_STATEMENT_LENGTH,
910
type SqlCteLayout,
1011
type SqlCteLayoutDialect,
@@ -13,11 +14,11 @@ import {
1314
visibleSqlCtesAt,
1415
} from "../cte-layout.js";
1516
import {
16-
BIGQUERY_SQL_LEXICAL_PROFILE,
17-
DREMIO_SQL_LEXICAL_PROFILE,
18-
DUCKDB_SQL_LEXICAL_PROFILE,
19-
POSTGRESQL_SQL_LEXICAL_PROFILE,
20-
} from "../lexical.js";
17+
BIGQUERY_SQL_RELATION_DIALECT,
18+
DREMIO_SQL_RELATION_DIALECT,
19+
DUCKDB_SQL_RELATION_DIALECT,
20+
POSTGRESQL_SQL_RELATION_DIALECT,
21+
} from "../relation-dialect.js";
2122
import {
2223
createIdentitySqlSource,
2324
createMaskedSqlSource,
@@ -28,94 +29,10 @@ import {
2829
type ExactSqlStatementSlot,
2930
} from "../statement-index.js";
3031

31-
function decodeQuoted(raw: string): string {
32-
const quote = raw.at(0) ?? "";
33-
return raw
34-
.slice(1, -1)
35-
.replaceAll(`${quote}${quote}`, quote)
36-
.replaceAll(`\\${quote}`, quote);
37-
}
38-
39-
function isAscii(value: string): boolean {
40-
for (let index = 0; index < value.length; index += 1) {
41-
if (value.charCodeAt(index) > 0x7f) {
42-
return false;
43-
}
44-
}
45-
return true;
46-
}
47-
48-
function createDialect(
49-
kind: "bigquery" | "dremio" | "duckdb" | "postgresql",
50-
): SqlCteLayoutDialect {
51-
const lexicalProfile =
52-
kind === "bigquery"
53-
? BIGQUERY_SQL_LEXICAL_PROFILE
54-
: kind === "dremio"
55-
? DREMIO_SQL_LEXICAL_PROFILE
56-
: kind === "duckdb"
57-
? DUCKDB_SQL_LEXICAL_PROFILE
58-
: POSTGRESQL_SQL_LEXICAL_PROFILE;
59-
return {
60-
classifyIdentifierToken: (raw, quoted) => {
61-
const value = quoted ? decodeQuoted(raw) : raw;
62-
if (
63-
value.length === 0 ||
64-
(!quoted &&
65-
/^(?:as|materialized|not|recursive|select|with)$/i.test(
66-
value,
67-
))
68-
) {
69-
return { status: "unsupported" };
70-
}
71-
return {
72-
status: "identifier",
73-
value: {
74-
component: { quoted, value },
75-
},
76-
};
77-
},
78-
compareCteIdentifiers: (left, right) => {
79-
const leftKey =
80-
kind === "postgresql" && left.quoted
81-
? isAscii(left.value)
82-
? left.value
83-
: null
84-
: isAscii(left.value)
85-
? left.value.toLowerCase()
86-
: null;
87-
const rightKey =
88-
kind === "postgresql" && right.quoted
89-
? isAscii(right.value)
90-
? right.value
91-
: null
92-
: isAscii(right.value)
93-
? right.value.toLowerCase()
94-
: null;
95-
if (leftKey !== null && rightKey !== null) {
96-
return leftKey === rightKey ? "equal" : "distinct";
97-
}
98-
return left.quoted === right.quoted &&
99-
left.value === right.value
100-
? "equal"
101-
: "unknown";
102-
},
103-
grammar: {
104-
declaredColumns: kind !== "bigquery",
105-
materialization:
106-
kind === "duckdb" || kind === "postgresql",
107-
maximumDeclarationsPerFrame:
108-
kind === "dremio" ? 1 : MAX_CTE_DECLARATIONS,
109-
recursive: kind !== "dremio",
110-
},
111-
lexicalProfile,
112-
};
113-
}
114-
115-
const postgres = createDialect("postgresql");
116-
const duckdb = createDialect("duckdb");
117-
const bigquery = createDialect("bigquery");
118-
const dremio = createDialect("dremio");
32+
const postgres = POSTGRESQL_SQL_RELATION_DIALECT.cteLayout;
33+
const duckdb = DUCKDB_SQL_RELATION_DIALECT.cteLayout;
34+
const bigquery = BIGQUERY_SQL_RELATION_DIALECT.cteLayout;
35+
const dremio = DREMIO_SQL_RELATION_DIALECT.cteLayout;
11936

12037
function analyze(
12138
text: string,
@@ -787,7 +704,7 @@ describe("bounded CTE layout", () => {
787704
expect(calls).toBe(0);
788705

789706
const oversizedQuoted = `"${"a".repeat(
790-
MAX_CTE_IDENTIFIER_LENGTH * 2 + 1,
707+
MAX_CTE_QUOTED_IDENTIFIER_LENGTH,
791708
)}"`;
792709
expectPartial(
793710
`WITH ${oversizedQuoted} AS (SELECT 1) SELECT 1`,

src/vnext/__tests__/query-site.bench.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
11
import { bench, describe } from "vitest";
22
import {
33
recognizeSqlRelationQuerySite,
4-
type SqlQuerySiteDialect,
54
} from "../query-site.js";
5+
import { DUCKDB_SQL_RELATION_DIALECT } from "../relation-dialect.js";
66
import { createIdentitySqlSource } from "../source.js";
77
import {
88
buildSqlStatementIndex,
9-
DUCKDB_SQL_LEXICAL_PROFILE,
109
findSqlStatementSlot,
1110
} from "../statement-index.js";
1211

13-
const dialect: SqlQuerySiteDialect = {
14-
classifyIdentifierToken: (rawIdentifier) => ({
15-
status: "identifier",
16-
value: rawIdentifier,
17-
}),
18-
decodeRelationPath: (rawPath, cursorOffset) => ({
19-
finalSegment: { from: 0, to: rawPath.length },
20-
prefix: {
21-
quoted: false,
22-
value: rawPath.slice(0, cursorOffset),
23-
},
24-
qualifier: [],
25-
quality: "exact",
26-
status: "decoded",
27-
}),
28-
lexicalProfile: DUCKDB_SQL_LEXICAL_PROFILE,
29-
maximumPathDepth: 16,
30-
supportsNaturalJoin: true,
31-
};
12+
const dialect = DUCKDB_SQL_RELATION_DIALECT.querySite;
3213
const TEN_KIBIBYTES = 10 * 1_024;
3314
const queryPrefix = "SELECT ";
3415
const querySuffix = " FROM schema_prefix";

0 commit comments

Comments
 (0)