feat(completion): improve SQL function suggestions

This commit is contained in:
zipg authored and GitHub committed 2026-07-20 01:55:52 +08:00
1 parent 21652bc797
commit f5cdbb8662
12 files changed
+412 -40

No files matched your search

@@ -41,7 +41,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, mongoCompletionNeedsCollections, mongoCompletionNeedsFields, shouldAutoOpenMongoCompletion, type MongoCompletionItem } from "@/lib/mongo/mongoCompletion";
import { resolveSqlCompletionTableLookupTarget } from "@/lib/sql/sqlCompletionLookupTarget";
import { resolveSqlCompletionRoutineLookupTarget, resolveSqlCompletionTableLookupTarget } from "@/lib/sql/sqlCompletionLookupTarget";
import { extractIdentifierDetailsAt, isSqlKeyword, matchTable, mergeSqlObjectNavigationType, splitQualifiedIdentifier, sqlObjectHoverDetail, sqlObjectNavigationTarget, type SqlObjectNavigationTarget } from "@/lib/sql/sqlNavigation";
import { lineColumnToOffset, parseSqlErrorLocation } from "@/lib/sql/sqlDiagnostics";
import {
@@ -74,7 +74,7 @@ import { areSqlSemanticDiagnosticsEqual, buildSqlParserErrorDiagnostic, buildSql
import { buildRedisSyntaxDiagnostics, shouldRunRedisDiagnostics } from "@/lib/redis/redisSyntaxDiagnostics";
import { buildRedisCompletionItemsFromContext, getRedisCompletionContext, getRedisCompletionResultValidFor, shouldAutoOpenRedisCompletion, takesKeyArgument, type RedisCompletionItem } from "@/lib/redis/redisCompletion";
import type { SqlCompletionColumn, SqlCompletionForeignKey, SqlCompletionItem, SqlCompletionObject, SqlCompletionReferencedTable, SqlCompletionTable } from "@/lib/sql/sqlCompletion";
import type { DatabaseType, SqlReferenceAnalysis, SqlTableReference, SqlTextSpan } from "@/types/database";
import type { CompletionAssistantObjectKind, DatabaseType, SqlReferenceAnalysis, SqlTableReference, SqlTextSpan } from "@/types/database";
const props = defineProps<{
modelValue: string;
@@ -2278,12 +2278,12 @@ async function provideSqlCompletions(context: CompletionContext) {
const shouldResolveAsyncCompletion = tableNameCompletion || shouldResolveColumnCompletion;
const localResult = buildLocalSqlCompletionResult(completionContext, fullDoc, position);
if (localResult) {
scheduleCompletionMetadataRefresh(completionContext);
scheduleCompletionMetadataRefresh(completionContext, fullDoc, position);
const hasLocalColumnResult = localResult.options.some((option) => option.type === "column");
if ((!explicit || typedActivation) && (!shouldResolveColumnCompletion || hasLocalColumnResult)) return localResult;
}
if ((!explicit || typedActivation) && !shouldResolveAsyncCompletion) {
scheduleCompletionMetadataRefresh(completionContext);
scheduleCompletionMetadataRefresh(completionContext, fullDoc, position);
return null;
}
@@ -2465,7 +2465,7 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getS
return buildCompletionResult(items, position - completionContext.prefix.length, getSqlCompletionResultValidFor(fullDoc, position));
}
function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof getSqlCompletionContext>) {
function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof getSqlCompletionContext>, fullDoc: string, position: number) {
if (!props.connectionId || props.database == null) return;
const localOnlyMetadata = usesLocalOnlyCompletionMetadata();
const onDemandOnlyColumns = usesOnDemandOnlyCompletionColumns();
@@ -2494,7 +2494,10 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
if (!localOnlyMetadata && shouldLoadCompletionObjects(completionContext)) {
void listCompletionObjectsForContext(completionContext)
.then((objects) => {
cachedCompletionObjects = mergeCompletionObjects(cachedCompletionObjects, objects);
const merged = mergeCompletionObjects(cachedCompletionObjects, objects);
const changed = completionObjectsDiffer(cachedCompletionObjects, merged);
cachedCompletionObjects = merged;
if (changed) refreshActiveSqlCompletion(fullDoc, position, completionContext);
})
.catch(() => {});
}
@@ -2609,18 +2612,29 @@ function lookupLocalCompletionObjectsForContext(completionContext: ReturnType<ty
if (props.databaseType === "oracle") {
return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.prefix, MAX_COMPLETION_TABLES);
}
return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier && !completionContext.exclusiveColumnSuggestions ? completionContext.qualifier : props.schema);
const target = resolveSqlCompletionRoutineLookupTarget({ currentSchema: props.schema, completionContext });
return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, target.mask, MAX_COMPLETION_TABLES, target.schema);
}
async function listCompletionObjectsForContext(completionContext: ReturnType<typeof getSqlCompletionContext>): Promise<SqlCompletionObject[]> {
if (!props.connectionId || props.database == null) return [];
const objectKinds = completionObjectKindsForContext(completionContext);
if (props.databaseType !== "oracle") {
return connectionStore.listCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema);
const target = resolveSqlCompletionRoutineLookupTarget({ currentSchema: props.schema, completionContext });
return connectionStore.listCompletionObjects(props.connectionId, props.database, target.mask, MAX_COMPLETION_TABLES, target.schema, undefined, false, props.schema, objectKinds);
}
const groups = await Promise.all(oracleRoutineCompletionTargets(completionContext).map((target) => connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, target.schema, target.parentName, target.globalSearch, props.schema)));
const groups = await Promise.all(
oracleRoutineCompletionTargets(completionContext).map((target) => connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, target.schema, target.parentName, target.globalSearch, props.schema, objectKinds)),
);
return groups.reduce((objects, group) => mergeCompletionObjects(objects, group), [] as SqlCompletionObject[]);
}
function completionObjectKindsForContext(completionContext: ReturnType<typeof getSqlCompletionContext>): CompletionAssistantObjectKind[] {
if (completionContext.contextKind === "exec") return ["procedure"];
if (completionContext.suggestColumns && completionContext.referencedTables.length > 0 && !completionContext.qualifier) return ["function"];
return ["routine"];
}
async function performAsyncCompletionWithResult(epoch: number, completionContext: ReturnType<typeof getSqlCompletionContext>, fullDoc: string, position: number) {
const localOnlyMetadata = usesLocalOnlyCompletionMetadata();
const onDemandOnlyColumns = usesOnDemandOnlyCompletionColumns();
@@ -2876,6 +2890,35 @@ function mergeCompletionObjects(existing: SqlCompletionObject[], incoming: SqlCo
return merged;
}
function completionObjectsDiffer(existing: SqlCompletionObject[], incoming: SqlCompletionObject[]): boolean {
if (existing.length !== incoming.length) return true;
return existing.some((object, index) => {
const other = incoming[index];
return (
!other ||
object.name !== other.name ||
object.schema !== other.schema ||
object.type !== other.type ||
object.parentSchema !== other.parentSchema ||
object.parentName !== other.parentName ||
object.dataType !== other.dataType ||
object.signature !== other.signature ||
object.comment !== other.comment ||
object.applyName !== other.applyName ||
object.boost !== other.boost
);
});
}
function refreshActiveSqlCompletion(fullDoc: string, position: number, completionContext: ReturnType<typeof getSqlCompletionContext>) {
const currentView = view.value;
if (!currentView || codeMirrorCompletionStatus?.(currentView.state) !== "active") return;
if (currentView.state.doc.toString() !== fullDoc || currentView.state.selection.main.head !== position) return;
const currentContext = getSqlCompletionContext(fullDoc, position);
if (currentContext.prefix !== completionContext.prefix || currentContext.contextKind !== completionContext.contextKind) return;
scheduleSqlCompletionStart(currentView);
}
function refreshCompletionCache() {
cachedTables = [];
cachedCompletionObjects = [];