feat(completion): improve SQL function suggestions
This commit is contained in:
12 files changed
+412
-40
No files matched your search
@@ -1171,6 +1171,9 @@ export interface SqlCompletionObject {
|
||||
type: "procedure" | "function" | "trigger" | "package";
|
||||
parentSchema?: string;
|
||||
parentName?: string;
|
||||
dataType?: string;
|
||||
signature?: string;
|
||||
comment?: string | null;
|
||||
applyName?: string;
|
||||
boost?: number;
|
||||
}
|
||||
@@ -1338,10 +1341,15 @@ class SqlCompletionProvider {
|
||||
}
|
||||
|
||||
const preferReferencedColumns = hasMatchingReferencedColumnPrefix(context, this.input.columnsByTable);
|
||||
if (!pendingJoinKeyword && !preferReferencedColumns && !context.exclusiveTableSuggestions && !context.exclusiveColumnSuggestions && !context.exclusiveRoutineSuggestions) {
|
||||
if (!pendingJoinKeyword && !context.exclusiveTableSuggestions && !context.exclusiveColumnSuggestions && !context.exclusiveRoutineSuggestions) {
|
||||
const snippets = this.databaseType === "manticoresearch" ? [...(this.input.snippets ?? DEFAULT_SQL_SNIPPETS), ...MANTICORESEARCH_SQL_SNIPPETS] : (this.input.snippets ?? DEFAULT_SQL_SNIPPETS);
|
||||
this.items.push(...buildSnippetItems(context.prefix, snippets, this.input.keywordCase));
|
||||
this.items.push(...buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType));
|
||||
if (!preferReferencedColumns) {
|
||||
this.items.push(...buildSnippetItems(context.prefix, snippets, this.input.keywordCase));
|
||||
}
|
||||
if (!preferReferencedColumns || context.suggestRoutines) {
|
||||
const functionItems = buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType);
|
||||
this.items.push(...(preferReferencedColumns ? functionItems.filter((item) => item.label.toLowerCase().startsWith(context.prefix.toLowerCase())) : functionItems));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.databaseType === "manticoresearch" && context.exclusiveRoutineSuggestions) {
|
||||
@@ -1818,10 +1826,9 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
|
||||
const inJoinConditionContext = isInJoinConditionContext(beforeCursor);
|
||||
const prioritizeSelectAliases = isInOrderOrGroupByContext(beforeCursor);
|
||||
const inCallRoutineContext = isCallRoutineContext(beforeCursor);
|
||||
const inPotentialPackageMemberContext = !!qualifier && !exclusiveTableSuggestions && !insertInfo && !oracleTableFunctionContext;
|
||||
const inPotentialPackageMemberContext = !!qualifier && !exclusiveTableSuggestions && !insertInfo && !updateInfo?.inSetClause && !oracleTableFunctionContext;
|
||||
const suggestColumns = !!qualifier || !!updateInfo?.inSetClause || !!insertInfo || (inColumnContext && referencedTables.length > 0);
|
||||
const preferColumnsOverGlobalRoutines = suggestColumns && referencedTables.length > 0 && !qualifier;
|
||||
const suggestRoutines = inCallRoutineContext || oracleTableFunctionContext || inPotentialPackageMemberContext || (!preferColumnsOverGlobalRoutines && !exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && prefix.length >= 2);
|
||||
const suggestRoutines = inCallRoutineContext || oracleTableFunctionContext || inPotentialPackageMemberContext || (!exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && !updateInfo?.inSetClause && prefix.length >= 2);
|
||||
|
||||
const statementKind = detectStatementKind(beforeCursor || fullStatement);
|
||||
const preferredKeywords = qualifier ? [] : preferredKeywordsForCompletion(beforeCursor, beforeToken, selectListColumnContext, exclusiveTableSuggestions, updateInfo, deleteInfo);
|
||||
@@ -1903,12 +1910,12 @@ function detectCompletionContextKind(options: {
|
||||
if (options.updateInfo?.inSetClause) return "column";
|
||||
if (options.inCallRoutineContext) return "exec";
|
||||
if (options.qualifier && options.exclusiveColumnSuggestions) return "alias_column";
|
||||
if (options.suggestColumns) return options.qualifier ? "alias_column" : "column";
|
||||
if (options.oracleTableFunctionContext || options.suggestRoutines) return "routine";
|
||||
if (options.exclusiveTableSuggestions || options.afterTableTrigger) {
|
||||
if (options.statementKind === "insert" && options.lastWord === "into") return "insert_target";
|
||||
return options.lastWord === "join" ? "join" : "table";
|
||||
}
|
||||
if (options.suggestColumns) return options.qualifier ? "alias_column" : "column";
|
||||
return "keyword";
|
||||
}
|
||||
|
||||
@@ -2190,7 +2197,7 @@ function detectInsertColumnListContext(beforeCursor: string): { table: string; s
|
||||
|
||||
function detectUpdateCompletionContext(beforeCursor: string): { target: { table: string; schema?: string }; afterTarget: boolean; inSetClause: boolean; afterSetAssignments: boolean } | null {
|
||||
const cleaned = beforeCursor.replace(/'[^']*'/g, "''").replace(/"[^"]*"/g, '""');
|
||||
const match = /^\s*update\s+((?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?([A-Za-z_][\w$]*))?/i.exec(cleaned);
|
||||
const match = /^\s*update\s+((?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?((?!set\b|where\b)[A-Za-z_][\w$]*))?/i.exec(cleaned);
|
||||
if (!match) return null;
|
||||
const [first, second] = splitQualifiedName(match[1] ?? "");
|
||||
if (!first) return null;
|
||||
@@ -2975,9 +2982,10 @@ function buildSchemaItems(prefix: string, schemas: string[], dialect?: "mysql" |
|
||||
function buildObjectItems(context: SqlCompletionContext, objects: SqlCompletionObject[], dialect?: "mysql" | "postgres" | "sqlserver", databaseType?: DatabaseType, currentSchema?: string): SqlCompletionItem[] {
|
||||
if (completionQualifierIsReferencedTable(context)) return [];
|
||||
const onlyProcedures = context.contextKind === "exec";
|
||||
const onlyFunctions = context.suggestColumns && context.referencedTables.length > 0 && !context.qualifier;
|
||||
const prioritizeOracleFunctions = databaseType === "oracle" && context.statementKind === "select";
|
||||
return objects
|
||||
.filter((object) => (!onlyProcedures || object.type === "procedure") && objectMatchesCompletionContext(object, context))
|
||||
.filter((object) => (!onlyProcedures || object.type === "procedure") && (!onlyFunctions || (object.type === "function" && object.name.toLowerCase().startsWith(context.prefix.toLowerCase()))) && objectMatchesCompletionContext(object, context))
|
||||
.map((object) => {
|
||||
const qualifiedByContext = objectIsQualifiedByContext(object, context);
|
||||
const objectInCurrentSchema = !!currentSchema && !!object.schema && normalizeIdentifierPart(object.schema) === normalizeIdentifierPart(currentSchema);
|
||||
@@ -2985,13 +2993,17 @@ function buildObjectItems(context: SqlCompletionContext, objects: SqlCompletionO
|
||||
qualifiedByContext || (context.qualifier && object.schema?.toLowerCase() === context.qualifier.toLowerCase())
|
||||
? quoteSqlIdentifier(object.name, dialect)
|
||||
: (object.applyName ?? (object.schema && !objectInCurrentSchema ? `${quoteSqlIdentifier(object.schema, dialect)}.${quoteSqlIdentifier(object.name, dialect)}` : quoteSqlIdentifier(object.name, dialect)));
|
||||
const detail = object.type === "trigger" && object.parentName ? `trigger on ${object.parentName}` : object.parentName ? `${object.type} in ${object.parentName}` : object.schema ? `${object.type} in ${object.schema}` : object.type;
|
||||
const locationDetail = object.type === "trigger" && object.parentName ? `trigger on ${object.parentName}` : object.parentName ? `${object.type} in ${object.parentName}` : object.schema ? `${object.type} in ${object.schema}` : object.type;
|
||||
const detail = object.dataType ? `${locationDetail} [${object.dataType}]` : locationDetail;
|
||||
const schemaBoost = onlyFunctions ? Math.min(object.boost ?? 0, 1000) : (object.boost ?? 0);
|
||||
const typeBoost = routineTypeBoost(object.type, prioritizeOracleFunctions && !onlyFunctions);
|
||||
return {
|
||||
label: object.name,
|
||||
type: "function" as const,
|
||||
detail,
|
||||
info: buildRoutineInfo(object),
|
||||
apply: object.type === "trigger" || object.type === "package" ? applyName : `${applyName}()`,
|
||||
boost: computeBoost(object.name, context.prefix) + routineTypeBoost(object.type, prioritizeOracleFunctions) + (object.boost ?? 0),
|
||||
boost: computeBoost(object.name, context.prefix) + typeBoost + schemaBoost,
|
||||
dedupeKey: object.applyName || (databaseType === "oracle" && object.schema) ? applyName : undefined,
|
||||
// Preserve exact routine matches before the capped candidate list is truncated.
|
||||
exactMatch: !!context.prefix && object.name.toLowerCase() === context.prefix.toLowerCase(),
|
||||
@@ -3001,6 +3013,12 @@ function buildObjectItems(context: SqlCompletionContext, objects: SqlCompletionO
|
||||
.slice(0, MAX_TABLE_COMPLETION_ITEMS);
|
||||
}
|
||||
|
||||
function buildRoutineInfo(object: SqlCompletionObject): string | undefined {
|
||||
const qualifiedName = object.parentName ? [object.parentSchema ?? object.schema, object.parentName, object.name].filter(Boolean).join(".") : [object.schema, object.name].filter(Boolean).join(".");
|
||||
const parts = [qualifiedName || object.name, object.signature?.trim(), object.comment?.trim()].filter((part): part is string => !!part);
|
||||
return parts.length > 1 ? parts.join("\n") : undefined;
|
||||
}
|
||||
|
||||
function routineTypeBoost(type: SqlCompletionObject["type"], prioritizeFunctions: boolean): number {
|
||||
if (type === "package") return 1600;
|
||||
if (type === "function") return prioritizeFunctions ? 1800 : 900;
|
||||
|
||||
Reference in new issue
Block a user