fix(editor): complete columns through table names

This commit is contained in:
zipg 2026-07-30 00:08:24 +08:00 committed by GitHub
parent 87ef514188
commit 62aeae5de4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 25 additions and 1 deletions

View File

@ -204,6 +204,19 @@ describe("semantic SQL completion candidates", () => {
expect(items.filter((item) => item.type === "column").map((item) => item.label)).toEqual(["id", "name", "email"]);
});
it.each([
["table name", "SELECT orders.| FROM orders"],
["schema-qualified table name", "SELECT public.orders.| FROM public.orders"],
] as const)("completes PostgreSQL columns through a %s qualifier", (_label, markedSql) => {
const columnsByTable = new Map<string, SqlCompletionColumn[]>([["orders", ["id", "customer_name", "total_amount"].map((name) => ({ name, table: "orders", schema: "public" }))]]);
const { context, items } = semanticCompletion(markedSql, { columnsByTable }, { databaseType: "postgres", dialect: "postgres" });
expect(context.contextKind).toBe("alias_column");
expect(context.exclusiveColumnSuggestions).toBe(true);
expect(items.filter((item) => item.type === "column").map((item) => item.label)).toEqual(["id", "customer_name", "total_amount"]);
});
it.each([
["PostgreSQL", "postgres", "postgres"],
["SQL Server", "sqlserver", "sqlserver"],

View File

@ -80,6 +80,17 @@ describe("sqlSemanticModel baseline fixtures", () => {
expect(model.cursorIntent).toEqual(expect.objectContaining({ kind: "alias_column", qualifierParts: ["b"] }));
});
it.each([
["table name", "SELECT orders.| FROM orders", ["orders"]],
["schema-qualified table name", "SELECT public.orders.| FROM public.orders", ["public", "orders"]],
] as const)("resolves columns through a PostgreSQL %s qualifier before FROM", (_label, markedSql, qualifierParts) => {
const { sql, cursor } = sqlFixtureCursor(markedSql);
const model = buildSqlSemanticModel(sql, cursor, { databaseType: "postgres", dialect: "postgres" });
expect(model.rowSources).toEqual(expect.arrayContaining([expect.objectContaining({ name: "orders", alias: undefined })]));
expect(model.cursorIntent).toEqual(expect.objectContaining({ kind: "alias_column", qualifierParts, targetSourceId: model.rowSources[0]?.id }));
});
it("keeps nested EXISTS sources and outer correlated sources visible", () => {
const { sql, cursor } = sqlFixtureCursor("SELECT * FROM aa.tb t WHERE EXISTS (SELECT 1 FROM aa.tb1 t1, aa.tb2 t2 WHERE t1.|)");
const model = buildSqlSemanticModel(sql, cursor, { databaseType: "mysql", dialect: "mysql" });

View File

@ -713,7 +713,7 @@ function buildCursorIntent(tokens: readonly SqlSemanticToken[], cursor: number,
TABLE_INTRODUCERS.has(previous) ||
TABLE_INTRODUCERS.has(wordBeforeReplacement) ||
TABLE_INTRODUCERS.has(wordBeforeTrailing) ||
(!!targetSource && !targetSource.alias && trailing.replacementRange.start <= targetSource.sourceSpan.end + 1 && TABLE_INTRODUCERS.has(wordBeforePosition(tokens, targetSource.sourceSpan.start))))
(!!targetSource && !targetSource.alias && trailing.replacementRange.start >= targetSource.sourceSpan.start && trailing.replacementRange.start <= targetSource.sourceSpan.end + 1 && TABLE_INTRODUCERS.has(wordBeforePosition(tokens, targetSource.sourceSpan.start))))
) {
const role = dialect.qualifierRole(trailing.qualifierParts, "table");
return { kind: role === "catalog" ? "catalog" : "table", prefix: trailing.prefix, replacementRange: trailing.replacementRange, qualifierParts: trailing.qualifierParts, expectedObjectKinds: ["table", "view"], confidence: "medium" };