fix(sql): improve completion ranking and fuzzy table lookup

This commit is contained in:
t8y2 committed 2026-06-01 14:04:54 +08:00
1 parent ce411f27e9
commit 4b00bf8215
3 files changed
+205 -62

No files matched your search

+20 -17
View File
@@ -759,8 +759,9 @@ export function shouldAutoOpenSqlCompletion(sql: string, cursor: number): boolea
}
export function getSqlCompletionResultValidFor(sql: string, cursor: number): RegExp | undefined {
const context = getSqlCompletionContext(sql, cursor);
return context.suggestTables ? undefined : /^[\w$]*$/;
void sql;
void cursor;
return undefined;
}
export function getSqlFunctionSignatureHelp(sql: string, cursor: number): SqlFunctionSignatureHelp | null {
@@ -1487,13 +1488,14 @@ function unquoteIdentifier(value: string): string {
function buildTableItems(prefix: string, tables: SqlCompletionTable[]): SqlCompletionItem[] {
return tables
.filter((table) => matchesPrefix(table.name, prefix))
.slice(0, MAX_TABLE_COMPLETION_ITEMS)
.map((table) => ({
label: table.name,
type: "table" as const,
detail: table.schema ? `${table.schema}.${table.name}` : table.type,
boost: computeBoost(table.name, prefix) + 1000,
}));
}))
.sort(compareCompletionItems)
.slice(0, MAX_TABLE_COMPLETION_ITEMS);
}
function buildSchemaItems(prefix: string, schemas: string[]): SqlCompletionItem[] {
@@ -1773,7 +1775,8 @@ function buildColumnItems(
detail: buildColumnDetail(column),
boost: computeBoost(column.displayLabel, context.prefix) + keyBoost,
};
});
})
.sort(compareCompletionItems);
}
function isKeyColumn(name: string): boolean {
@@ -2126,18 +2129,18 @@ function getHistoryBoost(label: string, type: string): number {
function dedupeAndSort(items: SqlCompletionItem[]): SqlCompletionItem[] {
const seen = new Set<string>();
return items
.sort((left, right) => {
const leftBonus = getHistoryBoost(left.label, left.type);
const rightBonus = getHistoryBoost(right.label, right.type);
return right.boost + rightBonus - (left.boost + leftBonus);
})
.filter((item) => {
const key = `${item.type}:${item.label}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
return items.sort(compareCompletionItems).filter((item) => {
const key = `${item.type}:${item.label}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
function compareCompletionItems(left: SqlCompletionItem, right: SqlCompletionItem): number {
const leftBonus = getHistoryBoost(left.label, left.type);
const rightBonus = getHistoryBoost(right.label, right.type);
return right.boost + rightBonus - (left.boost + leftBonus);
}
function findActiveFunctionOpenParen(sqlBeforeCursor: string): number | null {
+58 -2
View File
@@ -1408,6 +1408,7 @@ export const useConnectionStore = defineStore("connection", () => {
schema?: string,
): Promise<SqlCompletionTable[]> {
const normalizedFilter = filter.trim().toLowerCase();
const relaxedFilter = relaxedCompletionTableFilter(normalizedFilter);
const cacheKey = `${connectionId}:${database}:${normalizedFilter}:${limit ?? ""}:${schema ?? ""}`;
if (completionTablesCache.value[cacheKey]) {
return completionTablesCache.value[cacheKey];
@@ -1440,7 +1441,37 @@ export const useConnectionStore = defineStore("connection", () => {
results.push(...group);
}
}
const limitedTables = limit ? results.slice(0, limit) : results;
if (results.length === 0 && relaxedFilter) {
for (let i = 0; i < schemas.length && results.length < (limit ?? Infinity); i += batchSize) {
const batch = schemas.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(async (s) => {
try {
const tables = await api.listTables(
connectionId,
database,
s,
relaxedFilter,
expandedCompletionLimit(limit),
);
return tables.map((table) => ({
name: table.name,
schema: s,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
})) as SqlCompletionTable[];
} catch {
return [] as SqlCompletionTable[];
}
}),
);
for (const group of batchResults) {
results.push(...group);
}
}
}
const limitedTables = limit
? dedupeCompletionTables(results).slice(0, expandedCompletionLimit(limit))
: results;
completionTablesCache.value[cacheKey] = limitedTables;
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
return completionTablesCache.value[cacheKey];
@@ -1465,7 +1496,10 @@ export const useConnectionStore = defineStore("connection", () => {
return completionTablesCache.value[cacheKey];
}
const tables = await api.listTables(connectionId, database, database, normalizedFilter, limit);
let tables = await api.listTables(connectionId, database, database, normalizedFilter, limit);
if (tables.length === 0 && relaxedFilter) {
tables = await api.listTables(connectionId, database, database, relaxedFilter, expandedCompletionLimit(limit));
}
completionTablesCache.value[cacheKey] = tables.map((table) => ({
name: table.name,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
@@ -1474,6 +1508,28 @@ export const useConnectionStore = defineStore("connection", () => {
return completionTablesCache.value[cacheKey];
}
function relaxedCompletionTableFilter(filter: string): string | undefined {
if (filter.length < 3) return undefined;
return filter.slice(0, 2);
}
function expandedCompletionLimit(limit?: number): number | undefined {
if (!limit) return limit;
return Math.min(Math.max(limit * 3, limit), 1000);
}
function dedupeCompletionTables(tables: SqlCompletionTable[]): SqlCompletionTable[] {
const seen = new Set<string>();
const deduped: SqlCompletionTable[] = [];
for (const table of tables) {
const key = `${table.schema ?? ""}.${table.name}`.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
deduped.push(table);
}
return deduped;
}
async function listCompletionColumns(
connectionId: string,
database: string,