Files
MyGoNavi/frontend/src/utils/sqlDialect.ts
Syngnat aeecffb517 feat(table-designer): 补齐 MySQL 空间字段类型
- 补充 Geometry、Point、MultiPoint 等八种 MySQL 空间类型候选
- 保留自定义字段类型表达式在建表和改表 SQL 中的原样输出
- 增加空间类型候选及 POINT SRID 属性回归测试

Fixes #341
2026-07-18 16:15:33 +08:00

990 lines
38 KiB
TypeScript

import { resolveOceanBaseProtocolForDialect } from './oceanBaseProtocol';
import { t as translate } from '../i18n';
export type ColumnTypeOption = { value: string };
export type SqlFunctionCompletion = {
name: string;
detail: string;
};
export type SqlDialect =
| 'mysql'
| 'mariadb'
| 'oceanbase'
| 'diros'
| 'starrocks'
| 'sphinx'
| 'postgres'
| 'kingbase'
| 'highgo'
| 'vastbase'
| 'opengauss'
| 'gaussdb'
| 'oracle'
| 'dameng'
| 'sqlserver'
| 'iris'
| 'sqlite'
| 'duckdb'
| 'clickhouse'
| 'tdengine'
| 'iotdb'
| 'rocketmq'
| 'mqtt'
| 'kafka'
| 'rabbitmq'
| 'mongodb'
| 'redis'
| 'elasticsearch'
| 'chroma'
| 'qdrant'
| 'milvus'
| 'unknown'
| string;
const unique = <T>(items: T[]): T[] => Array.from(new Set(items));
const optionValues = (values: string[]): ColumnTypeOption[] => values.map((value) => ({ value }));
const normalizeRawDialect = (value: string): string => String(value || '').trim().toLowerCase();
export const normalizeOceanBaseSqlProtocol = resolveOceanBaseProtocolForDialect;
export const resolveSqlDialect = (
rawType: string,
rawDriver = '',
options?: { oceanBaseProtocol?: unknown },
): SqlDialect => {
const normalized = normalizeRawDialect(rawType);
const driver = normalizeRawDialect(rawDriver);
const source = normalized === 'custom' ? driver : normalized;
if (!source) return 'unknown';
if (source === 'oceanbase' && normalizeOceanBaseSqlProtocol(options?.oceanBaseProtocol) === 'oracle') {
return 'oracle';
}
switch (source) {
case 'postgresql':
case 'postgres':
case 'pg':
case 'pq':
case 'pgx':
return 'postgres';
case 'opengauss':
case 'open_gauss':
case 'open-gauss':
return 'opengauss';
case 'gaussdb':
case 'gauss_db':
case 'gauss-db':
return 'gaussdb';
case 'mssql':
case 'sql_server':
case 'sql-server':
return 'sqlserver';
case 'intersystems':
case 'intersystemsiris':
case 'inter-systems':
case 'inter-systems-iris':
case 'iris':
return 'iris';
case 'doris':
case 'diros':
return 'diros';
case 'starrocks':
return 'starrocks';
case 'dm':
case 'dm8':
case 'dameng':
return 'dameng';
case 'sqlite3':
case 'sqlite':
return 'sqlite';
case 'sphinxql':
return 'sphinx';
case 'kingbase8':
case 'kingbasees':
case 'kingbasev8':
return 'kingbase';
case 'gdb':
case 'goldendb':
case 'greatdb':
return 'mysql';
case 'mariadb':
case 'oceanbase':
case 'mysql':
case 'sphinx':
case 'kingbase':
case 'highgo':
case 'vastbase':
case 'oracle':
case 'duckdb':
case 'clickhouse':
case 'tdengine':
case 'iotdb':
case 'mongodb':
case 'redis':
case 'elasticsearch':
return source;
case 'elastic':
return 'elasticsearch';
case 'chromadb':
case 'chroma-db':
case 'chroma':
return 'chroma';
case 'qdrantdb':
case 'qdrant-db':
case 'qdrant':
return 'qdrant';
case 'milvusdb':
case 'milvus-db':
case 'milvus':
return 'milvus';
case 'apache-iotdb':
case 'apache_iotdb':
return 'iotdb';
case 'rocketmq':
case 'rocket-mq':
case 'rocket_mq':
case 'apache-rocketmq':
case 'apache_rocketmq':
case 'rmq':
return 'rocketmq';
case 'mqtt':
case 'mqtts':
return 'mqtt';
case 'kafka':
case 'apache-kafka':
case 'apache_kafka':
return 'kafka';
case 'rabbitmq':
case 'rabbit-mq':
case 'rabbit_mq':
return 'rabbitmq';
default:
break;
}
if (source.includes('opengauss') || source.includes('open_gauss') || source.includes('open-gauss')) return 'opengauss';
if (source.includes('gaussdb') || source.includes('gauss_db') || source.includes('gauss-db')) return 'gaussdb';
if (source.includes('postgres')) return 'postgres';
if (source.includes('oceanbase')) return 'oceanbase';
if (source.includes('mariadb')) return 'mariadb';
if (source.includes('goldendb') || source.includes('greatdb')) return 'mysql';
if (source.includes('mysql')) return 'mysql';
if (source.includes('doris') || source.includes('diros')) return 'diros';
if (source.includes('starrocks')) return 'starrocks';
if (source.includes('sphinx')) return 'sphinx';
if (source.includes('kingbase')) return 'kingbase';
if (source.includes('highgo')) return 'highgo';
if (source.includes('vastbase')) return 'vastbase';
if (source.includes('oracle')) return 'oracle';
if (source.includes('dameng') || source.includes('dm8')) return 'dameng';
if (source.includes('sqlite')) return 'sqlite';
if (source.includes('duckdb')) return 'duckdb';
if (source.includes('clickhouse')) return 'clickhouse';
if (source.includes('tdengine')) return 'tdengine';
if (source.includes('iotdb')) return 'iotdb';
if (source.includes('rocketmq') || source.includes('rocket-mq') || source.includes('rocket_mq') || source === 'rmq') return 'rocketmq';
if (source.includes('mqtt')) return 'mqtt';
if (source.includes('kafka')) return 'kafka';
if (source.includes('rabbitmq') || source.includes('rabbit-mq') || source.includes('rabbit_mq')) return 'rabbitmq';
if (source.includes('sqlserver') || source.includes('mssql')) return 'sqlserver';
if (source.includes('iris') || source.includes('intersystems')) return 'iris';
if (source.includes('elastic')) return 'elasticsearch';
if (source.includes('chroma')) return 'chroma';
if (source.includes('qdrant')) return 'qdrant';
if (source.includes('milvus')) return 'milvus';
return source;
};
export const isMysqlFamilyDialect = (dbType: string): boolean => (
['mysql', 'mariadb', 'oceanbase', 'diros', 'starrocks', 'sphinx', 'tidb'].includes(resolveSqlDialect(dbType))
);
export const isPgLikeDialect = (dbType: string): boolean => (
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss', 'gaussdb'].includes(resolveSqlDialect(dbType))
);
export const isOracleLikeDialect = (dbType: string): boolean => (
['oracle', 'dameng', 'dm'].includes(resolveSqlDialect(dbType))
);
export const isSqlServerDialect = (dbType: string): boolean => resolveSqlDialect(dbType) === 'sqlserver';
export const isBacktickIdentifierDialect = (dbType: string): boolean => (
isMysqlFamilyDialect(dbType) || ['clickhouse', 'tdengine', 'iotdb'].includes(resolveSqlDialect(dbType))
);
const stripIdentifierQuotes = (part: string): string => {
const text = String(part || '').trim();
if (!text) return '';
if ((text.startsWith('`') && text.endsWith('`')) || (text.startsWith('"') && text.endsWith('"'))) {
return text.slice(1, -1).trim();
}
if (text.startsWith('[') && text.endsWith(']')) {
return text.slice(1, -1).replace(/]]/g, ']').trim();
}
return text;
};
const escapeBacktickIdentifier = (value: string) => String(value || '').replace(/`/g, '``');
const escapeDoubleQuoteIdentifier = (value: string) => String(value || '').replace(/"/g, '""');
const escapeBracketIdentifier = (value: string) => String(value || '').replace(/]/g, ']]');
const needsPgLikeQuote = (ident: string): boolean => !/^[a-z_][a-z0-9_]*$/.test(ident);
export const unquoteSqlIdentifierPart = stripIdentifierQuotes;
export const unquoteSqlIdentifierPath = (path: string): string => (
String(path || '')
.trim()
.split('.')
.map((part) => stripIdentifierQuotes(part))
.filter(Boolean)
.join('.')
);
export const quoteSqlIdentifierPart = (dbType: string, part: string): string => {
const ident = stripIdentifierQuotes(part);
if (!ident) return '';
const dialect = resolveSqlDialect(dbType);
if (isBacktickIdentifierDialect(dialect)) {
return `\`${escapeBacktickIdentifier(ident)}\``;
}
if (isSqlServerDialect(dialect)) {
return `[${escapeBracketIdentifier(ident)}]`;
}
if (isPgLikeDialect(dialect)) {
return needsPgLikeQuote(ident) ? `"${escapeDoubleQuoteIdentifier(ident)}"` : ident;
}
return `"${escapeDoubleQuoteIdentifier(ident)}"`;
};
export const quoteSqlIdentifierPath = (dbType: string, path: string): string => (
String(path || '')
.trim()
.split('.')
.map((part) => stripIdentifierQuotes(part))
.filter(Boolean)
.map((part) => quoteSqlIdentifierPart(dbType, part))
.join('.')
);
const MYSQL_TYPES = optionValues([
'tinyint',
'tinyint(1)',
'smallint',
'mediumint',
'int',
'bigint',
'float',
'double',
'decimal(10,2)',
'char(50)',
'varchar(255)',
'tinytext',
'text',
'mediumtext',
'longtext',
'binary(255)',
'varbinary(255)',
'tinyblob',
'blob',
'mediumblob',
'longblob',
'date',
'time',
'datetime',
'timestamp',
'year',
'json',
'geometry',
'point',
'linestring',
'polygon',
'multipoint',
'multilinestring',
'multipolygon',
'geometrycollection',
'enum',
'set',
'bit(1)',
]);
const PG_TYPES = optionValues([
'smallint',
'integer',
'bigint',
'real',
'double precision',
'numeric(10,2)',
'serial',
'bigserial',
'char(50)',
'varchar(255)',
'text',
'boolean',
'date',
'time',
'timestamp',
'timestamptz',
'interval',
'bytea',
'json',
'jsonb',
'uuid',
'inet',
'cidr',
'macaddr',
'xml',
'int4range',
'tsquery',
'tsvector',
]);
const SQLSERVER_TYPES = optionValues([
'tinyint',
'smallint',
'int',
'bigint',
'float',
'real',
'decimal(10,2)',
'numeric(10,2)',
'money',
'smallmoney',
'char(50)',
'varchar(255)',
'varchar(max)',
'nchar(50)',
'nvarchar(255)',
'nvarchar(max)',
'text',
'ntext',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
'smalldatetime',
'binary(255)',
'varbinary(255)',
'varbinary(max)',
'image',
'bit',
'uniqueidentifier',
'xml',
]);
const SQLITE_TYPES = optionValues(['INTEGER', 'REAL', 'TEXT', 'BLOB', 'NUMERIC']);
const ORACLE_TYPES = optionValues([
'NUMBER(10)',
'NUMBER(10,2)',
'FLOAT',
'BINARY_FLOAT',
'BINARY_DOUBLE',
'CHAR(50)',
'VARCHAR2(255)',
'NVARCHAR2(255)',
'CLOB',
'NCLOB',
'BLOB',
'DATE',
'TIMESTAMP',
'TIMESTAMP WITH TIME ZONE',
'RAW(255)',
'LONG RAW',
'XMLTYPE',
]);
const DAMENG_TYPES = optionValues([
'INT',
'BIGINT',
'NUMBER(10)',
'NUMBER(10,2)',
'DECIMAL(10,2)',
'CHAR(50)',
'VARCHAR(255)',
'VARCHAR2(255)',
'NVARCHAR2(255)',
'TEXT',
'CLOB',
'BLOB',
'DATE',
'TIME',
'TIMESTAMP',
'BIT',
]);
const DORIS_TYPES = optionValues([
'BOOLEAN',
'TINYINT',
'SMALLINT',
'INT',
'BIGINT',
'LARGEINT',
'FLOAT',
'DOUBLE',
'DECIMAL(10,2)',
'CHAR(50)',
'VARCHAR(255)',
'STRING',
'DATE',
'DATETIME',
'JSON',
'HLL',
'BITMAP',
'ARRAY<INT>',
'MAP<STRING,STRING>',
'STRUCT<name:STRING>',
]);
const STARROCKS_TYPES = optionValues([
'BOOLEAN',
'TINYINT',
'SMALLINT',
'INT',
'BIGINT',
'LARGEINT',
'FLOAT',
'DOUBLE',
'DECIMAL(10,2)',
'DATE',
'DATETIME',
'CHAR(50)',
'VARCHAR(255)',
'STRING',
'JSON',
'BITMAP',
'HLL',
'PERCENTILE',
'ARRAY<INT>',
'MAP<STRING,STRING>',
'STRUCT<name STRING>',
]);
const SPHINX_TYPES = optionValues([
'text',
'string',
'integer',
'bigint',
'float',
'bool',
'timestamp',
'json',
]);
const CLICKHOUSE_TYPES = optionValues([
'Int8',
'UInt8',
'Int16',
'UInt16',
'Int32',
'UInt32',
'Int64',
'UInt64',
'Float32',
'Float64',
'Decimal(10,2)',
'String',
'FixedString(32)',
'Date',
'Date32',
'DateTime',
'DateTime64(3)',
'UUID',
'IPv4',
'IPv6',
'Array(String)',
'Nullable(String)',
'LowCardinality(String)',
"Enum8('A'=1)",
]);
const TDENGINE_TYPES = optionValues([
'TIMESTAMP',
'BOOL',
'TINYINT',
'SMALLINT',
'INT',
'BIGINT',
'FLOAT',
'DOUBLE',
'BINARY(255)',
'NCHAR(255)',
'VARBINARY(255)',
'JSON',
'GEOMETRY',
]);
const IOTDB_TYPES = optionValues([
'BOOLEAN',
'INT32',
'INT64',
'FLOAT',
'DOUBLE',
'TEXT',
'STRING',
'BLOB',
'TIMESTAMP',
'DATE',
]);
const DUCKDB_TYPES = optionValues([
'BOOLEAN',
'TINYINT',
'SMALLINT',
'INTEGER',
'BIGINT',
'UTINYINT',
'USMALLINT',
'UINTEGER',
'UBIGINT',
'REAL',
'DOUBLE',
'DECIMAL(10,2)',
'VARCHAR',
'BLOB',
'DATE',
'TIME',
'TIMESTAMP',
'TIMESTAMPTZ',
'INTERVAL',
'UUID',
'JSON',
'STRUCT',
'LIST',
'MAP',
]);
const COMMON_TYPES = optionValues(['int', 'varchar(255)', 'text', 'datetime', 'decimal(10,2)', 'bigint', 'json']);
export const resolveColumnTypeOptions = (dbType: string): ColumnTypeOption[] => {
const dialect = resolveSqlDialect(dbType);
if (dialect === 'diros') return DORIS_TYPES;
if (dialect === 'starrocks') return STARROCKS_TYPES;
if (dialect === 'sphinx') return SPHINX_TYPES;
if (isMysqlFamilyDialect(dialect)) return MYSQL_TYPES;
if (isPgLikeDialect(dialect)) return PG_TYPES;
if (dialect === 'oracle') return ORACLE_TYPES;
if (dialect === 'dameng') return DAMENG_TYPES;
if (dialect === 'sqlserver') return SQLSERVER_TYPES;
if (dialect === 'iris') return COMMON_TYPES;
if (dialect === 'sqlite') return SQLITE_TYPES;
if (dialect === 'duckdb') return DUCKDB_TYPES;
if (dialect === 'clickhouse') return CLICKHOUSE_TYPES;
if (dialect === 'tdengine') return TDENGINE_TYPES;
if (dialect === 'iotdb') return IOTDB_TYPES;
return COMMON_TYPES;
};
const COMMON_KEYWORDS = [
'SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'JOIN', 'LEFT', 'RIGHT',
'INNER', 'OUTER', 'ON', 'GROUP BY', 'ORDER BY', 'HAVING', 'AS', 'AND', 'OR', 'NOT',
'NULL', 'IS', 'IN', 'VALUES', 'SET', 'CREATE', 'TABLE', 'DROP', 'ALTER', 'ADD',
'COLUMN', 'KEY', 'PRIMARY', 'FOREIGN', 'REFERENCES', 'CONSTRAINT', 'DEFAULT',
'COMMENT', 'EXPLAIN', 'DISTINCT', 'UNION', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END',
];
const MYSQL_KEYWORDS = [
'LIMIT', 'OFFSET', 'MODIFY', 'CHANGE', 'AUTO_INCREMENT', 'SHOW', 'DESCRIBE',
'DESC', 'ENGINE', 'CHARSET', 'COLLATE', 'REPLACE', 'DUPLICATE KEY', 'LOCK',
'CALL',
];
const PG_KEYWORDS = [
'LIMIT', 'OFFSET', 'RETURNING', 'SERIAL', 'BIGSERIAL', 'BOOLEAN', 'JSONB',
'ILIKE', 'RENAME', 'TYPE', 'CASCADE', 'RESTRICT', 'ONLY',
];
const ORACLE_KEYWORDS = [
'ROWNUM', 'FETCH', 'FIRST', 'ROWS', 'ONLY', 'VARCHAR2', 'NVARCHAR2', 'NUMBER',
'DATE', 'TIMESTAMP', 'CLOB', 'BLOB', 'SEQUENCE', 'SYNONYM', 'MERGE', 'MINUS',
'CONNECT BY', 'START WITH', 'MODIFY', 'RENAME',
];
const SQLSERVER_KEYWORDS = [
'TOP', 'OFFSET', 'FETCH', 'NEXT', 'ROWS', 'ONLY', 'IDENTITY', 'NVARCHAR',
'DATETIME2', 'BIT', 'GO', 'EXEC', 'PROCEDURE', 'WITH', 'NOLOCK', 'MERGE',
];
const SQLITE_KEYWORDS = ['LIMIT', 'OFFSET', 'AUTOINCREMENT', 'PRAGMA', 'WITHOUT', 'ROWID', 'RENAME'];
const DUCKDB_KEYWORDS = ['LIMIT', 'OFFSET', 'SAMPLE', 'QUALIFY', 'STRUCT', 'LIST', 'MAP', 'JSON', 'UNNEST'];
const CLICKHOUSE_KEYWORDS = [
'LIMIT', 'OFFSET', 'FORMAT', 'ENGINE', 'PARTITION', 'ORDER BY', 'PRIMARY KEY',
'SAMPLE', 'MATERIALIZED', 'ALIAS', 'SETTINGS', 'TTL', 'CODEC',
];
const STARROCKS_KEYWORDS = [
'LIMIT', 'OFFSET', 'ENGINE', 'OLAP', 'DUPLICATE KEY', 'PRIMARY KEY',
'AGGREGATE KEY', 'UNIQUE KEY', 'DISTRIBUTED BY', 'HASH', 'BUCKETS',
'PARTITION BY', 'PROPERTIES', 'MATERIALIZED VIEW', 'REFRESH ASYNC',
'REFRESH MANUAL', 'ROLLUP', 'ADD ROLLUP', 'EXTERNAL CATALOG',
'CREATE EXTERNAL TABLE', 'BITMAP', 'HLL',
];
const TDENGINE_KEYWORDS = ['LIMIT', 'SLIMIT', 'SOFFSET', 'TAGS', 'USING', 'INTERVAL', 'FILL', 'PARTITION BY'];
const IOTDB_KEYWORDS = [
'LIMIT',
'OFFSET',
'ALIGN BY DEVICE',
'DISABLE ALIGN',
'GROUP BY',
'LEVEL',
'FILL',
'SLIMIT',
'SOFFSET',
'CREATE TIMESERIES',
'SHOW TIMESERIES',
'SHOW DEVICES',
'SHOW DATABASES',
'STORAGE GROUP',
'WITH DATATYPE',
'ENCODING',
'COMPRESSION',
];
const ROCKETMQ_KEYWORDS = [
'SHOW TOPICS',
'DESCRIBE TOPIC',
'CONSUME',
'FROM',
'LIMIT',
'OFFSET',
];
const MQTT_KEYWORDS = [
'SHOW TOPICS',
'DESCRIBE TOPIC',
'CONSUME',
'FROM',
'LIMIT',
'OFFSET',
];
const KAFKA_KEYWORDS = [
'SHOW TOPICS',
'SHOW TOPIC',
'DESCRIBE TOPIC',
'CONSUME',
'GROUP',
'FROM',
'LIMIT',
'OFFSET',
];
const RABBITMQ_KEYWORDS = [
'SHOW VHOSTS',
'SHOW QUEUES',
'SHOW EXCHANGES',
'DESCRIBE QUEUE',
'DESCRIBE EXCHANGE',
'CONSUME',
'FROM',
'LIMIT',
'OFFSET',
];
export const resolveSqlKeywords = (dbType: string): string[] => {
const dialect = resolveSqlDialect(dbType);
if (dialect === 'starrocks') return unique([...COMMON_KEYWORDS, ...MYSQL_KEYWORDS, ...STARROCKS_KEYWORDS]);
if (isMysqlFamilyDialect(dialect)) return unique([...COMMON_KEYWORDS, ...MYSQL_KEYWORDS]);
if (isPgLikeDialect(dialect)) return unique([...COMMON_KEYWORDS, ...PG_KEYWORDS]);
if (isOracleLikeDialect(dialect)) return unique([...COMMON_KEYWORDS, ...ORACLE_KEYWORDS]);
if (dialect === 'sqlserver') return unique([...COMMON_KEYWORDS, ...SQLSERVER_KEYWORDS]);
if (dialect === 'sqlite') return unique([...COMMON_KEYWORDS, ...SQLITE_KEYWORDS]);
if (dialect === 'duckdb') return unique([...COMMON_KEYWORDS, ...DUCKDB_KEYWORDS]);
if (dialect === 'clickhouse') return unique([...COMMON_KEYWORDS, ...CLICKHOUSE_KEYWORDS]);
if (dialect === 'tdengine') return unique([...COMMON_KEYWORDS, ...TDENGINE_KEYWORDS]);
if (dialect === 'iotdb') return unique([...COMMON_KEYWORDS, ...IOTDB_KEYWORDS]);
if (dialect === 'rocketmq') return unique([...COMMON_KEYWORDS, ...ROCKETMQ_KEYWORDS]);
if (dialect === 'mqtt') return unique([...COMMON_KEYWORDS, ...MQTT_KEYWORDS]);
if (dialect === 'kafka') return unique([...COMMON_KEYWORDS, ...KAFKA_KEYWORDS]);
if (dialect === 'rabbitmq') return unique([...COMMON_KEYWORDS, ...RABBITMQ_KEYWORDS]);
return COMMON_KEYWORDS;
};
type SqlFunctionDetailTemplate =
| { kind: 'scoped'; scopeKey: string; actionKey: string }
| { kind: 'vendor'; vendor: string; actionKey: string };
type SqlFunctionDefinition = {
name: string;
detail: SqlFunctionDetailTemplate;
};
const scopedDetail = (scopeKey: string, actionKey: string): SqlFunctionDetailTemplate => ({ kind: 'scoped', scopeKey, actionKey });
const vendorDetail = (vendor: string, actionKey: string): SqlFunctionDetailTemplate => ({ kind: 'vendor', vendor, actionKey });
const fn = (name: string, detail: SqlFunctionDetailTemplate): SqlFunctionDefinition => ({ name, detail });
const renderSqlFunctionDetail = (detail: SqlFunctionDetailTemplate): string => {
if (detail.kind === 'scoped') {
return `${translate(detail.scopeKey)} - ${translate(detail.actionKey)}`;
}
return `${detail.vendor} - ${translate(detail.actionKey)}`;
};
const COMMON_FUNCTIONS = [
fn('COUNT', scopedDetail('query_editor.completion.detail.aggregate', 'query_editor.completion.action.count')),
fn('SUM', scopedDetail('query_editor.completion.detail.aggregate', 'query_editor.completion.action.sum')),
fn('AVG', scopedDetail('query_editor.completion.detail.aggregate', 'query_editor.completion.action.average')),
fn('MAX', scopedDetail('query_editor.completion.detail.aggregate', 'query_editor.completion.action.maximum')),
fn('MIN', scopedDetail('query_editor.completion.detail.aggregate', 'query_editor.completion.action.minimum')),
fn('CONCAT', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.concatenation')),
fn('SUBSTRING', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.substring_extraction')),
fn('SUBSTR', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.substring_extraction')),
fn('LENGTH', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.length')),
fn('UPPER', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.uppercase')),
fn('LOWER', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.lowercase')),
fn('TRIM', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.space_trimming')),
fn('LTRIM', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.left_space_trimming')),
fn('RTRIM', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.right_space_trimming')),
fn('REPLACE', scopedDetail('query_editor.completion.detail.string', 'query_editor.completion.action.replacement')),
fn('ABS', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.absolute_value')),
fn('CEIL', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.round_up')),
fn('CEILING', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.round_up')),
fn('FLOOR', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.round_down')),
fn('ROUND', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.rounding')),
fn('MOD', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.modulo')),
fn('POWER', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.power_operation')),
fn('SQRT', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.square_root')),
fn('LOG', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.logarithm')),
fn('EXP', scopedDetail('query_editor.completion.detail.math', 'query_editor.completion.action.e_power')),
fn('COALESCE', scopedDetail('query_editor.completion.detail.conditional', 'query_editor.completion.action.first_non_null')),
fn('NULLIF', scopedDetail('query_editor.completion.detail.conditional', 'query_editor.completion.action.null_if_equal')),
fn('CAST', scopedDetail('query_editor.completion.detail.conversion', 'query_editor.completion.action.type_conversion')),
fn('CONVERT', scopedDetail('query_editor.completion.detail.conversion', 'query_editor.completion.action.type_conversion')),
fn('ROW_NUMBER', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.row_number')),
fn('RANK', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.rank')),
fn('DENSE_RANK', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.dense_rank')),
fn('LAG', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.previous_row')),
fn('LEAD', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.next_row')),
fn('FIRST_VALUE', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.first_value')),
fn('LAST_VALUE', scopedDetail('query_editor.completion.detail.window', 'query_editor.completion.action.last_value')),
];
const MYSQL_FUNCTIONS = [
fn('GROUP_CONCAT', vendorDetail('MySQL', 'query_editor.completion.action.group_concatenation')),
fn('CONCAT_WS', vendorDetail('MySQL', 'query_editor.completion.action.concat_with_separator')),
fn('LEFT', vendorDetail('MySQL', 'query_editor.completion.action.left_substring')),
fn('RIGHT', vendorDetail('MySQL', 'query_editor.completion.action.right_substring')),
fn('CHAR_LENGTH', vendorDetail('MySQL', 'query_editor.completion.action.character_length')),
fn('REVERSE', vendorDetail('MySQL', 'query_editor.completion.action.string_reversal')),
fn('REPEAT', vendorDetail('MySQL', 'query_editor.completion.action.string_repetition')),
fn('LPAD', vendorDetail('MySQL', 'query_editor.completion.action.left_padding')),
fn('RPAD', vendorDetail('MySQL', 'query_editor.completion.action.right_padding')),
fn('INSTR', vendorDetail('MySQL', 'query_editor.completion.action.position_lookup')),
fn('LOCATE', vendorDetail('MySQL', 'query_editor.completion.action.position_lookup')),
fn('FIND_IN_SET', vendorDetail('MySQL', 'query_editor.completion.action.set_lookup')),
fn('FORMAT', vendorDetail('MySQL', 'query_editor.completion.action.number_formatting')),
fn('TRUNCATE', vendorDetail('MySQL', 'query_editor.completion.action.decimal_truncation')),
fn('RAND', vendorDetail('MySQL', 'query_editor.completion.action.random_number')),
fn('POW', vendorDetail('MySQL', 'query_editor.completion.action.power_operation')),
fn('LOG2', vendorDetail('MySQL', 'query_editor.completion.action.log_base_2')),
fn('LOG10', vendorDetail('MySQL', 'query_editor.completion.action.log_base_10')),
fn('NOW', vendorDetail('MySQL', 'query_editor.completion.action.current_date_time')),
fn('CURDATE', vendorDetail('MySQL', 'query_editor.completion.action.current_date')),
fn('CURTIME', vendorDetail('MySQL', 'query_editor.completion.action.current_time')),
fn('DATE_FORMAT', vendorDetail('MySQL', 'query_editor.completion.action.date_formatting')),
fn('DATE_ADD', vendorDetail('MySQL', 'query_editor.completion.action.date_addition')),
fn('DATE_SUB', vendorDetail('MySQL', 'query_editor.completion.action.date_subtraction')),
fn('DATEDIFF', vendorDetail('MySQL', 'query_editor.completion.action.date_difference')),
fn('TIMESTAMPDIFF', vendorDetail('MySQL', 'query_editor.completion.action.timestamp_difference')),
fn('STR_TO_DATE', vendorDetail('MySQL', 'query_editor.completion.action.string_to_date')),
fn('UNIX_TIMESTAMP', vendorDetail('MySQL', 'query_editor.completion.action.unix_timestamp')),
fn('IF', vendorDetail('MySQL', 'query_editor.completion.action.conditional_check')),
fn('IFNULL', vendorDetail('MySQL', 'query_editor.completion.action.null_replacement')),
fn('JSON_EXTRACT', vendorDetail('MySQL', 'query_editor.completion.action.json_value_extraction')),
fn('JSON_UNQUOTE', vendorDetail('MySQL', 'query_editor.completion.action.json_unquote')),
fn('JSON_SET', vendorDetail('MySQL', 'query_editor.completion.action.json_value_set')),
fn('MD5', vendorDetail('MySQL', 'query_editor.completion.action.md5_hash')),
fn('SHA1', vendorDetail('MySQL', 'query_editor.completion.action.sha1_hash')),
fn('SHA2', vendorDetail('MySQL', 'query_editor.completion.action.sha2_hash')),
fn('UUID', vendorDetail('MySQL', 'query_editor.completion.action.uuid_generation')),
fn('DATABASE', vendorDetail('MySQL', 'query_editor.completion.action.current_database')),
fn('VERSION', vendorDetail('MySQL', 'query_editor.completion.action.version')),
fn('LAST_INSERT_ID', vendorDetail('MySQL', 'query_editor.completion.action.last_insert_id')),
];
const PG_FUNCTIONS = [
fn('STRING_AGG', vendorDetail('PostgreSQL', 'query_editor.completion.action.string_aggregation')),
fn('ARRAY_AGG', vendorDetail('PostgreSQL', 'query_editor.completion.action.array_aggregation')),
fn('BOOL_AND', vendorDetail('PostgreSQL', 'query_editor.completion.action.boolean_and_aggregation')),
fn('BOOL_OR', vendorDetail('PostgreSQL', 'query_editor.completion.action.boolean_or_aggregation')),
fn('POSITION', vendorDetail('PostgreSQL', 'query_editor.completion.action.position_lookup')),
fn('EXTRACT', vendorDetail('PostgreSQL', 'query_editor.completion.action.date_field_extraction')),
fn('DATE_TRUNC', vendorDetail('PostgreSQL', 'query_editor.completion.action.date_truncation')),
fn('NOW', vendorDetail('PostgreSQL', 'query_editor.completion.action.current_time')),
fn('TO_CHAR', vendorDetail('PostgreSQL', 'query_editor.completion.action.format_as_text')),
fn('TO_DATE', vendorDetail('PostgreSQL', 'query_editor.completion.action.string_to_date')),
fn('TO_TIMESTAMP', vendorDetail('PostgreSQL', 'query_editor.completion.action.string_to_timestamp')),
fn('AGE', vendorDetail('PostgreSQL', 'query_editor.completion.action.time_difference')),
fn('RANDOM', vendorDetail('PostgreSQL', 'query_editor.completion.action.random_number')),
fn('CURRENT_DATABASE', vendorDetail('PostgreSQL', 'query_editor.completion.action.current_database')),
fn('JSONB_EXTRACT_PATH', vendorDetail('PostgreSQL', 'query_editor.completion.action.jsonb_path_extraction')),
];
const ORACLE_FUNCTIONS = [
fn('LISTAGG', vendorDetail('Oracle', 'query_editor.completion.action.string_aggregation')),
fn('NVL', vendorDetail('Oracle', 'query_editor.completion.action.null_replacement')),
fn('NVL2', vendorDetail('Oracle', 'query_editor.completion.action.null_branch')),
fn('DECODE', vendorDetail('Oracle', 'query_editor.completion.action.condition_mapping')),
fn('TO_DATE', vendorDetail('Oracle', 'query_editor.completion.action.string_to_date')),
fn('TO_TIMESTAMP', vendorDetail('Oracle', 'query_editor.completion.action.string_to_timestamp')),
fn('TO_CHAR', vendorDetail('Oracle', 'query_editor.completion.action.format_as_text')),
fn('TO_NUMBER', vendorDetail('Oracle', 'query_editor.completion.action.number_conversion')),
fn('TRUNC', vendorDetail('Oracle', 'query_editor.completion.action.truncate_date_or_number')),
fn('ADD_MONTHS', vendorDetail('Oracle', 'query_editor.completion.action.month_addition')),
fn('MONTHS_BETWEEN', vendorDetail('Oracle', 'query_editor.completion.action.month_difference')),
fn('LAST_DAY', vendorDetail('Oracle', 'query_editor.completion.action.month_end_date')),
fn('SYSDATE', vendorDetail('Oracle', 'query_editor.completion.action.database_current_time')),
fn('SYSTIMESTAMP', vendorDetail('Oracle', 'query_editor.completion.action.current_timestamp')),
fn('INSTR', vendorDetail('Oracle', 'query_editor.completion.action.position_lookup')),
fn('REGEXP_LIKE', vendorDetail('Oracle', 'query_editor.completion.action.regex_match')),
fn('REGEXP_REPLACE', vendorDetail('Oracle', 'query_editor.completion.action.regex_replace')),
fn('USER', vendorDetail('Oracle', 'query_editor.completion.action.current_user')),
];
const SQLSERVER_FUNCTIONS = [
fn('GETDATE', vendorDetail('SQL Server', 'query_editor.completion.action.current_date_time')),
fn('SYSDATETIME', vendorDetail('SQL Server', 'query_editor.completion.action.high_precision_current_time')),
fn('DATEADD', vendorDetail('SQL Server', 'query_editor.completion.action.date_addition')),
fn('DATEDIFF', vendorDetail('SQL Server', 'query_editor.completion.action.date_difference')),
fn('FORMAT', vendorDetail('SQL Server', 'query_editor.completion.action.value_formatting')),
fn('ISNULL', vendorDetail('SQL Server', 'query_editor.completion.action.null_replacement')),
fn('IIF', vendorDetail('SQL Server', 'query_editor.completion.action.conditional_check')),
fn('NEWID', vendorDetail('SQL Server', 'query_editor.completion.action.guid_generation')),
fn('STRING_AGG', vendorDetail('SQL Server', 'query_editor.completion.action.string_aggregation')),
fn('LEFT', vendorDetail('SQL Server', 'query_editor.completion.action.left_substring')),
fn('RIGHT', vendorDetail('SQL Server', 'query_editor.completion.action.right_substring')),
fn('LEN', vendorDetail('SQL Server', 'query_editor.completion.action.character_length')),
fn('CHARINDEX', vendorDetail('SQL Server', 'query_editor.completion.action.position_lookup')),
fn('TRY_CAST', vendorDetail('SQL Server', 'query_editor.completion.action.try_conversion')),
fn('TRY_CONVERT', vendorDetail('SQL Server', 'query_editor.completion.action.try_conversion')),
fn('DB_NAME', vendorDetail('SQL Server', 'query_editor.completion.action.current_database')),
];
const SQLITE_FUNCTIONS = [
fn('DATE', vendorDetail('SQLite', 'query_editor.completion.action.date_value')),
fn('TIME', vendorDetail('SQLite', 'query_editor.completion.action.time_value')),
fn('DATETIME', vendorDetail('SQLite', 'query_editor.completion.action.datetime_value')),
fn('JULIANDAY', vendorDetail('SQLite', 'query_editor.completion.action.julian_day')),
fn('STRFTIME', vendorDetail('SQLite', 'query_editor.completion.action.date_formatting')),
fn('IFNULL', vendorDetail('SQLite', 'query_editor.completion.action.null_replacement')),
fn('RANDOM', vendorDetail('SQLite', 'query_editor.completion.action.random_number')),
fn('PRINTF', vendorDetail('SQLite', 'query_editor.completion.action.value_formatting')),
fn('HEX', vendorDetail('SQLite', 'query_editor.completion.action.hexadecimal')),
fn('QUOTE', vendorDetail('SQLite', 'query_editor.completion.action.sql_literal')),
fn('JSON_EXTRACT', vendorDetail('SQLite', 'query_editor.completion.action.json_value_extraction')),
];
const DUCKDB_FUNCTIONS = [
fn('LIST', vendorDetail('DuckDB', 'query_editor.completion.action.list_aggregation')),
fn('STRUCT_PACK', vendorDetail('DuckDB', 'query_editor.completion.action.struct_construction')),
fn('UNNEST', vendorDetail('DuckDB', 'query_editor.completion.action.list_unnest')),
fn('STRFTIME', vendorDetail('DuckDB', 'query_editor.completion.action.date_formatting')),
fn('EPOCH', vendorDetail('DuckDB', 'query_editor.completion.action.epoch_seconds')),
fn('RANDOM', vendorDetail('DuckDB', 'query_editor.completion.action.random_number')),
fn('UUID', vendorDetail('DuckDB', 'query_editor.completion.action.uuid_generation')),
];
const CLICKHOUSE_FUNCTIONS = [
fn('now', vendorDetail('ClickHouse', 'query_editor.completion.action.current_time')),
fn('today', vendorDetail('ClickHouse', 'query_editor.completion.action.current_date')),
fn('toDate', vendorDetail('ClickHouse', 'query_editor.completion.action.date_conversion')),
fn('toDateTime', vendorDetail('ClickHouse', 'query_editor.completion.action.datetime_conversion')),
fn('formatDateTime', vendorDetail('ClickHouse', 'query_editor.completion.action.date_formatting')),
fn('groupArray', vendorDetail('ClickHouse', 'query_editor.completion.action.array_aggregation')),
fn('groupUniqArray', vendorDetail('ClickHouse', 'query_editor.completion.action.distinct_array_aggregation')),
fn('uniq', vendorDetail('ClickHouse', 'query_editor.completion.action.approximate_distinct')),
fn('uniqExact', vendorDetail('ClickHouse', 'query_editor.completion.action.exact_distinct')),
fn('quantile', vendorDetail('ClickHouse', 'query_editor.completion.action.quantile')),
fn('JSONExtractString', vendorDetail('ClickHouse', 'query_editor.completion.action.json_string_extraction')),
fn('toString', vendorDetail('ClickHouse', 'query_editor.completion.action.string_conversion')),
fn('toInt64', vendorDetail('ClickHouse', 'query_editor.completion.action.int64_conversion')),
];
const STARROCKS_FUNCTIONS = [
fn('DATE_FORMAT', vendorDetail('StarRocks', 'query_editor.completion.action.date_formatting')),
fn('STR_TO_DATE', vendorDetail('StarRocks', 'query_editor.completion.action.string_to_date')),
fn('FROM_UNIXTIME', vendorDetail('StarRocks', 'query_editor.completion.action.unix_time_to_datetime')),
fn('TO_BITMAP', vendorDetail('StarRocks', 'query_editor.completion.action.bitmap_construction')),
fn('BITMAP_UNION', vendorDetail('StarRocks', 'query_editor.completion.action.bitmap_aggregation')),
fn('BITMAP_COUNT', vendorDetail('StarRocks', 'query_editor.completion.action.bitmap_count')),
fn('HLL_HASH', vendorDetail('StarRocks', 'query_editor.completion.action.hll_hash')),
fn('HLL_UNION_AGG', vendorDetail('StarRocks', 'query_editor.completion.action.hll_aggregation')),
fn('APPROX_COUNT_DISTINCT', vendorDetail('StarRocks', 'query_editor.completion.action.approximate_distinct_count')),
fn('PERCENTILE_APPROX', vendorDetail('StarRocks', 'query_editor.completion.action.approximate_quantile')),
fn('GET_JSON_STRING', vendorDetail('StarRocks', 'query_editor.completion.action.json_string_extraction')),
fn('ARRAY_LENGTH', vendorDetail('StarRocks', 'query_editor.completion.action.array_length')),
];
const TDENGINE_FUNCTIONS = [
fn('NOW', vendorDetail('TDengine', 'query_editor.completion.action.current_time')),
fn('TODAY', vendorDetail('TDengine', 'query_editor.completion.action.current_date')),
fn('TIMEDIFF', vendorDetail('TDengine', 'query_editor.completion.action.time_difference')),
fn('ELAPSED', vendorDetail('TDengine', 'query_editor.completion.action.elapsed_time')),
fn('SPREAD', vendorDetail('TDengine', 'query_editor.completion.action.spread')),
fn('TWA', vendorDetail('TDengine', 'query_editor.completion.action.time_weighted_average')),
fn('LEASTSQUARES', vendorDetail('TDengine', 'query_editor.completion.action.least_squares')),
fn('APERCENTILE', vendorDetail('TDengine', 'query_editor.completion.action.approximate_percentile')),
fn('FIRST', vendorDetail('TDengine', 'query_editor.completion.action.first_value')),
fn('LAST', vendorDetail('TDengine', 'query_editor.completion.action.last_value')),
fn('LAST_ROW', vendorDetail('TDengine', 'query_editor.completion.action.last_row')),
fn('INTERP', vendorDetail('TDengine', 'query_editor.completion.action.interpolation')),
fn('RATE', vendorDetail('TDengine', 'query_editor.completion.action.rate_of_change')),
fn('IRATE', vendorDetail('TDengine', 'query_editor.completion.action.instant_rate_of_change')),
];
const IOTDB_FUNCTIONS = [
fn('NOW', vendorDetail('IoTDB', 'query_editor.completion.action.current_time')),
fn('DATE_BIN', vendorDetail('IoTDB', 'query_editor.completion.action.date_truncation')),
fn('DIFF', vendorDetail('IoTDB', 'query_editor.completion.action.time_difference')),
fn('TIME_DIFFERENCE', vendorDetail('IoTDB', 'query_editor.completion.action.time_difference')),
fn('DERIVATIVE', vendorDetail('IoTDB', 'query_editor.completion.action.rate_of_change')),
fn('NON_NEGATIVE_DERIVATIVE', vendorDetail('IoTDB', 'query_editor.completion.action.rate_of_change')),
fn('TOP_K', vendorDetail('IoTDB', 'query_editor.completion.action.maximum')),
fn('BOTTOM_K', vendorDetail('IoTDB', 'query_editor.completion.action.minimum')),
fn('M4', vendorDetail('IoTDB', 'query_editor.completion.action.approximate_quantile')),
fn('EQUAL_SIZE_BUCKET_RANDOM_SAMPLE', vendorDetail('IoTDB', 'query_editor.completion.action.random_number')),
];
const mergeFunctions = (items: SqlFunctionDefinition[]): SqlFunctionCompletion[] => {
const seen = new Set<string>();
const result: SqlFunctionCompletion[] = [];
for (const item of items) {
const key = item.name.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
result.push({
name: item.name,
detail: renderSqlFunctionDetail(item.detail),
});
}
return result;
};
export const resolveSqlFunctions = (dbType: string): SqlFunctionCompletion[] => {
const dialect = resolveSqlDialect(dbType);
if (dialect === 'starrocks') return mergeFunctions([...COMMON_FUNCTIONS, ...MYSQL_FUNCTIONS, ...STARROCKS_FUNCTIONS]);
if (isMysqlFamilyDialect(dialect)) return mergeFunctions([...COMMON_FUNCTIONS, ...MYSQL_FUNCTIONS]);
if (isPgLikeDialect(dialect)) return mergeFunctions([...COMMON_FUNCTIONS, ...PG_FUNCTIONS]);
if (isOracleLikeDialect(dialect)) return mergeFunctions([...COMMON_FUNCTIONS, ...ORACLE_FUNCTIONS]);
if (dialect === 'sqlserver') return mergeFunctions([...COMMON_FUNCTIONS, ...SQLSERVER_FUNCTIONS]);
if (dialect === 'sqlite') return mergeFunctions([...COMMON_FUNCTIONS, ...SQLITE_FUNCTIONS]);
if (dialect === 'duckdb') return mergeFunctions([...COMMON_FUNCTIONS, ...DUCKDB_FUNCTIONS]);
if (dialect === 'clickhouse') return mergeFunctions([...COMMON_FUNCTIONS, ...CLICKHOUSE_FUNCTIONS]);
if (dialect === 'tdengine') return mergeFunctions([...COMMON_FUNCTIONS, ...TDENGINE_FUNCTIONS]);
if (dialect === 'iotdb') return mergeFunctions([...COMMON_FUNCTIONS, ...IOTDB_FUNCTIONS]);
return mergeFunctions(COMMON_FUNCTIONS);
};