Skip to content

Commit 9ca185f

Browse files
committed
feat(vnext): authenticate CTE main query sites
1 parent 19bbded commit 9ca185f

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)