fix(editor): complete MySQL table names after DESC

Closes #6668
This commit is contained in:
t8y2
2026-08-19 12:41:29 +00:00
parent 7a707e77d8
commit 4e74f77cc8
2 changed files with 47 additions and 2 deletions
@@ -1,5 +1,14 @@
import { describe, expect, it } from "vitest";
import { buildPostgresSequenceLiteralCompletionItems, buildSelectStarExpansion, buildSqlCompletionItems, getPostgresSequenceLiteralCompletionContext, getSqlCompletionContext, selectStarResultColumnsMatch, shouldAutoOpenSqlCompletion } from "@/lib/sql/sqlCompletion";
import {
buildPostgresSequenceLiteralCompletionItems,
buildSelectStarExpansion,
buildSqlCompletionItems,
buildSqlCompletionItemsFromContext,
getPostgresSequenceLiteralCompletionContext,
getSqlCompletionContext,
selectStarResultColumnsMatch,
shouldAutoOpenSqlCompletion,
} from "@/lib/sql/sqlCompletion";
import { sqlCompletionContextFromSemantic } from "@/lib/sql/semantic/completion";
import { buildSqlSemanticModel } from "@/lib/sql/semantic/model";
import { originForSqlCompletionProvider, originForTypedSqlCompletionStart, shouldAllowSqlCompletionTrigger, type SqlCompletionTriggerFacts } from "@/lib/sql/sqlCompletionTriggerPolicy";
@@ -17,6 +26,38 @@ describe("sqlCompletion keyword snippets", () => {
});
});
describe("MySQL DESCRIBE table completion", () => {
it.each(["DESC", "DESCRIBE"])("treats %s as a table-name context", (keyword) => {
const sql = `${keyword} ord`;
const options = { databaseType: "mysql", dialect: "mysql" } as const;
const context = sqlCompletionContextFromSemantic(buildSqlSemanticModel(sql, sql.length, options), getSqlCompletionContext(sql, sql.length, options));
expect(context.suggestTables).toBe(true);
expect(shouldAutoOpenSqlCompletion(sql, sql.length, options)).toBe(true);
expect(
buildSqlCompletionItemsFromContext(context, {
tables: [{ name: "orders" }],
columnsByTable: new Map(),
...options,
}),
).toEqual(expect.arrayContaining([expect.objectContaining({ label: "orders", type: "table" })]));
});
it("does not treat PostgreSQL ORDER BY DESC as a table-name context", () => {
const sql = "SELECT * FROM users ORDER BY name DESC ord";
const options = { databaseType: "postgres", dialect: "postgres" } as const;
const context = sqlCompletionContextFromSemantic(buildSqlSemanticModel(sql, sql.length, options), getSqlCompletionContext(sql, sql.length, options));
expect(context.suggestTables).toBe(false);
expect(
buildSqlCompletionItemsFromContext(context, {
tables: [{ name: "orders" }],
columnsByTable: new Map(),
...options,
}).some((item) => item.type === "table"),
).toBe(false);
});
});
describe("PostgreSQL sequence literal completion", () => {
it.each(["nextval", "currval", "setval"])("recognizes the first %s regclass literal", (functionName) => {
const sql = `SELECT ${functionName}('public.order_`;
+5 -1
View File
@@ -624,6 +624,10 @@ const JOIN_MODIFIER_KEYWORD_PHRASES = ["LEFT JOIN", "RIGHT JOIN", "INNER JOIN",
const MAX_TABLE_COMPLETION_ITEMS = 200;
const EXACT_LABEL_MATCH_BOOST = 10000;
function isTableTriggerKeyword(keyword: string, options: SqlSemanticBuildOptions): boolean {
return TABLE_TRIGGER_KEYWORDS.has(keyword) || (keyword === "desc" && resolveSqlDialectId(options) === "mysql");
}
// Keywords that only make sense in DDL / statement-start contexts (not inside SELECT/INSERT/UPDATE/DELETE)
const DDL_ONLY_KEYWORDS = new Set([
"CREATE",
@@ -2136,7 +2140,7 @@ export function getSqlCompletionContext(sql: string, cursor: number, options: Sq
const deleteInfo = detectDeleteCompletionContext(beforeCursor);
const oracleTableFunctionContext = detectOracleTableFunctionContext(beforeCursor);
const afterTableTrigger = TABLE_TRIGGER_KEYWORDS.has(lastWord) || (JOIN_MODIFIERS.has(lastWord) && isFollowedByJoin(beforeToken)) || isInTableListContext(beforeToken);
const afterTableTrigger = isTableTriggerKeyword(lastWord, options) || (JOIN_MODIFIERS.has(lastWord) && isFollowedByJoin(beforeToken)) || isInTableListContext(beforeToken);
const exclusiveTableSuggestions = EXCLUSIVE_TABLE_TRIGGER_KEYWORDS.has(lastWord) || (JOIN_MODIFIERS.has(lastWord) && isFollowedByJoin(beforeToken)) || isInTableListContext(beforeToken);
const tableAliasAfterCursor = hasTableAliasAfterCursor(sql, cursor);
const autoAliasTableCompletions = (lastWord === "from" || lastWord === "join" || (JOIN_MODIFIERS.has(lastWord) && isFollowedByJoin(beforeToken)) || isInTableListContext(beforeToken)) && !tableAliasAfterCursor;