Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions docs/adr/0005-parser-independent-relation-completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,20 @@ embedded region are unavailable or inactive according to the closed result.
CTE visibility is the one narrow scope-semantics exception; general
parser-derived scope semantics remain deferred.

The recognizer may cross a `USING` join constraint only after authenticating
the complete bounded grammar `USING(identifier [, identifier ...])` with
dialect-owned identifier validation. It does not interpret `ON` expressions:
encountering `ON` makes the query site unavailable until a future
parser-backed or separately specified expression recognizer can prove the
boundary.

The conformance corpus includes positive base `FROM`, qualified prefix,
aliased `JOIN`, same-depth comma, and nested supported-query cases. It includes
aliased `JOIN`, authenticated `USING`, same-depth comma, and nested
supported-query cases. It includes
negative `IS DISTINCT FROM`, `substring(... FROM ...)`, `extract(... FROM
...)`, `DELETE FROM`, `COPY ... FROM`, set-operation, `QUALIFY`, `WINDOW`, join
constraint, DML, and expression cases. A keyword match alone never creates a
site.
...)`, `DELETE FROM`, `COPY ... FROM`, set-operation, `QUALIFY`, `WINDOW`,
`ON`, malformed `USING`, DML, and expression cases. A keyword match alone
never creates a site.

The result distinguishes:

Expand Down Expand Up @@ -518,7 +526,7 @@ The initial checked limits are:
| Lexical tokens | 16,384 |
| Parenthesis/query depth | 128 |
| CTE declarations | 256 |
| Identifier path segments | 4 |
| Identifier path segments | 32 global ceiling; dialect runtime sets the checked limit |
| Identifier segment | 256 UTF-16 units |
| Catalog scope | 512 UTF-16 units |
| Search paths | 32 |
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"test:worker-placement": "node ./scripts/worker-placement.mjs",
"test:integrity": "node ./scripts/check-test-integrity.mjs",
"bench:parser-adapter": "vitest bench --run src/vnext/__tests__/node-sql-parser-adapter.bench.ts",
"bench:query-site": "vitest bench --run src/vnext/__tests__/query-site.bench.ts",
"bench:statement-index": "vitest bench --run src/vnext/__tests__/statement-index.bench.ts",
"test:package": "node ./scripts/clean.mjs && tsc && node ./scripts/package-smoke.mjs",
"demo": "vite build",
Expand Down
106 changes: 106 additions & 0 deletions src/vnext/__tests__/query-site.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { bench, describe } from "vitest";
import {
recognizeSqlRelationQuerySite,
type SqlQuerySiteDialect,
} from "../query-site.js";
import { createIdentitySqlSource } from "../source.js";
import {
buildSqlStatementIndex,
DUCKDB_SQL_LEXICAL_PROFILE,
findSqlStatementSlot,
} from "../statement-index.js";

const dialect: SqlQuerySiteDialect = {
classifyIdentifierToken: (rawIdentifier) => ({
status: "identifier",
value: rawIdentifier,
}),
decodeRelationPath: (rawPath, cursorOffset) => ({
finalSegment: { from: 0, to: rawPath.length },
prefix: {
quoted: false,
value: rawPath.slice(0, cursorOffset),
},
qualifier: [],
quality: "exact",
status: "decoded",
}),
lexicalProfile: DUCKDB_SQL_LEXICAL_PROFILE,
maximumPathDepth: 16,
supportsNaturalJoin: true,
};
const TEN_KIBIBYTES = 10 * 1_024;
const queryPrefix = "SELECT ";
const querySuffix = " FROM schema_prefix";
const projectedListLength =
TEN_KIBIBYTES - queryPrefix.length - querySuffix.length;
const projectedList = `${"x,".repeat(
Math.floor((projectedListLength - 1) / 2),
)}x`;
const tenKilobyteQuery = `${queryPrefix}${projectedList}${" ".repeat(
projectedListLength - projectedList.length,
)}${querySuffix}`;
if (tenKilobyteQuery.length !== TEN_KIBIBYTES) {
throw new Error("Query benchmark fixture must be exactly 10 KiB");
}
const source = createIdentitySqlSource(tenKilobyteQuery);
const index = buildSqlStatementIndex(
source.analysisText,
dialect.lexicalProfile,
);
const position = tenKilobyteQuery.length;
const slot = findSqlStatementSlot(index, position, "left");
const aliasHeavyQuery = `SELECT * FROM ${Array.from(
{ length: 1_000 },
(_, aliasIndex) => `table_name alias_${aliasIndex}`,
).join(", ")}, `;
const aliasHeavySource = createIdentitySqlSource(aliasHeavyQuery);
const aliasHeavyIndex = buildSqlStatementIndex(
aliasHeavySource.analysisText,
dialect.lexicalProfile,
);
const aliasHeavyPosition = aliasHeavyQuery.length;
const aliasHeavySlot = findSqlStatementSlot(
aliasHeavyIndex,
aliasHeavyPosition,
"left",
);
const usingHeavyQuery = `SELECT * FROM first_table JOIN second_table USING(${Array.from(
{ length: 1_000 },
(_, columnIndex) => `column_${columnIndex}`,
).join(", ")}) JOIN target`;
const usingHeavySource = createIdentitySqlSource(usingHeavyQuery);
const usingHeavyIndex = buildSqlStatementIndex(
usingHeavySource.analysisText,
dialect.lexicalProfile,
);
const usingHeavyPosition = usingHeavyQuery.length;
const usingHeavySlot = findSqlStatementSlot(
usingHeavyIndex,
usingHeavyPosition,
"left",
);

describe("query-site recognizer", () => {
bench("10 KiB active statement", () => {
recognizeSqlRelationQuerySite(source, slot, position, dialect);
});

bench("1,000 classified aliases", () => {
recognizeSqlRelationQuerySite(
aliasHeavySource,
aliasHeavySlot,
aliasHeavyPosition,
dialect,
);
});

bench("1,000 authenticated USING columns", () => {
recognizeSqlRelationQuerySite(
usingHeavySource,
usingHeavySlot,
usingHeavyPosition,
dialect,
);
});
});
Loading
Loading