Files
MyGoNavi/frontend/src/utils/sqlDialect.ts
tianqijiuyun-latiao 76b0163bd3 Merge branch 'dev' into feature/20260602_connection_driver_i18n
# Conflicts:
#	frontend/package.json.md5
#	frontend/src/App.tsx
#	frontend/src/components/AIChatPanel.message-boundary.test.tsx
#	frontend/src/components/AIChatPanel.tsx
#	frontend/src/components/AISettingsModal.tsx
#	frontend/src/components/ConnectionModal.tsx
#	frontend/src/components/DataGrid.ddl.test.tsx
#	frontend/src/components/DataGrid.layout.test.tsx
#	frontend/src/components/DataGrid.tsx
#	frontend/src/components/DataGridColumnTitle.test.tsx
#	frontend/src/components/DataGridLegacyCellContextMenu.tsx
#	frontend/src/components/DataGridSecondaryActions.tsx
#	frontend/src/components/DataGridToolbarFrame.tsx
#	frontend/src/components/DataSyncModal.tsx
#	frontend/src/components/DataViewer.tsx
#	frontend/src/components/DefinitionViewer.tsx
#	frontend/src/components/DriverManagerModal.tsx
#	frontend/src/components/QueryEditor.external-sql-save.test.tsx
#	frontend/src/components/QueryEditor.tsx
#	frontend/src/components/RedisViewer.tsx
#	frontend/src/components/Sidebar.locate-toolbar.test.tsx
#	frontend/src/components/Sidebar.tsx
#	frontend/src/components/TabManager.hover.test.tsx
#	frontend/src/components/TabManager.tsx
#	frontend/src/components/TableDesigner.tsx
#	frontend/src/components/V2TableContextMenu.tsx
#	frontend/src/components/ai/AIChatHeader.tsx
#	frontend/src/components/ai/AIHistoryDrawer.tsx
#	frontend/src/main.tsx
#	frontend/src/store.ts
#	frontend/src/utils/aiComposerNotice.test.ts
#	frontend/src/utils/aiComposerNotice.ts
#	frontend/src/utils/connectionModalPresentation.ts
#	frontend/src/utils/driverImportGuidance.ts
#	frontend/src/utils/externalSqlTree.test.ts
#	frontend/src/utils/externalSqlTree.ts
#	frontend/src/utils/sqlDialect.ts
#	internal/ai/service/service.go
2026-06-16 18:35:11 +08:00

975 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'
| '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 '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';
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',
'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',
];
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);
};