diff --git a/apps/desktop/src/lib/__tests__/sql/dorisCompletion.spec.ts b/apps/desktop/src/lib/__tests__/sql/dorisCompletion.spec.ts index dc475e91a9..f780265b65 100644 --- a/apps/desktop/src/lib/__tests__/sql/dorisCompletion.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/dorisCompletion.spec.ts @@ -18,6 +18,27 @@ describe("Doris and SelectDB SQL assistance", () => { expect(getSqlFunctionSignatureHelp(call, call.length, "doris")).not.toBeNull(); }); + it.each(["doris", "starrocks"] as const)("offers MySQL date and interval functions with proper templates for %s", (databaseType) => { + const addItems = buildSqlCompletionItems("SELECT date_ad", 14, { databaseType, tables: [], columnsByTable: new Map() }); + expect(addItems).toContainEqual(expect.objectContaining({ label: "DATE_ADD", type: "function", apply: "DATE_ADD(${date}, INTERVAL ${expr} ${unit})" })); + + const subItems = buildSqlCompletionItems("SELECT date_su", 14, { databaseType, tables: [], columnsByTable: new Map() }); + expect(subItems).toContainEqual(expect.objectContaining({ label: "DATE_SUB", type: "function", apply: "DATE_SUB(${date}, INTERVAL ${expr} ${unit})" })); + + const addDateItems = buildSqlCompletionItems("SELECT addda", 12, { databaseType, tables: [], columnsByTable: new Map() }); + expect(addDateItems).toContainEqual(expect.objectContaining({ label: "ADDDATE", type: "function", apply: "ADDDATE(${date}, INTERVAL ${expr} ${unit})" })); + + const extractItems = buildSqlCompletionItems("SELECT extra", 12, { databaseType, tables: [], columnsByTable: new Map() }); + expect(extractItems).toContainEqual(expect.objectContaining({ label: "EXTRACT", type: "function", apply: "EXTRACT(${unit} FROM ${date})" })); + + const call = "SELECT DATE_ADD(created_at, "; + const help = getSqlFunctionSignatureHelp(call, call.length, databaseType); + expect(help).not.toBeNull(); + expect(help?.name).toBe("DATE_ADD"); + expect(help?.parameters).toEqual(["date", "INTERVAL expr unit"]); + expect(help?.activeParameter).toBe(1); + }); + it("ranks a matching column ahead of a function prefix match", () => { const sql = "SELECT bitmap_ FROM events"; const items = buildSqlCompletionItems(sql, sql.indexOf(" FROM"), { diff --git a/apps/desktop/src/lib/__tests__/sql/sqlCompletion.functionFamilies.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlCompletion.functionFamilies.spec.ts index f02d9f338b..9b8e05ba26 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlCompletion.functionFamilies.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlCompletion.functionFamilies.spec.ts @@ -81,6 +81,18 @@ describe("SQL function completion across dialects", () => { expect(functionItem("DATE_FORMAT", "goldendb")).toMatchObject({ type: "function" }); expect(functionItem("GROUP_CONCAT", "goldendb")).toMatchObject({ type: "function" }); expect(functionItem("IFNULL", "goldendb")).toMatchObject({ type: "function" }); + expect(functionItem("DATE_FORMAT", "gbase")).toMatchObject({ type: "function" }); + expect(functionItem("DATE_ADD", "gbase")).toMatchObject({ type: "function" }); + }); + + it("inserts MySQL syntax-sensitive functions through their templates", () => { + expect(functionItem("DATE_ADD", "mysql")?.apply).toBe("DATE_ADD(${date}, INTERVAL ${expr} ${unit})"); + expect(functionItem("DATE_SUB", "mysql")?.apply).toBe("DATE_SUB(${date}, INTERVAL ${expr} ${unit})"); + expect(functionItem("ADDDATE", "mysql")?.apply).toBe("ADDDATE(${date}, INTERVAL ${expr} ${unit})"); + expect(functionItem("POSITION", "mysql")?.apply).toBe("POSITION(${substring} IN ${string})"); + expect(functionItem("EXTRACT", "mysql")?.apply).toBe("EXTRACT(${unit} FROM ${date})"); + expect(functionItem("TIMESTAMPADD", "mysql")).toMatchObject({ type: "function" }); + expect(functionItem("REGEXP_LIKE", "mysql")).toMatchObject({ type: "function" }); }); it("keeps dialect-specific functions out of other dialects", () => { diff --git a/apps/desktop/src/lib/sql/sqlCompletion.ts b/apps/desktop/src/lib/sql/sqlCompletion.ts index 6632b48d62..5b934af81b 100644 --- a/apps/desktop/src/lib/sql/sqlCompletion.ts +++ b/apps/desktop/src/lib/sql/sqlCompletion.ts @@ -987,6 +987,16 @@ function getFunctionDescriptions(t?: SqlCompletionTranslations): Map([ ["DATE_SUB", ["date", "INTERVAL expr unit"]], ["DATEDIFF", ["date1", "date2"]], ["TIMESTAMPDIFF", ["unit", "datetime_expr1", "datetime_expr2"]], + ["TIMESTAMPADD", ["unit", "interval", "datetime_expr"]], + ["EXTRACT", ["unit FROM date"]], ["YEAR", ["date"]], ["MONTH", ["date"]], ["DAY", ["date"]], @@ -1134,6 +1146,15 @@ const MYSQL_FUNCTION_SIGNATURES = new Map([ ["TO_DAYS", ["date"]], ["MAKEDATE", ["year", "day_of_year"]], ["MAKETIME", ["hour", "minute", "second"]], + ["DAYNAME", ["date"]], + ["WEEKOFYEAR", ["date"]], + ["YEARWEEK", ["date"]], + ["TIME_TO_SEC", ["time"]], + ["SEC_TO_TIME", ["seconds"]], + ["CONVERT_TZ", ["dt", "from_tz", "to_tz"]], + ["PERIOD_ADD", ["period", "months"]], + ["PERIOD_DIFF", ["period1", "period2"]], + ["TO_SECONDS", ["expr"]], ["IFNULL", ["expression", "fallback"]], ["IF", ["condition", "true_value", "false_value"]], ["CONCAT_WS", ["separator", "...values"]], @@ -1153,6 +1174,10 @@ const MYSQL_FUNCTION_SIGNATURES = new Map([ ["ELT", ["index", "string1", "...strings"]], ["FIELD", ["value", "value1", "...values"]], ["MAKE_SET", ["bits", "string1", "...strings"]], + ["REGEXP_LIKE", ["expr", "pattern"]], + ["REGEXP_REPLACE", ["expr", "pattern", "replacement"]], + ["REGEXP_SUBSTR", ["expr", "pattern"]], + ["REGEXP_INSTR", ["expr", "pattern"]], ["RAND", []], ["POW", ["base", "exponent"]], ["EXP", ["number"]], @@ -1279,12 +1304,18 @@ const MANTICORESEARCH_FUNCTION_SIGNATURES = new Map([ ]); // 同一 SQL 方言族的兼容库共享一张函数表(以及函数插入模板),避免为每个兼容库重复维护同一份数据 -const MYSQL_COMPAT_FUNCTION_DATABASES = new Set(["mysql", "goldendb"]); +const MYSQL_COMPAT_FUNCTION_DATABASES = new Set(["mysql", "goldendb", "gbase"]); const POSTGRES_COMPAT_FUNCTION_DATABASES = new Set(["postgres", "redshift", "kingbase", "highgo", "uxdb", "vastbase", "gaussdb", "opengauss", "sundb"]); const ORACLE_COMPAT_FUNCTION_DATABASES = new Set(["oracle", "oceanbase-oracle", "dameng", "yashandb", "oscar", "xugu"]); const HIVE_SPARK_COMPAT_FUNCTION_DATABASES = new Set(["hive", "spark", "databricks", "impala", "transwarp", "kyuubi", "argo"]); const TRINO_COMPAT_FUNCTION_DATABASES = new Set(["trino", "prestosql"]); +/** + * Apache Doris / StarRocks 有意兼容 MySQL 语法,因此以 MySQL 函数表作为基线, + * 再叠加 Doris / StarRocks 特有的分析/位图/HLL/数组函数(同名函数以 Doris 表为准)。 + */ +const DORIS_COMPAT_FUNCTION_SIGNATURES = new Map([...MYSQL_FUNCTION_SIGNATURES, ...DORIS_FUNCTION_SIGNATURES]); + const DATABASE_FUNCTION_SIGNATURES: Partial>> = { sqlite: SQLITE_FUNCTION_SIGNATURES, rqlite: SQLITE_FUNCTION_SIGNATURES, @@ -1292,8 +1323,8 @@ const DATABASE_FUNCTION_SIGNATURES: Partial([ ["DATE_ADD", "DATE_ADD(${date}, INTERVAL ${expr} ${unit})"], ["DATE_SUB", "DATE_SUB(${date}, INTERVAL ${expr} ${unit})"], + ["ADDDATE", "ADDDATE(${date}, INTERVAL ${expr} ${unit})"], + ["SUBDATE", "SUBDATE(${date}, INTERVAL ${expr} ${unit})"], ["POSITION", "POSITION(${substring} IN ${string})"], + ["EXTRACT", "EXTRACT(${unit} FROM ${date})"], ]); const COMMON_SQL_FUNCTION_NAMES = new Set([