mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-09 14:31:35 +08:00
@@ -16,6 +16,8 @@ describe('connectionDriverType', () => {
|
||||
expect(normalizeDriverType('chroma-db')).toBe('chroma');
|
||||
expect(normalizeDriverType('qdrantdb')).toBe('qdrant');
|
||||
expect(normalizeDriverType('qdrant-db')).toBe('qdrant');
|
||||
expect(normalizeDriverType('apache-iotdb')).toBe('iotdb');
|
||||
expect(normalizeDriverType('apache_iotdb')).toBe('iotdb');
|
||||
expect(normalizeDriverType('doris')).toBe('diros');
|
||||
expect(normalizeDriverType('open-gauss')).toBe('opengauss');
|
||||
expect(normalizeDriverType('InterSystemsIRIS')).toBe('iris');
|
||||
|
||||
@@ -17,6 +17,7 @@ export const normalizeDriverType = (value: string): string => {
|
||||
if (normalized === 'elastic') return 'elasticsearch';
|
||||
if (normalized === 'chromadb' || normalized === 'chroma-db') return 'chroma';
|
||||
if (normalized === 'qdrantdb' || normalized === 'qdrant-db') return 'qdrant';
|
||||
if (normalized === 'apache-iotdb' || normalized === 'apache_iotdb') return 'iotdb';
|
||||
if (normalized === 'doris') return 'diros';
|
||||
if (
|
||||
normalized === 'open_gauss' ||
|
||||
|
||||
@@ -91,6 +91,7 @@ describe('connectionModalPresentation', () => {
|
||||
'qdrant',
|
||||
'redis',
|
||||
'tdengine',
|
||||
'iotdb',
|
||||
'custom',
|
||||
'jvm',
|
||||
];
|
||||
@@ -174,6 +175,14 @@ describe('connectionModalPresentation', () => {
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
]);
|
||||
expect(resolveConnectionConfigLayout('iotdb').sections).toEqual([
|
||||
'identity',
|
||||
'uri',
|
||||
'target',
|
||||
'service',
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses localized labels for layout kinds shown in the modal', () => {
|
||||
@@ -181,5 +190,6 @@ describe('connectionModalPresentation', () => {
|
||||
expect(getConnectionConfigLayoutKindLabel('file')).toBe('文件型数据库');
|
||||
expect(getConnectionConfigLayoutKindLabel('search')).toBe('搜索引擎');
|
||||
expect(getConnectionConfigLayoutKindLabel('vector')).toBe('向量数据库');
|
||||
expect(getConnectionConfigLayoutKindLabel('timeseries')).toBe('时序数据库');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ export type ConnectionConfigLayoutKind =
|
||||
| 'file'
|
||||
| 'search'
|
||||
| 'vector'
|
||||
| 'timeseries'
|
||||
| 'custom'
|
||||
| 'jvm'
|
||||
| 'generic-sql';
|
||||
@@ -163,6 +164,8 @@ export const getConnectionConfigLayoutKindLabel = (
|
||||
return '搜索引擎';
|
||||
case 'vector':
|
||||
return '向量数据库';
|
||||
case 'timeseries':
|
||||
return '时序数据库';
|
||||
case 'custom':
|
||||
return '自定义连接';
|
||||
case 'jvm':
|
||||
@@ -265,6 +268,19 @@ export const resolveConnectionConfigLayout = (
|
||||
],
|
||||
};
|
||||
}
|
||||
if (type === 'iotdb') {
|
||||
return {
|
||||
kind: 'timeseries',
|
||||
sections: [
|
||||
'identity',
|
||||
'uri',
|
||||
'target',
|
||||
'service',
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
],
|
||||
};
|
||||
}
|
||||
if (postgresCompatibleTypes.has(type)) {
|
||||
return {
|
||||
kind: 'postgres-compatible',
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(singleHostUriSchemesByType.elasticsearch).toEqual(['http', 'https']);
|
||||
expect(singleHostUriSchemesByType.chroma).toEqual(['http', 'https', 'chroma']);
|
||||
expect(singleHostUriSchemesByType.qdrant).toEqual(['http', 'https', 'qdrant']);
|
||||
expect(singleHostUriSchemesByType.iotdb).toEqual(['iotdb']);
|
||||
expect(singleHostUriSchemesByType.redis).toEqual(['redis']);
|
||||
});
|
||||
|
||||
@@ -29,6 +30,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsSSLForType('chroma')).toBe(true);
|
||||
expect(supportsSSLForType('qdrant')).toBe(true);
|
||||
expect(supportsSSLForType('tdengine')).toBe(true);
|
||||
expect(supportsSSLForType('iotdb')).toBe(false);
|
||||
expect(supportsSSLForType('dameng')).toBe(true);
|
||||
expect(supportsSSLForType('sqlite')).toBe(false);
|
||||
});
|
||||
@@ -70,6 +72,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsConnectionParamsForType('mongodb')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('dameng')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('tdengine')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('iotdb')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('elasticsearch')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('chroma')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('qdrant')).toBe(true);
|
||||
|
||||
@@ -7,6 +7,7 @@ export const singleHostUriSchemesByType: Record<string, string[]> = {
|
||||
iris: ["iris", "intersystems"],
|
||||
redis: ["redis"],
|
||||
tdengine: ["tdengine"],
|
||||
iotdb: ["iotdb"],
|
||||
dameng: ["dameng", "dm"],
|
||||
kingbase: ["kingbase"],
|
||||
highgo: ["highgo"],
|
||||
@@ -129,6 +130,7 @@ export const supportsConnectionParamsForType = (type: string) =>
|
||||
type === "mongodb" ||
|
||||
type === "dameng" ||
|
||||
type === "tdengine" ||
|
||||
type === "iotdb" ||
|
||||
type === "elasticsearch" ||
|
||||
type === "chroma" ||
|
||||
type === "qdrant";
|
||||
|
||||
@@ -26,6 +26,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(keys).toContain('elasticsearch');
|
||||
expect(keys).toContain('chroma');
|
||||
expect(keys).toContain('qdrant');
|
||||
expect(keys).toContain('iotdb');
|
||||
expect(keys).toContain('jvm');
|
||||
expect(keys).toContain('custom');
|
||||
expect(new Set(keys).size).toBe(keys.length);
|
||||
@@ -42,6 +43,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(getConnectionTypeDefaultPort('elasticsearch')).toBe(9200);
|
||||
expect(getConnectionTypeDefaultPort('chroma')).toBe(8000);
|
||||
expect(getConnectionTypeDefaultPort('qdrant')).toBe(6333);
|
||||
expect(getConnectionTypeDefaultPort('iotdb')).toBe(6667);
|
||||
expect(getConnectionTypeDefaultPort('sqlite')).toBe(0);
|
||||
expect(getConnectionTypeDefaultPort('duckdb')).toBe(0);
|
||||
expect(getConnectionTypeDefaultPort('unknown')).toBe(3306);
|
||||
@@ -53,6 +55,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(getConnectionTypeHint('elasticsearch')).toContain('Mapping');
|
||||
expect(getConnectionTypeHint('chroma')).toContain('向量');
|
||||
expect(getConnectionTypeHint('qdrant')).toContain('Payload');
|
||||
expect(getConnectionTypeHint('iotdb')).toContain('Timeseries');
|
||||
expect(getConnectionTypeHint('oceanbase')).toBe('MySQL / Oracle 租户');
|
||||
expect(getConnectionTypeHint('duckdb')).toBe('本地文件连接');
|
||||
expect(getConnectionTypeHint('mysql')).toBe('标准连接配置');
|
||||
|
||||
@@ -56,6 +56,7 @@ export const CONNECTION_TYPE_GROUPS: ConnectionTypeCatalogGroup[] = [
|
||||
label: '时序数据库',
|
||||
items: [
|
||||
{ key: 'tdengine', name: 'TDengine' },
|
||||
{ key: 'iotdb', name: 'Apache IoTDB' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -90,6 +91,8 @@ export const getConnectionTypeDefaultPort = (type: string): number => {
|
||||
return 6379;
|
||||
case 'tdengine':
|
||||
return 6041;
|
||||
case 'iotdb':
|
||||
return 6667;
|
||||
case 'oracle':
|
||||
return 1521;
|
||||
case 'dameng':
|
||||
@@ -138,6 +141,8 @@ export const getConnectionTypeHint = (type: string): string => {
|
||||
return 'Collection 浏览、向量检索和元数据过滤';
|
||||
case 'qdrant':
|
||||
return 'Collection 浏览、向量搜索和 Payload 过滤';
|
||||
case 'iotdb':
|
||||
return 'Storage Group / Device / Timeseries';
|
||||
case 'oceanbase':
|
||||
return 'MySQL / Oracle 租户';
|
||||
case 'sqlite':
|
||||
|
||||
@@ -108,6 +108,25 @@ describe('dataSourceCapabilities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('treats Apache IoTDB as a queryable timeseries datasource with IoTDB-specific writes', () => {
|
||||
expect(getDataSourceCapabilities({ type: 'iotdb' })).toMatchObject({
|
||||
type: 'iotdb',
|
||||
supportsQueryEditor: true,
|
||||
supportsSqlQueryExport: false,
|
||||
supportsCopyInsert: false,
|
||||
supportsCreateDatabase: false,
|
||||
supportsRenameDatabase: false,
|
||||
supportsDropDatabase: false,
|
||||
forceReadOnlyQueryResult: true,
|
||||
});
|
||||
expect(getDataSourceCapabilities({ type: 'custom', driver: 'apache-iotdb' })).toMatchObject({
|
||||
type: 'iotdb',
|
||||
supportsQueryEditor: true,
|
||||
supportsCopyInsert: false,
|
||||
forceReadOnlyQueryResult: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats OceanBase Oracle protocol as Oracle capabilities', () => {
|
||||
expect(getDataSourceCapabilities({
|
||||
type: 'oceanbase',
|
||||
|
||||
@@ -27,6 +27,9 @@ const normalizeDataSourceToken = (raw: string): string => {
|
||||
case 'qdrantdb':
|
||||
case 'qdrant-db':
|
||||
return 'qdrant';
|
||||
case 'apache-iotdb':
|
||||
case 'apache_iotdb':
|
||||
return 'iotdb';
|
||||
case 'intersystems':
|
||||
case 'intersystemsiris':
|
||||
case 'inter-systems':
|
||||
@@ -98,7 +101,7 @@ const COPY_INSERT_TYPES = new Set([
|
||||
]);
|
||||
|
||||
const QUERY_EDITOR_DISABLED_TYPES = new Set(['redis']);
|
||||
const FORCE_READ_ONLY_QUERY_TYPES = new Set(['tdengine', 'clickhouse']);
|
||||
const FORCE_READ_ONLY_QUERY_TYPES = new Set(['tdengine', 'iotdb', 'clickhouse']);
|
||||
const MANUAL_TOTAL_COUNT_TYPES = new Set(['duckdb', 'oracle']);
|
||||
const APPROXIMATE_TABLE_COUNT_TYPES = new Set(['duckdb', 'oracle']);
|
||||
const APPROXIMATE_TOTAL_PAGE_TYPES = new Set(['duckdb']);
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('applyQueryAutoLimit', () => {
|
||||
'duckdb',
|
||||
'clickhouse',
|
||||
'tdengine',
|
||||
'iotdb',
|
||||
];
|
||||
|
||||
it.each(limitDialects)('adds generic LIMIT for %s connections', (dbType) => {
|
||||
|
||||
@@ -54,6 +54,11 @@ describe('reverseOrderBySQL', () => {
|
||||
});
|
||||
|
||||
describe('quoteQualifiedIdent', () => {
|
||||
it('quotes Apache IoTDB device paths with backticks per path segment', () => {
|
||||
expect(quoteQualifiedIdent('iotdb', 'root.sg.d1'))
|
||||
.toBe('`root`.`sg`.`d1`');
|
||||
});
|
||||
|
||||
it('does not split dots inside quoted DuckDB identifiers', () => {
|
||||
expect(quoteQualifiedIdent('duckdb', '"daily.events"."2026.06"'))
|
||||
.toBe('"daily.events"."2026.06"');
|
||||
|
||||
@@ -29,7 +29,7 @@ export const quoteIdentPart = (dbType: string, ident: string) => {
|
||||
if (!raw) return raw;
|
||||
const dbTypeLower = (dbType || '').toLowerCase();
|
||||
|
||||
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'starrocks' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'clickhouse') {
|
||||
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'starrocks' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'iotdb' || dbTypeLower === 'clickhouse') {
|
||||
return `\`${raw.replace(/`/g, '``')}\``;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ describe('sqlDialect', () => {
|
||||
expect(resolveSqlDialect('custom', 'chroma-db')).toBe('chroma');
|
||||
expect(resolveSqlDialect('QdrantDB')).toBe('qdrant');
|
||||
expect(resolveSqlDialect('custom', 'qdrant-db')).toBe('qdrant');
|
||||
expect(resolveSqlDialect('Apache-IoTDB')).toBe('iotdb');
|
||||
expect(resolveSqlDialect('custom', 'apache_iotdb')).toBe('iotdb');
|
||||
expect(resolveSqlDialect('OceanBase', '', { oceanBaseProtocol: 'oracle' })).toBe('oracle');
|
||||
expect(resolveSqlDialect('custom', 'oceanbase', { oceanBaseProtocol: 'oracle' })).toBe('oracle');
|
||||
expect(isMysqlFamilyDialect('mariadb')).toBe(true);
|
||||
@@ -56,9 +58,16 @@ describe('sqlDialect', () => {
|
||||
expect(values(resolveColumnTypeOptions('clickhouse'))).toContain('DateTime64(3)');
|
||||
expect(values(resolveColumnTypeOptions('iris'))).toContain('varchar(255)');
|
||||
expect(values(resolveColumnTypeOptions('tdengine'))).toContain('TIMESTAMP');
|
||||
expect(values(resolveColumnTypeOptions('iotdb'))).toContain('INT64');
|
||||
expect(values(resolveColumnTypeOptions('duckdb'))).toContain('STRUCT');
|
||||
});
|
||||
|
||||
it('resolves Apache IoTDB completion keywords and functions independently', () => {
|
||||
expect(resolveSqlKeywords('iotdb')).toEqual(expect.arrayContaining(['ALIGN BY DEVICE', 'SHOW TIMESERIES', 'WITH DATATYPE']));
|
||||
expect(names(resolveSqlFunctions('iotdb'))).toEqual(expect.arrayContaining(['DATE_BIN', 'DIFF', 'TOP_K']));
|
||||
expect(resolveSqlKeywords('iotdb')).not.toEqual(expect.arrayContaining(['TAGS', 'USING']));
|
||||
});
|
||||
|
||||
it('resolves oracle completion keywords and functions without mysql-only suggestions', () => {
|
||||
expect(resolveSqlKeywords('oracle')).toEqual(expect.arrayContaining(['ROWNUM', 'FETCH', 'VARCHAR2', 'NUMBER']));
|
||||
expect(resolveSqlKeywords('oracle')).not.toEqual(expect.arrayContaining(['AUTO_INCREMENT', 'CHANGE', 'LIMIT']));
|
||||
|
||||
@@ -27,6 +27,7 @@ export type SqlDialect =
|
||||
| 'duckdb'
|
||||
| 'clickhouse'
|
||||
| 'tdengine'
|
||||
| 'iotdb'
|
||||
| 'mongodb'
|
||||
| 'redis'
|
||||
| 'elasticsearch'
|
||||
@@ -111,6 +112,7 @@ export const resolveSqlDialect = (
|
||||
case 'duckdb':
|
||||
case 'clickhouse':
|
||||
case 'tdengine':
|
||||
case 'iotdb':
|
||||
case 'mongodb':
|
||||
case 'redis':
|
||||
case 'elasticsearch':
|
||||
@@ -125,6 +127,9 @@ export const resolveSqlDialect = (
|
||||
case 'qdrant-db':
|
||||
case 'qdrant':
|
||||
return 'qdrant';
|
||||
case 'apache-iotdb':
|
||||
case 'apache_iotdb':
|
||||
return 'iotdb';
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -147,6 +152,7 @@ export const resolveSqlDialect = (
|
||||
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('sqlserver') || source.includes('mssql')) return 'sqlserver';
|
||||
if (source.includes('iris') || source.includes('intersystems')) return 'iris';
|
||||
if (source.includes('elastic')) return 'elasticsearch';
|
||||
@@ -171,7 +177,7 @@ export const isOracleLikeDialect = (dbType: string): boolean => (
|
||||
export const isSqlServerDialect = (dbType: string): boolean => resolveSqlDialect(dbType) === 'sqlserver';
|
||||
|
||||
export const isBacktickIdentifierDialect = (dbType: string): boolean => (
|
||||
isMysqlFamilyDialect(dbType) || ['clickhouse', 'tdengine'].includes(resolveSqlDialect(dbType))
|
||||
isMysqlFamilyDialect(dbType) || ['clickhouse', 'tdengine', 'iotdb'].includes(resolveSqlDialect(dbType))
|
||||
);
|
||||
|
||||
const stripIdentifierQuotes = (part: string): string => {
|
||||
@@ -470,6 +476,19 @@ const TDENGINE_TYPES = optionValues([
|
||||
'GEOMETRY',
|
||||
]);
|
||||
|
||||
const IOTDB_TYPES = optionValues([
|
||||
'BOOLEAN',
|
||||
'INT32',
|
||||
'INT64',
|
||||
'FLOAT',
|
||||
'DOUBLE',
|
||||
'TEXT',
|
||||
'STRING',
|
||||
'BLOB',
|
||||
'TIMESTAMP',
|
||||
'DATE',
|
||||
]);
|
||||
|
||||
const DUCKDB_TYPES = optionValues([
|
||||
'BOOLEAN',
|
||||
'TINYINT',
|
||||
@@ -514,6 +533,7 @@ export const resolveColumnTypeOptions = (dbType: string): ColumnTypeOption[] =>
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -565,6 +585,26 @@ const STARROCKS_KEYWORDS = [
|
||||
|
||||
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',
|
||||
];
|
||||
|
||||
export const resolveSqlKeywords = (dbType: string): string[] => {
|
||||
const dialect = resolveSqlDialect(dbType);
|
||||
if (dialect === 'starrocks') return unique([...COMMON_KEYWORDS, ...MYSQL_KEYWORDS, ...STARROCKS_KEYWORDS]);
|
||||
@@ -576,6 +616,7 @@ export const resolveSqlKeywords = (dbType: string): string[] => {
|
||||
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]);
|
||||
return COMMON_KEYWORDS;
|
||||
};
|
||||
|
||||
@@ -793,6 +834,19 @@ const TDENGINE_FUNCTIONS = [
|
||||
fn('IRATE', 'TDengine - 瞬时变化率'),
|
||||
];
|
||||
|
||||
const IOTDB_FUNCTIONS = [
|
||||
fn('NOW', 'IoTDB - 当前时间'),
|
||||
fn('DATE_BIN', 'IoTDB - 时间分桶'),
|
||||
fn('DIFF', 'IoTDB - 差分'),
|
||||
fn('TIME_DIFFERENCE', 'IoTDB - 时间差'),
|
||||
fn('DERIVATIVE', 'IoTDB - 导数'),
|
||||
fn('NON_NEGATIVE_DERIVATIVE', 'IoTDB - 非负导数'),
|
||||
fn('TOP_K', 'IoTDB - Top K'),
|
||||
fn('BOTTOM_K', 'IoTDB - Bottom K'),
|
||||
fn('M4', 'IoTDB - M4 降采样'),
|
||||
fn('EQUAL_SIZE_BUCKET_RANDOM_SAMPLE', 'IoTDB - 等宽随机采样'),
|
||||
];
|
||||
|
||||
const mergeFunctions = (items: SqlFunctionCompletion[]): SqlFunctionCompletion[] => {
|
||||
const seen = new Set<string>();
|
||||
const result: SqlFunctionCompletion[] = [];
|
||||
@@ -816,5 +870,6 @@ export const resolveSqlFunctions = (dbType: string): SqlFunctionCompletion[] =>
|
||||
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 COMMON_FUNCTIONS;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user