fix(mysql): complete database-qualified columns

This commit is contained in:
t8y2 committed 2026-06-15 23:35:11 +08:00
1 parent 3574113feb
commit cce7fcc600
3 files changed
+189 -12

No files matched your search

+45 -11
View File
@@ -1063,6 +1063,7 @@ export type SqlStatementKind = "select" | "insert" | "update" | "delete" | "crea
export interface SqlCompletionContext {
prefix: string;
qualifier?: string;
qualifierParts?: string[];
suggestTables: boolean;
suggestColumns: boolean;
suggestKeywords: boolean;
@@ -1496,6 +1497,7 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
const trailingIdentifier = parseTrailingIdentifierContext(beforeCursor);
const prefix = trailingIdentifier?.prefix ?? "";
const qualifier = trailingIdentifier?.qualifier;
const qualifierParts = trailingIdentifier?.qualifierParts;
const bareStart = trailingIdentifier?.start ?? beforeCursor.length;
const beforeToken = beforeCursor.slice(0, Math.max(0, bareStart)).trimEnd();
const lastWord = /([A-Za-z_][\w$]*)$/.exec(beforeToken)?.[1]?.toLowerCase() ?? "";
@@ -1546,6 +1548,7 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
return {
prefix,
qualifier: insertInfo ? undefined : qualifier,
qualifierParts: insertInfo ? undefined : qualifierParts,
suggestTables: insertInfo ? false : afterTableTrigger,
suggestColumns: !!qualifier || !!updateInfo?.inSetClause || (inColumnContext && referencedTables.length > 0),
suggestKeywords: !exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && !inCallRoutineContext,
@@ -1572,7 +1575,7 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
};
}
function parseTrailingIdentifierContext(input: string): { start: number; prefix: string; qualifier?: string } | null {
function parseTrailingIdentifierContext(input: string): { start: number; prefix: string; qualifier?: string; qualifierParts?: string[] } | null {
if (/\s$/.test(input)) return null;
let i = input.length - 1;
while (i >= 0 && /\s/.test(input[i] ?? "")) i--;
@@ -1606,6 +1609,7 @@ function parseTrailingIdentifierContext(input: string): { start: number; prefix:
start,
prefix: prefixPart,
qualifier: qualifierValue || undefined,
qualifierParts: qualifierParts.length > 0 ? qualifierParts : undefined,
};
}
@@ -2589,16 +2593,9 @@ function buildColumnItems(context: SqlCompletionContext, columnsByTable: Map<str
} else if (context.qualifier) {
const q = context.qualifier;
const qLower = q.toLowerCase();
const relatedTables = context.referencedTables.filter((table) => table.alias === q || table.alias?.toLowerCase() === qLower || table.name === q || table.name.toLowerCase() === qLower);
const tableNameSet = new Set(relatedTables.map((t) => t.name.toLowerCase()));
const tableKeys = new Set<string>();
for (const table of relatedTables) {
tableKeys.add(table.name);
if (table.schema) {
tableKeys.add(`${table.schema}.${table.name}`);
}
}
relevantCols = allColumns.filter((c) => tableNameSet.has(c.table.toLowerCase()) || tableKeys.has(c.key));
const qualifiedTarget = qualifiedTableTargetFromContext(context);
const relatedTables = context.referencedTables.filter((table) => referencedTableMatchesColumnQualifier(table, q, qLower, qualifiedTarget));
relevantCols = allColumns.filter((column) => relatedTables.some((table) => columnMatchesReferencedTable(column, table)) || (!!qualifiedTarget && columnMatchesQualifiedTable(column, qualifiedTarget)));
}
// Count name frequencies to detect duplicates across tables
@@ -2646,6 +2643,43 @@ function buildColumnItems(context: SqlCompletionContext, columnsByTable: Map<str
.sort(compareCompletionItems);
}
function qualifiedTableTargetFromContext(context: SqlCompletionContext): { schema: string; table: string } | null {
const parts = context.qualifierParts ?? context.qualifier?.split(".").filter(Boolean) ?? [];
if (parts.length < 2) return null;
const table = parts[parts.length - 1];
const schema = parts[parts.length - 2];
if (!schema || !table) return null;
return { schema, table };
}
function referencedTableMatchesColumnQualifier(table: SqlCompletionReferencedTable, qualifier: string, qualifierLower: string, qualifiedTarget: { schema: string; table: string } | null): boolean {
if (table.alias === qualifier || table.alias?.toLowerCase() === qualifierLower) return true;
if (table.name === qualifier || table.name.toLowerCase() === qualifierLower) return true;
if (!qualifiedTarget) return false;
if (normalizeIdentifierPart(table.name) !== normalizeIdentifierPart(qualifiedTarget.table)) return false;
return !table.schema || normalizeIdentifierPart(table.schema) === normalizeIdentifierPart(qualifiedTarget.schema);
}
function columnMatchesReferencedTable(column: SqlCompletionColumn & { key: string }, table: SqlCompletionReferencedTable): boolean {
if (normalizeIdentifierPart(column.table) !== normalizeIdentifierPart(table.name)) return false;
if (!table.schema) return true;
return columnMatchesQualifiedTable(column, { schema: table.schema, table: table.name });
}
function columnMatchesQualifiedTable(column: SqlCompletionColumn & { key: string }, target: { schema: string; table: string }): boolean {
if (normalizeIdentifierPart(column.table) !== normalizeIdentifierPart(target.table)) return false;
if (column.schema && normalizeIdentifierPart(column.schema) === normalizeIdentifierPart(target.schema)) return true;
return normalizeCompletionKey(column.key) === normalizeCompletionKey(`${target.schema}.${target.table}`);
}
function normalizeCompletionKey(key: string): string {
return key
.split(".")
.filter(Boolean)
.map((part) => normalizeIdentifierPart(part))
.join(".");
}
function buildColumnApply(column: SqlCompletionColumn & { displayLabel: string }, context: SqlCompletionContext, dialect?: "mysql" | "postgres" | "sqlserver"): string {
if (context.qualifier || column.displayLabel === column.name || !column.displayLabel.includes(".")) {
return quoteSqlIdentifier(column.name, dialect);