Skip to content

Commit 1dc15d8

Browse files
authored
feat(vnext): authenticate CTE main query sites (#190)
## Summary - authenticate bounded CTE main-query entrypoints before the relation query-site recognizer consumes them - unify source, statement-index, slot, layout, and built-in runtime provenance so mixed or stale evidence fails closed - keep the low-level query-site recognizer independent of CTE and aggregate runtime modules through a one-way orchestration layer - preserve independent old/new statement contexts when incremental indexes reuse immutable slot identities - validate real ready paths in the CTE/query-site benchmarks ## Correctness and trust boundaries - package-created sources, statement indexes, exact slots, CTE layouts, and coherent dialect runtimes are authenticated by identity - each statement index privately owns its exact analysis text, frozen lexical-profile scalar snapshot, and bounded slot membership - foreign sources, indexes, slots, profiles, layouts, mixed runtimes, mutable structural copies, and hostile proxies return unavailable - incomplete and invalid CTE headers remain fail-closed while valid main queries work across PostgreSQL, DuckDB, BigQuery, and Dremio ## Verification - 1,671 tests passed and 1 expected failure remained expected - changed coverage: 96.99% statements, 95.95% branches, 100% functions, 96.96% lines - all five TypeScript configurations passed - oxlint passed with zero warnings or errors - test-integrity and diff checks passed - exact tarball package smoke, SSR import, strict CSP, worker placement, and bundle budgets passed - Chromium browser suite passed: 4 files / 7 tests - statement-index, CTE-layout, and query-site benchmarks passed - exact SHA c096a16 received three independent adversarial approvals after five review/fix loops Part of #169. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Authenticate CTE main‑query entrypoints and only recognize relation query sites when the source, statement index, slot, and dialect all match. Ambiguous evidence fails closed, keeping the core recognizer independent; part of #169. - **New Features** - Authenticated CTE layouts: `analyzeSqlCteLayout(source, index, slot, dialect)` records provenance and binds main‑query entrypoints to the exact source/index/slot/dialect; `resolveAuthenticatedSqlCteEntrypoints(...)` exposes them safely. - Orchestration: `recognizeSqlRelationQuerySiteWithCteLayout(...)` validates runtime/source/slot via `isSqlRelationDialectRuntime`, `isSqlSourceSnapshot`, and `isSqlStatementSlotSnapshot`, then feeds authenticated entrypoints into the query‑site recognizer; runtimes are registered with `registerSqlRelationDialectRuntime`. - Entrypoint‑aware query‑site: `recognizeSqlRelationQuerySiteWithEntrypoints(...)` matches SELECTs at the exact depth/offset for nested CTEs. - Statement index provenance: `buildSqlStatementIndex` snapshots the lexical profile; `isExactSqlStatementSlotSnapshot(For)` ensures slots belong to the index and text/profile; `updateSqlStatementIndex` rebuilds when text or lexical profile changes. - **Migration** - Pass the statement index into `analyzeSqlCteLayout` (new signature: `(source, index, slot, dialect)`). - Use `recognizeSqlRelationQuerySiteWithCteLayout` when combining CTE layout with relation query‑site; keep `recognizeSqlRelationQuerySite` for non‑CTE cases. - Use built‑in registered runtimes from `relation-dialect` and authentic sources/slots produced by our builders. <sup>Written for commit 9ca185f. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/190?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 19bbded commit 1dc15d8

15 files changed

Lines changed: 1379 additions & 43 deletions

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ const dialect = DUCKDB_SQL_RELATION_DIALECT.cteLayout;
1717

1818
function fixture(text: string): {
1919
readonly source: ReturnType<typeof createIdentitySqlSource>;
20+
readonly index: ReturnType<typeof buildSqlStatementIndex>;
2021
readonly slot: ExactSqlStatementSlot;
2122
} {
2223
const source = createIdentitySqlSource(text);
23-
const slot = buildSqlStatementIndex(
24+
const index = buildSqlStatementIndex(
2425
source.analysisText,
2526
dialect.lexicalProfile,
26-
).slots[0];
27+
);
28+
const slot = index.slots[0];
2729
if (!slot || slot.boundaryQuality !== "exact") {
2830
throw new Error("CTE benchmark fixture requires an exact statement");
2931
}
30-
return { slot, source };
32+
return { index, slot, source };
3133
}
3234

3335
const tenKibibytes = 10 * 1_024;
@@ -62,6 +64,7 @@ const bareFrameHeavyText = `SELECT ${Array.from(
6264
const bareFrameHeavyFixture = fixture(bareFrameHeavyText);
6365
const depthLayout = analyzeSqlCteLayout(
6466
depthHeavyFixture.source,
67+
depthHeavyFixture.index,
6568
depthHeavyFixture.slot,
6669
dialect,
6770
);
@@ -70,6 +73,7 @@ if (depthLayout.status !== "ready") {
7073
}
7174
const projectedLayout = analyzeSqlCteLayout(
7275
declarationHeavyFixture.source,
76+
declarationHeavyFixture.index,
7377
declarationHeavyFixture.slot,
7478
dialect,
7579
);
@@ -81,6 +85,7 @@ describe("CTE layout", () => {
8185
bench("ordinary 10 KiB statement", () => {
8286
analyzeSqlCteLayout(
8387
ordinaryFixture.source,
88+
ordinaryFixture.index,
8489
ordinaryFixture.slot,
8590
dialect,
8691
);
@@ -89,6 +94,7 @@ describe("CTE layout", () => {
8994
bench("256 declarations", () => {
9095
analyzeSqlCteLayout(
9196
declarationHeavyFixture.source,
97+
declarationHeavyFixture.index,
9298
declarationHeavyFixture.slot,
9399
dialect,
94100
);
@@ -97,6 +103,7 @@ describe("CTE layout", () => {
97103
bench("128-depth nested CTE", () => {
98104
analyzeSqlCteLayout(
99105
depthHeavyFixture.source,
106+
depthHeavyFixture.index,
100107
depthHeavyFixture.slot,
101108
dialect,
102109
);
@@ -105,6 +112,7 @@ describe("CTE layout", () => {
105112
bench("256 sequential incomplete frames", () => {
106113
analyzeSqlCteLayout(
107114
bareFrameHeavyFixture.source,
115+
bareFrameHeavyFixture.index,
108116
bareFrameHeavyFixture.slot,
109117
dialect,
110118
);

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

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
MAX_CTE_IDENTIFIER_LENGTH,
88
MAX_CTE_QUOTED_IDENTIFIER_LENGTH,
99
MAX_CTE_STATEMENT_LENGTH,
10+
resolveAuthenticatedSqlCteEntrypoints,
1011
type SqlCteLayout,
1112
type SqlCteLayoutDialect,
1213
type SqlCteLayoutIssue,
@@ -55,6 +56,7 @@ function analyze(
5556
expect(slot?.boundaryQuality).toBe("exact");
5657
const result = analyzeSqlCteLayout(
5758
source,
59+
index,
5860
slot as ExactSqlStatementSlot,
5961
dialect,
6062
);
@@ -68,15 +70,18 @@ function analyze(
6870
function analyzeRaw(
6971
text: string,
7072
dialect: SqlCteLayoutDialect = postgres,
73+
indexDialect: SqlCteLayoutDialect = dialect,
7174
): SqlCteLayout {
7275
const source = createIdentitySqlSource(text);
73-
const slot = buildSqlStatementIndex(
76+
const index = buildSqlStatementIndex(
7477
source.analysisText,
75-
dialect.lexicalProfile,
76-
).slots[0];
78+
indexDialect.lexicalProfile,
79+
);
80+
const slot = index.slots[0];
7781
expect(slot?.boundaryQuality).toBe("exact");
7882
return analyzeSqlCteLayout(
7983
source,
84+
index,
8085
slot as ExactSqlStatementSlot,
8186
dialect,
8287
);
@@ -400,6 +405,7 @@ describe("bounded CTE layout", () => {
400405
}
401406
const layout = analyzeSqlCteLayout(
402407
source,
408+
index,
403409
slot,
404410
postgres,
405411
);
@@ -1022,13 +1028,60 @@ describe("bounded CTE layout", () => {
10221028
},
10231029
},
10241030
};
1025-
expect(analyzeRaw("SELECT 1", getterLexicalProfile)).toEqual({
1031+
expect(
1032+
analyzeRaw("SELECT 1", getterLexicalProfile, postgres),
1033+
).toEqual({
10261034
reason: "resource-limit",
10271035
status: "unavailable",
10281036
});
10291037
expect(getterInvoked).toBe(false);
10301038
});
10311039

1040+
it("never reads a raw dialect after validating its data descriptors", () => {
1041+
const text = "WITH local AS (SELECT 1) SELECT * FROM local";
1042+
const source = createIdentitySqlSource(text);
1043+
const index = buildSqlStatementIndex(
1044+
source.analysisText,
1045+
postgres.lexicalProfile,
1046+
);
1047+
const slot = index.slots[0];
1048+
expect(slot?.boundaryQuality).toBe("exact");
1049+
if (!slot || slot.boundaryQuality !== "exact") {
1050+
throw new Error("Expected one exact statement slot");
1051+
}
1052+
1053+
let rawRead = false;
1054+
const descriptorOnlyDialect = new Proxy(postgres, {
1055+
get(target, property, receiver) {
1056+
if (property === "lexicalProfile") {
1057+
rawRead = true;
1058+
throw new Error("hostile");
1059+
}
1060+
return Reflect.get(target, property, receiver);
1061+
},
1062+
});
1063+
const layout = analyzeSqlCteLayout(
1064+
source,
1065+
index,
1066+
slot,
1067+
descriptorOnlyDialect,
1068+
);
1069+
1070+
expect(layout.status).toBe("ready");
1071+
if (layout.status === "unavailable") {
1072+
throw new Error("Expected an authenticated CTE layout");
1073+
}
1074+
expect(
1075+
resolveAuthenticatedSqlCteEntrypoints(
1076+
layout,
1077+
source,
1078+
slot,
1079+
descriptorOnlyDialect,
1080+
),
1081+
).toEqual(layout.mainQueryEntrypoints);
1082+
expect(rawRead).toBe(false);
1083+
});
1084+
10321085
it("projects only proven frame phases and validates positions", () => {
10331086
const text =
10341087
"WITH a AS (SELECT 1) SELECT * FROM a";

src/vnext/__tests__/incremental-statement-index.test.ts

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
buildSqlStatementIndex,
55
DREMIO_SQL_LEXICAL_PROFILE,
66
DUCKDB_SQL_LEXICAL_PROFILE,
7+
isExactSqlStatementSlotSnapshotFor,
78
MAX_SQL_STATEMENT_SLOTS,
89
POSTGRESQL_SQL_LEXICAL_PROFILE,
910
type SqlLexicalProfile,
@@ -358,18 +359,93 @@ describe("incremental statement index reuse", () => {
358359
},
359360
);
360361

362+
it("rebuilds when empty change metadata does not match the text", () => {
363+
const previousIndex = buildSqlStatementIndex(
364+
"SELECT 1",
365+
POSTGRESQL_SQL_LEXICAL_PROFILE,
366+
);
367+
const nextIndex = updateSqlStatementIndex(
368+
previousIndex,
369+
"SELECT 2",
370+
[],
371+
POSTGRESQL_SQL_LEXICAL_PROFILE,
372+
);
373+
expect(nextIndex).not.toBe(previousIndex);
374+
expect(nextIndex).toEqual(
375+
buildSqlStatementIndex(
376+
"SELECT 2",
377+
POSTGRESQL_SQL_LEXICAL_PROFILE,
378+
),
379+
);
380+
});
381+
382+
it("rebuilds when the lexical profile changes", () => {
383+
const text = "SELECT 1";
384+
const previousIndex = buildSqlStatementIndex(
385+
text,
386+
DUCKDB_SQL_LEXICAL_PROFILE,
387+
);
388+
const nextIndex = updateSqlStatementIndex(
389+
previousIndex,
390+
text,
391+
[],
392+
POSTGRESQL_SQL_LEXICAL_PROFILE,
393+
);
394+
expect(nextIndex).not.toBe(previousIndex);
395+
expect(nextIndex).toEqual(
396+
buildSqlStatementIndex(
397+
text,
398+
POSTGRESQL_SQL_LEXICAL_PROFILE,
399+
),
400+
);
401+
});
402+
403+
it("rebuilds when one lexical profile object mutates", () => {
404+
const profile = { ...POSTGRESQL_SQL_LEXICAL_PROFILE };
405+
const text = "SELECT # ;\n SELECT 2";
406+
const previousIndex = buildSqlStatementIndex(text, profile);
407+
expect(previousIndex.slots).toHaveLength(2);
408+
profile.hashLineComments = true;
409+
const nextIndex = updateSqlStatementIndex(
410+
previousIndex,
411+
text,
412+
[],
413+
profile,
414+
);
415+
expect(nextIndex).not.toBe(previousIndex);
416+
expect(nextIndex).toEqual(buildSqlStatementIndex(text, profile));
417+
expect(nextIndex.slots).toHaveLength(1);
418+
});
419+
361420
it("reuses unchanged prefix and zero-delta suffix slot identities", () => {
362421
const profile = DUCKDB_SQL_LEXICAL_PROFILE;
363422
const text = "SELECT 1; SELECT 2; SELECT 3";
364423
const change = replaceFirst(text, "2", "9");
365-
const { nextIndex, previousIndex } = expectIncrementalMatchesOracle(
366-
text,
367-
[change],
368-
profile,
369-
);
370-
expect(expectSlot(nextIndex.slots, 0)).toBe(
371-
expectSlot(previousIndex.slots, 0),
372-
);
424+
const { nextIndex, nextText, previousIndex } =
425+
expectIncrementalMatchesOracle(
426+
text,
427+
[change],
428+
profile,
429+
);
430+
const sharedPrefix = expectSlot(nextIndex.slots, 0);
431+
expect(sharedPrefix).toBe(expectSlot(previousIndex.slots, 0));
432+
expect(sharedPrefix.boundaryQuality).toBe("exact");
433+
expect(
434+
isExactSqlStatementSlotSnapshotFor(
435+
previousIndex,
436+
sharedPrefix,
437+
text,
438+
profile,
439+
),
440+
).toBe(true);
441+
expect(
442+
isExactSqlStatementSlotSnapshotFor(
443+
nextIndex,
444+
sharedPrefix,
445+
nextText,
446+
profile,
447+
),
448+
).toBe(true);
373449
expect(expectSlot(nextIndex.slots, 1)).not.toBe(
374450
expectSlot(previousIndex.slots, 1),
375451
);

0 commit comments

Comments
 (0)