Skip to content

Commit c096a16

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

15 files changed

Lines changed: 1331 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: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function analyze(
5555
expect(slot?.boundaryQuality).toBe("exact");
5656
const result = analyzeSqlCteLayout(
5757
source,
58+
index,
5859
slot as ExactSqlStatementSlot,
5960
dialect,
6061
);
@@ -68,15 +69,18 @@ function analyze(
6869
function analyzeRaw(
6970
text: string,
7071
dialect: SqlCteLayoutDialect = postgres,
72+
indexDialect: SqlCteLayoutDialect = dialect,
7173
): SqlCteLayout {
7274
const source = createIdentitySqlSource(text);
73-
const slot = buildSqlStatementIndex(
75+
const index = buildSqlStatementIndex(
7476
source.analysisText,
75-
dialect.lexicalProfile,
76-
).slots[0];
77+
indexDialect.lexicalProfile,
78+
);
79+
const slot = index.slots[0];
7780
expect(slot?.boundaryQuality).toBe("exact");
7881
return analyzeSqlCteLayout(
7982
source,
83+
index,
8084
slot as ExactSqlStatementSlot,
8185
dialect,
8286
);
@@ -400,6 +404,7 @@ describe("bounded CTE layout", () => {
400404
}
401405
const layout = analyzeSqlCteLayout(
402406
source,
407+
index,
403408
slot,
404409
postgres,
405410
);
@@ -1022,7 +1027,9 @@ describe("bounded CTE layout", () => {
10221027
},
10231028
},
10241029
};
1025-
expect(analyzeRaw("SELECT 1", getterLexicalProfile)).toEqual({
1030+
expect(
1031+
analyzeRaw("SELECT 1", getterLexicalProfile, postgres),
1032+
).toEqual({
10261033
reason: "resource-limit",
10271034
status: "unavailable",
10281035
});

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
);

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

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { bench, describe } from "vitest";
2+
import {
3+
analyzeSqlCteLayout,
4+
MAX_CTE_DECLARATIONS,
5+
} from "../cte-layout.js";
26
import {
37
recognizeSqlRelationQuerySite,
48
} from "../query-site.js";
59
import { DUCKDB_SQL_RELATION_DIALECT } from "../relation-dialect.js";
10+
import {
11+
recognizeSqlRelationQuerySiteWithCteLayout,
12+
} from "../relation-query-site.js";
613
import { createIdentitySqlSource } from "../source.js";
714
import {
815
buildSqlStatementIndex,
@@ -61,6 +68,103 @@ const usingHeavySlot = findSqlStatementSlot(
6168
usingHeavyPosition,
6269
"left",
6370
);
71+
const cteQueryPrefix =
72+
"WITH cte_name AS (SELECT 1) SELECT ";
73+
const cteQuerySuffix = " FROM schema_prefix";
74+
const cteProjectedListLength =
75+
TEN_KIBIBYTES -
76+
cteQueryPrefix.length -
77+
cteQuerySuffix.length;
78+
const cteProjectedList = `${"x,".repeat(
79+
Math.floor((cteProjectedListLength - 1) / 2),
80+
)}x`;
81+
const tenKilobyteCteQuery =
82+
`${cteQueryPrefix}${cteProjectedList}` +
83+
`${" ".repeat(
84+
cteProjectedListLength - cteProjectedList.length,
85+
)}${cteQuerySuffix}`;
86+
if (tenKilobyteCteQuery.length !== TEN_KIBIBYTES) {
87+
throw new Error("CTE query benchmark fixture must be exactly 10 KiB");
88+
}
89+
const cteSource = createIdentitySqlSource(tenKilobyteCteQuery);
90+
const cteIndex = buildSqlStatementIndex(
91+
cteSource.analysisText,
92+
dialect.lexicalProfile,
93+
);
94+
const ctePosition = tenKilobyteCteQuery.length;
95+
const cteSlot = findSqlStatementSlot(
96+
cteIndex,
97+
ctePosition,
98+
"left",
99+
);
100+
if (cteSlot.boundaryQuality === "opaque") {
101+
throw new Error("CTE query benchmark requires an exact statement");
102+
}
103+
const maximumCteQuery =
104+
`WITH ${Array.from(
105+
{ length: MAX_CTE_DECLARATIONS },
106+
(_, index) => `cte_${index} AS (SELECT ${index})`,
107+
).join(", ")} SELECT * FROM target`;
108+
const maximumCteSource =
109+
createIdentitySqlSource(maximumCteQuery);
110+
const maximumCteIndex = buildSqlStatementIndex(
111+
maximumCteSource.analysisText,
112+
dialect.lexicalProfile,
113+
);
114+
const maximumCtePosition = maximumCteQuery.length;
115+
const maximumCteSlot = findSqlStatementSlot(
116+
maximumCteIndex,
117+
maximumCtePosition,
118+
"left",
119+
);
120+
if (maximumCteSlot.boundaryQuality === "opaque") {
121+
throw new Error("Maximum CTE benchmark requires an exact statement");
122+
}
123+
const ctePreflightLayout = analyzeSqlCteLayout(
124+
cteSource,
125+
cteIndex,
126+
cteSlot,
127+
DUCKDB_SQL_RELATION_DIALECT.cteLayout,
128+
);
129+
const ctePreflightResult =
130+
recognizeSqlRelationQuerySiteWithCteLayout(
131+
cteSource,
132+
cteSlot,
133+
ctePosition,
134+
DUCKDB_SQL_RELATION_DIALECT,
135+
ctePreflightLayout,
136+
);
137+
if (
138+
ctePreflightLayout.status !== "ready" ||
139+
ctePreflightResult.status !== "ready" ||
140+
ctePreflightResult.anchor !== "from"
141+
) {
142+
throw new Error("CTE query benchmark must exercise a ready FROM site");
143+
}
144+
const maximumCtePreflightLayout = analyzeSqlCteLayout(
145+
maximumCteSource,
146+
maximumCteIndex,
147+
maximumCteSlot,
148+
DUCKDB_SQL_RELATION_DIALECT.cteLayout,
149+
);
150+
const maximumCtePreflightResult =
151+
recognizeSqlRelationQuerySiteWithCteLayout(
152+
maximumCteSource,
153+
maximumCteSlot,
154+
maximumCtePosition,
155+
DUCKDB_SQL_RELATION_DIALECT,
156+
maximumCtePreflightLayout,
157+
);
158+
if (
159+
maximumCtePreflightLayout.status !== "ready" ||
160+
maximumCtePreflightLayout.declarations.length !==
161+
MAX_CTE_DECLARATIONS ||
162+
maximumCtePreflightLayout.mainQueryEntrypoints.length !== 1 ||
163+
maximumCtePreflightResult.status !== "ready" ||
164+
maximumCtePreflightResult.prefix.value !== "target"
165+
) {
166+
throw new Error("Maximum CTE benchmark must exercise all declarations");
167+
}
64168

65169
describe("query-site recognizer", () => {
66170
bench("10 KiB active statement", () => {
@@ -84,4 +188,36 @@ describe("query-site recognizer", () => {
84188
dialect,
85189
);
86190
});
191+
192+
bench("10 KiB WITH main-query double scan", () => {
193+
const layout = analyzeSqlCteLayout(
194+
cteSource,
195+
cteIndex,
196+
cteSlot,
197+
DUCKDB_SQL_RELATION_DIALECT.cteLayout,
198+
);
199+
recognizeSqlRelationQuerySiteWithCteLayout(
200+
cteSource,
201+
cteSlot,
202+
ctePosition,
203+
DUCKDB_SQL_RELATION_DIALECT,
204+
layout,
205+
);
206+
});
207+
208+
bench("256 CTE main-query double scan", () => {
209+
const layout = analyzeSqlCteLayout(
210+
maximumCteSource,
211+
maximumCteIndex,
212+
maximumCteSlot,
213+
DUCKDB_SQL_RELATION_DIALECT.cteLayout,
214+
);
215+
recognizeSqlRelationQuerySiteWithCteLayout(
216+
maximumCteSource,
217+
maximumCteSlot,
218+
maximumCtePosition,
219+
DUCKDB_SQL_RELATION_DIALECT,
220+
layout,
221+
);
222+
});
87223
});

0 commit comments

Comments
 (0)