feat(sql): improve MySQL function completion
This commit is contained in:
1 parent
1fb3e2816b
commit
bf41838006
10 files changed
+517
-5
No files matched your search
@@ -724,6 +724,44 @@ function getFunctionDescriptions(t?: SqlCompletionTranslations): Map<string, str
|
||||
["DATE_SUB", d.DATE_SUB || "Subtracts from a date"],
|
||||
["EXTRACT", d.EXTRACT || "Extracts a part from a date"],
|
||||
["NOW", d.NOW || "Returns the current date and time"],
|
||||
["CURRENT_DATE", d.CURRENT_DATE || "Returns the current date"],
|
||||
["CURRENT_TIME", d.CURRENT_TIME || "Returns the current time"],
|
||||
["CURRENT_TIMESTAMP", d.CURRENT_TIMESTAMP || "Returns the current date and time"],
|
||||
["CURDATE", d.CURDATE || "Returns the current date"],
|
||||
["CURTIME", d.CURTIME || "Returns the current time"],
|
||||
["LOCALTIME", d.LOCALTIME || "Returns the current local time"],
|
||||
["LOCALTIMESTAMP", d.LOCALTIMESTAMP || "Returns the current local date and time"],
|
||||
["UTC_DATE", d.UTC_DATE || "Returns the current UTC date"],
|
||||
["UTC_TIME", d.UTC_TIME || "Returns the current UTC time"],
|
||||
["UTC_TIMESTAMP", d.UTC_TIMESTAMP || "Returns the current UTC date and time"],
|
||||
["SYSDATE", d.SYSDATE || "Returns the current date and time"],
|
||||
["DATE", d.DATE || "Extracts the date part"],
|
||||
["TIME", d.TIME || "Extracts the time part"],
|
||||
["TIMESTAMPDIFF", d.TIMESTAMPDIFF || "Returns the difference between two datetimes"],
|
||||
["YEAR", d.YEAR || "Extracts the year"],
|
||||
["MONTH", d.MONTH || "Extracts the month"],
|
||||
["DAY", d.DAY || "Extracts the day"],
|
||||
["HOUR", d.HOUR || "Extracts the hour"],
|
||||
["MINUTE", d.MINUTE || "Extracts the minute"],
|
||||
["SECOND", d.SECOND || "Extracts the second"],
|
||||
["DAYOFWEEK", d.DAYOFWEEK || "Returns the weekday index"],
|
||||
["DAYOFYEAR", d.DAYOFYEAR || "Returns the day of year"],
|
||||
["LAST_DAY", d.LAST_DAY || "Returns the last day of the month"],
|
||||
["STR_TO_DATE", d.STR_TO_DATE || "Converts a string to a date"],
|
||||
["IF", d.IF || "Returns a value based on a condition"],
|
||||
["LEFT", d.LEFT || "Returns the leftmost characters"],
|
||||
["RIGHT", d.RIGHT || "Returns the rightmost characters"],
|
||||
["SUBSTRING_INDEX", d.SUBSTRING_INDEX || "Returns a substring before a delimiter count"],
|
||||
["CHAR_LENGTH", d.CHAR_LENGTH || "Returns the character length"],
|
||||
["INSTR", d.INSTR || "Returns the position of a substring"],
|
||||
["LOCATE", d.LOCATE || "Returns the position of a substring"],
|
||||
["LPAD", d.LPAD || "Left-pads a string to a length"],
|
||||
["RPAD", d.RPAD || "Right-pads a string to a length"],
|
||||
["FIND_IN_SET", d.FIND_IN_SET || "Returns the index in a comma-separated list"],
|
||||
["RAND", d.RAND || "Returns a random floating-point value"],
|
||||
["MD5", d.MD5 || "Returns the MD5 hash"],
|
||||
["SHA1", d.SHA1 || "Returns the SHA1 hash"],
|
||||
["SHA2", d.SHA2 || "Returns the SHA2 hash"],
|
||||
["ROUND", d.ROUND || "Rounds to the specified precision"],
|
||||
["FLOOR", d.FLOOR || "Rounds down"],
|
||||
["CEIL", d.CEIL || "Rounds up"],
|
||||
@@ -890,8 +928,8 @@ const SQL_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["DATE_FORMAT", ["date", "format"]],
|
||||
["DATEDIFF", ["date1", "date2"]],
|
||||
["TIMESTAMPDIFF", ["unit", "datetime_expr1", "datetime_expr2"]],
|
||||
["DATE_ADD", ["date", "interval"]],
|
||||
["DATE_SUB", ["date", "interval"]],
|
||||
["DATE_ADD", ["date", "INTERVAL expr unit"]],
|
||||
["DATE_SUB", ["date", "INTERVAL expr unit"]],
|
||||
["EXTRACT", ["unit", "date"]],
|
||||
["YEAR", ["date"]],
|
||||
["MONTH", ["date"]],
|
||||
@@ -967,6 +1005,48 @@ const MYSQL_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["FROM_UNIXTIME", ["unix_timestamp"]],
|
||||
["UNIX_TIMESTAMP", []],
|
||||
["SYSDATE", []],
|
||||
["CURRENT_DATE", []],
|
||||
["CURRENT_TIME", []],
|
||||
["CURRENT_TIMESTAMP", []],
|
||||
["CURDATE", []],
|
||||
["CURTIME", []],
|
||||
["LOCALTIME", []],
|
||||
["LOCALTIMESTAMP", []],
|
||||
["UTC_DATE", []],
|
||||
["UTC_TIME", []],
|
||||
["UTC_TIMESTAMP", []],
|
||||
["DATE", ["expression"]],
|
||||
["TIME", ["expression"]],
|
||||
["DATE_ADD", ["date", "INTERVAL expr unit"]],
|
||||
["DATE_SUB", ["date", "INTERVAL expr unit"]],
|
||||
["DATEDIFF", ["date1", "date2"]],
|
||||
["TIMESTAMPDIFF", ["unit", "datetime_expr1", "datetime_expr2"]],
|
||||
["YEAR", ["date"]],
|
||||
["MONTH", ["date"]],
|
||||
["DAY", ["date"]],
|
||||
["HOUR", ["datetime"]],
|
||||
["MINUTE", ["datetime"]],
|
||||
["SECOND", ["datetime"]],
|
||||
["DAYOFWEEK", ["date"]],
|
||||
["DAYOFYEAR", ["date"]],
|
||||
["LAST_DAY", ["date"]],
|
||||
["STR_TO_DATE", ["string", "format"]],
|
||||
["IFNULL", ["expression", "fallback"]],
|
||||
["IF", ["condition", "true_value", "false_value"]],
|
||||
["CONCAT_WS", ["separator", "...values"]],
|
||||
["LEFT", ["string", "length"]],
|
||||
["RIGHT", ["string", "length"]],
|
||||
["SUBSTRING_INDEX", ["string", "delimiter", "count"]],
|
||||
["CHAR_LENGTH", ["string"]],
|
||||
["INSTR", ["string", "substring"]],
|
||||
["LOCATE", ["substring", "string"]],
|
||||
["LPAD", ["string", "length", "pad"]],
|
||||
["RPAD", ["string", "length", "pad"]],
|
||||
["FIND_IN_SET", ["string", "string_list"]],
|
||||
["RAND", []],
|
||||
["MD5", ["string"]],
|
||||
["SHA1", ["string"]],
|
||||
["SHA2", ["string", "bit_length"]],
|
||||
["JSON_EXTRACT", ["json", "path"]],
|
||||
["JSON_UNQUOTE", ["json"]],
|
||||
["GROUP_CONCAT", ["expression"]],
|
||||
@@ -974,6 +1054,9 @@ const MYSQL_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["NOW", []],
|
||||
]);
|
||||
|
||||
/** Keywords that may also exist as built-in functions; keep both completion entries. */
|
||||
const DUAL_ROLE_SQL_KEYWORDS = new Set(["LEFT", "RIGHT", "IF"]);
|
||||
|
||||
const SQLITE_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["JSON_EXTRACT", ["json", "path"]],
|
||||
["JSON_SET", ["json", "path", "value"]],
|
||||
@@ -1052,6 +1135,11 @@ const DATABASE_FUNCTION_SIGNATURES: Partial<Record<DatabaseType, Map<string, str
|
||||
manticoresearch: MANTICORESEARCH_FUNCTION_SIGNATURES,
|
||||
};
|
||||
|
||||
const MYSQL_FUNCTION_APPLY_TEMPLATES = new Map<string, string>([
|
||||
["DATE_ADD", "DATE_ADD(${date}, INTERVAL ${expr} ${unit})"],
|
||||
["DATE_SUB", "DATE_SUB(${date}, INTERVAL ${expr} ${unit})"],
|
||||
]);
|
||||
|
||||
const COMMON_SQL_FUNCTION_NAMES = new Set([
|
||||
"COUNT",
|
||||
"SUM",
|
||||
@@ -1265,6 +1353,7 @@ export interface SqlCompletionContext {
|
||||
autoAliasTableCompletions: boolean;
|
||||
tableAliasAfterCursor?: boolean;
|
||||
contextKind: SqlCompletionContextKind;
|
||||
dataTypeContext: boolean;
|
||||
}
|
||||
|
||||
export interface SqlFunctionSignatureHelp {
|
||||
@@ -1356,7 +1445,7 @@ class SqlCompletionProvider {
|
||||
this.items.push(...buildSnippetItems(context.prefix, snippets, this.input.keywordCase));
|
||||
}
|
||||
if (!preferReferencedColumns || context.suggestRoutines) {
|
||||
const functionItems = buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType);
|
||||
const functionItems = context.dataTypeContext ? [] : buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType);
|
||||
this.items.push(...(preferReferencedColumns ? functionItems.filter((item) => item.label.toLowerCase().startsWith(context.prefix.toLowerCase())) : functionItems));
|
||||
if (isOracleLikeDatabase(this.databaseType)) {
|
||||
this.items.push(...buildOracleSystemValueItems(context.prefix, this.input.keywordCase));
|
||||
@@ -1845,6 +1934,7 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
|
||||
const suggestRoutines = inCallRoutineContext || oracleTableFunctionContext || inPotentialPackageMemberContext || (!exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && !updateInfo?.inSetClause && prefix.length >= 2);
|
||||
|
||||
const statementKind = detectStatementKind(beforeCursor || fullStatement);
|
||||
const dataTypeContext = isCreateTableColumnTypeContext(beforeToken);
|
||||
const preferredKeywords = qualifier ? [] : preferredKeywordsForCompletion(beforeCursor, beforeToken, selectListColumnContext, exclusiveTableSuggestions, updateInfo, deleteInfo);
|
||||
const contextKind = detectCompletionContextKind({
|
||||
qualifier,
|
||||
@@ -1892,9 +1982,30 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
|
||||
autoAliasTableCompletions,
|
||||
tableAliasAfterCursor,
|
||||
contextKind,
|
||||
dataTypeContext,
|
||||
};
|
||||
}
|
||||
|
||||
function isCreateTableColumnTypeContext(beforeToken: string): boolean {
|
||||
const cleaned = stripSqlLiterals(beforeToken);
|
||||
if (!/^\s*create\s+table\b/i.test(cleaned)) return false;
|
||||
|
||||
const bodyStart = cleaned.indexOf("(");
|
||||
if (bodyStart < 0) return false;
|
||||
|
||||
let depth = 0;
|
||||
let segmentStart = bodyStart + 1;
|
||||
for (let index = bodyStart + 1; index < cleaned.length; index += 1) {
|
||||
const char = cleaned[index];
|
||||
if (char === "(") depth += 1;
|
||||
else if (char === ")") depth = Math.max(0, depth - 1);
|
||||
else if (char === "," && depth === 0) segmentStart = index + 1;
|
||||
}
|
||||
|
||||
const segment = cleaned.slice(segmentStart).trim();
|
||||
return /^(?:[A-Za-z_][\w$]*|`[^`]+`|"[^"]+"|\[[^\]]+\])$/.test(segment);
|
||||
}
|
||||
|
||||
function removeActiveTableCompletionReference(referencedTables: SqlCompletionReferencedTable[], prefix: string, qualifier?: string): SqlCompletionReferencedTable[] {
|
||||
const activeName = normalizeIdentifierPart(prefix);
|
||||
const activeQualifier = qualifier ? normalizeIdentifierPart(qualifier) : undefined;
|
||||
@@ -4017,7 +4128,7 @@ function buildFunctionSnippetItems(prefix: string, functionDescriptions: Map<str
|
||||
label: name,
|
||||
type: "function" as const,
|
||||
detail: functionDescriptions.get(name) ?? "function",
|
||||
apply: `${name}(${paramStr})`,
|
||||
apply: (databaseType === "mysql" ? MYSQL_FUNCTION_APPLY_TEMPLATES.get(name) : undefined) ?? `${name}(${paramStr})`,
|
||||
boost: computeBoost(name, prefix) + 300,
|
||||
});
|
||||
}
|
||||
@@ -4132,7 +4243,7 @@ function buildKeywordItems(prefix: string, context: SqlCompletionContext, databa
|
||||
|
||||
return activeSqlKeywords(databaseType)
|
||||
.filter((keyword) => {
|
||||
if (functionSignatures.has(keyword)) return false;
|
||||
if (functionSignatures.has(keyword) && !DATA_TYPE_KEYWORDS.has(keyword) && !DUAL_ROLE_SQL_KEYWORDS.has(keyword)) return false;
|
||||
if (WINDOW_FUNCTIONS.has(keyword)) return false;
|
||||
if (!matchesPrefix(keyword, prefix)) return false;
|
||||
if (!showDdl && isDml && (DDL_ONLY_KEYWORDS.has(keyword) || DATA_TYPE_KEYWORDS.has(keyword))) return false;
|
||||
|
||||
Reference in new issue
Block a user