feat(editor): improve qualified table completion

This commit is contained in:
t8y2 committed 2026-07-07 18:57:46 +08:00
1 parent 3d935f7eb6
commit 42eaddac45
3 files changed
+155 -21

No files matched your search

@@ -36,6 +36,7 @@ import { buildSqlSemanticModel } from "@/lib/sql/semantic/model";
import { mergeSqlSemanticReferenceAnalysis, resolveSqlSemanticNavigationTarget } from "@/lib/sql/semantic/references";
import { buildElasticsearchCompletionItemsFromContext, getElasticsearchCompletionContext, getElasticsearchCompletionResultValidFor, shouldAutoOpenElasticsearchCompletion, type ElasticsearchCompletionItem } from "@/lib/elasticsearch/elasticsearchCompletion";
import { buildMongoCompletionItemsFromContext, getMongoCompletionContext, getMongoCompletionResultValidFor, shouldAutoOpenMongoCompletion, type MongoCompletionItem } from "@/lib/mongo/mongoCompletion";
import { resolveSqlCompletionTableLookupTarget } from "@/lib/sql/sqlCompletionLookupTarget";
import { extractIdentifierAt, isSqlKeyword, matchTable, splitQualifiedIdentifier } from "@/lib/sql/sqlNavigation";
import { lineColumnToOffset, parseSqlErrorLocation } from "@/lib/sql/sqlDiagnostics";
import {
@@ -1688,10 +1689,6 @@ function buildCompletionResult(items: QueryCompletionItem[], from: number, valid
};
}
function findExactName(names: string[], value: string): string | undefined {
return names.find((name) => name.toLowerCase() === value.toLowerCase());
}
function mergeCompletionQualifierNames(primary: string[], secondary: string[]): string[] {
const seen = new Set<string>();
const merged: string[] = [];
@@ -1704,6 +1701,11 @@ function mergeCompletionQualifierNames(primary: string[], secondary: string[]):
return merged;
}
function localCompletionDatabaseNames(completionContext: ReturnType<typeof getSqlCompletionContext>): string[] {
if (!supportsDatabaseQualifierCompletion() || !completionContext.suggestTables || completionContext.insertTable || !props.connectionId) return [];
return connectionStore.lookupLocalCompletionDatabases(props.connectionId, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES);
}
function completionOptionForItem(item: QueryCompletionItem) {
const record = () => {
recordCompletionSelection(item.label, item.type);
@@ -2009,13 +2011,16 @@ function shouldStartSqlCompletionAfterInput(insertedText: string, removedText: s
function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getSqlCompletionContext>, fullDoc: string, position: number) {
if (!props.connectionId || props.database == null) return null;
const databaseNames = supportsDatabaseQualifierCompletion() && completionContext.suggestTables && !completionContext.insertTable ? connectionStore.lookupLocalCompletionDatabases(props.connectionId, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES) : [];
const qualifierDatabase = completionContext.qualifier ? findExactName(databaseNames, completionContext.qualifier) : undefined;
const databaseNames = localCompletionDatabaseNames(completionContext);
const shouldLoadTables = completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext));
const tableLookupDatabase = qualifierDatabase ?? props.database;
const tableLookupSchema = qualifierDatabase ? undefined : completionContext.qualifier && completionContext.suggestTables ? completionContext.qualifier : props.schema;
const tableLookupFilter = completionContext.qualifier && completionContext.suggestTables ? completionContext.prefix : completionContext.qualifier || completionContext.prefix;
const tables = shouldLoadTables ? connectionStore.lookupLocalCompletionTables(props.connectionId, tableLookupDatabase, tableLookupFilter, MAX_COMPLETION_TABLES, tableLookupSchema) : cachedTables;
const tableLookupTarget = resolveSqlCompletionTableLookupTarget({
currentDatabase: props.database,
currentSchema: props.schema,
supportsDatabaseQualifier: supportsDatabaseQualifierCompletion(),
completionContext,
knownDatabases: databaseNames,
});
const tables = shouldLoadTables ? connectionStore.lookupLocalCompletionTables(props.connectionId, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema) : cachedTables;
const shouldLoadObjects = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions);
const completionObjects = shouldLoadObjects
@@ -2104,10 +2109,16 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
const tableNameCompletion = isTableNameCompletionContext(completionContext);
const connectionId = props.connectionId;
const database = props.database;
const schema = completionContext.qualifier && completionContext.suggestTables ? completionContext.qualifier : props.schema;
const tableLookupTarget = resolveSqlCompletionTableLookupTarget({
currentDatabase: database,
currentSchema: props.schema,
supportsDatabaseQualifier: supportsDatabaseQualifierCompletion(),
completionContext,
knownDatabases: localCompletionDatabaseNames(completionContext),
});
if (!localOnlyMetadata && (completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext)))) {
void connectionStore
.refreshCompletionTables(connectionId, database, completionContext.qualifier && !schema ? completionContext.qualifier : completionContext.prefix, MAX_COMPLETION_TABLES, schema)
.refreshCompletionTables(connectionId, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema)
.then((tables) => {
cachedTables = mergeCompletionTables(cachedTables, tables);
if (completionContext.suggestTables && completionContext.referencedTables.length > 0) {
@@ -2220,8 +2231,8 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
}
const shouldLoadTables = completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext));
let databaseNames: string[] = [];
if (!localOnlyMetadata && supportsDatabaseQualifierCompletion() && completionContext.suggestTables && !completionContext.insertTable) {
let databaseNames = localCompletionDatabaseNames(completionContext);
if (!localOnlyMetadata && supportsDatabaseQualifierCompletion() && completionContext.suggestTables && !completionContext.insertTable && !completionContext.qualifier) {
try {
databaseNames = await connectionStore.listCompletionDatabases(props.connectionId!);
if (epoch !== completionEpoch) return null;
@@ -2229,14 +2240,17 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
databaseNames = [];
}
}
const qualifierDatabase = completionContext.qualifier ? findExactName(databaseNames, completionContext.qualifier) : undefined;
const tableLookupDatabase = qualifierDatabase ?? props.database!;
const tableLookupSchema = qualifierDatabase ? undefined : completionContext.qualifier && completionContext.suggestTables ? completionContext.qualifier : props.schema;
const tableLookupFilter = completionContext.qualifier && completionContext.suggestTables ? completionContext.prefix : completionContext.qualifier && !qualifierDatabase ? completionContext.qualifier : completionContext.prefix;
const tableLookupTarget = resolveSqlCompletionTableLookupTarget({
currentDatabase: props.database!,
currentSchema: props.schema,
supportsDatabaseQualifier: supportsDatabaseQualifierCompletion(),
completionContext,
knownDatabases: databaseNames,
});
let tables = shouldLoadTables
? localOnlyMetadata
? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupDatabase, tableLookupFilter, MAX_COMPLETION_TABLES, tableLookupSchema)
: await listCompletionTablesWithLatencyBudget(props.connectionId!, tableLookupDatabase, tableLookupFilter, MAX_COMPLETION_TABLES, tableLookupSchema)
? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema)
: await listCompletionTablesWithLatencyBudget(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema)
: cachedTables;
if (epoch !== completionEpoch) return null;
@@ -2275,7 +2289,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
// If qualifier didn't match any table names, try it as a schema name
let qualifierIsSchema = false;
if (completionContext.qualifier && !qualifierDatabase && !isReferencedTableQualifier(completionContext) && tables.length === 0 && (completionContext.suggestTables || completionContext.exclusiveColumnSuggestions)) {
if (completionContext.qualifier && !tableLookupTarget.qualifierDatabase && !isReferencedTableQualifier(completionContext) && tables.length === 0 && (completionContext.suggestTables || completionContext.exclusiveColumnSuggestions)) {
let schemaTables = connectionStore.lookupLocalCompletionTables(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier);
if (!localOnlyMetadata) {
schemaTables = await listCompletionTablesWithLatencyBudget(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier);