feat(gaussdb): 新增 GaussDB 数据库连接支持

Refs #497
This commit is contained in:
Syngnat
2026-06-13 19:34:52 +08:00
parent f3dfffb8d1
commit d2f68acae8
70 changed files with 717 additions and 73 deletions

View File

@@ -76,6 +76,19 @@ describe('ConnectionModal data source registry', () => {
expect(source).toContain('return "iotdb://root:root@127.0.0.1:6667/root.sg";');
expect(source).toContain('return "fetchSize=1024&timeZone=Asia%2FShanghai";');
});
it('exposes GaussDB in the create-connection picker with PostgreSQL-family defaults', () => {
expect(source).toContain("case 'gaussdb':");
expect(source).toContain('return 5432;');
expect(source).toContain('gaussdb: ["gaussdb", "postgresql", "postgres"]');
expect(source).toContain("key: 'gaussdb'");
expect(source).toContain("name: 'GaussDB'");
expect(source).toContain('type === "gaussdb"');
expect(source).toContain('return "gaussdb://user:pass@127.0.0.1:5432/db_name";');
expect(source).toContain('return "application_name=GoNavi&statement_timeout=30000";');
expect(source).toContain('? "gaussdb"');
expect(source).toContain('dbType === "gaussdb"');
});
});
describe('ConnectionModal Redis Sentinel configuration', () => {

View File

@@ -1676,7 +1676,8 @@ const ConnectionModal: React.FC<{
type === "kingbase" ||
type === "highgo" ||
type === "vastbase" ||
type === "opengauss"
type === "opengauss" ||
type === "gaussdb"
) {
const sslMode = String(parsed.params.get("sslmode") || "")
.trim()
@@ -1888,6 +1889,9 @@ const ConnectionModal: React.FC<{
if (dbType === "opengauss") {
return "opengauss://user:pass@127.0.0.1:5432/db_name";
}
if (dbType === "gaussdb") {
return "gaussdb://user:pass@127.0.0.1:5432/db_name";
}
return "例如: postgres://user:pass@127.0.0.1:5432/db_name";
};
@@ -1906,6 +1910,7 @@ const ConnectionModal: React.FC<{
case "highgo":
case "vastbase":
case "opengauss":
case "gaussdb":
return "application_name=GoNavi&statement_timeout=30000";
case "oracle":
return "PREFETCH_ROWS=5000&TRACE FILE=/tmp/go-ora.trc";
@@ -2062,7 +2067,9 @@ const ConnectionModal: React.FC<{
? normalizeClickHouseProtocolValue(values.clickHouseProtocol)
: "auto";
const scheme =
type === "postgres"
type === "gaussdb"
? "gaussdb"
: type === "postgres"
? "postgresql"
: type === "chroma" || type === "qdrant"
? values.useSSL
@@ -4826,7 +4833,8 @@ const ConnectionModal: React.FC<{
dbType === "kingbase" ||
dbType === "highgo" ||
dbType === "vastbase" ||
dbType === "opengauss") &&
dbType === "opengauss" ||
dbType === "gaussdb") &&
renderConfigSectionCard({
sectionKey: "service",
icon: <DatabaseOutlined />,

View File

@@ -39,6 +39,13 @@ describe('DatabaseIcons', () => {
expect(markup).toContain('>Io</text>');
});
it('includes GaussDB in the selectable database icons', () => {
expect(DB_ICON_TYPES).toContain('gaussdb');
expect(getDbIconLabel('gaussdb')).toBe('GaussDB');
const markup = renderToStaticMarkup(<>{getDbIcon('gaussdb', undefined, 22)}</>);
expect(markup).toContain('>GS</text>');
});
it('wraps database icons in a consistent frame for sidebar sizing', () => {
const mysqlMarkup = renderToStaticMarkup(<>{getDbIcon('mysql', undefined, 22)}</>);
const jvmMarkup = renderToStaticMarkup(<>{getDbIcon('jvm', undefined, 22)}</>);

View File

@@ -46,6 +46,7 @@ const DB_DEFAULT_COLORS: Record<string, string> = {
duckdb: '#FFC107',
vastbase: '#0066CC',
opengauss: '#2446A8',
gaussdb: '#0B7FAB',
highgo: '#00A86B',
iris: '#1F6FEB',
tdengine: '#2962FF',
@@ -172,6 +173,9 @@ const VastBaseIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
const OpenGaussIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.opengauss} label="OG" />
);
const GaussDBIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.gaussdb} label="GS" />
);
const HighGoIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.highgo} label="HG" />
);
@@ -240,6 +244,7 @@ const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
duckdb: DuckDBIcon,
vastbase: VastBaseIcon,
opengauss: OpenGaussIcon,
gaussdb: GaussDBIcon,
highgo: HighGoIcon,
iris: IrisIcon,
tdengine: TDengineIcon,
@@ -254,7 +259,7 @@ const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
export const DB_ICON_TYPES: string[] = [
'mysql', 'mariadb', 'oceanbase', 'postgres', 'redis', 'mongodb', 'jvm',
'oracle', 'sqlserver', 'sqlite', 'duckdb', 'clickhouse', 'starrocks',
'kingbase', 'dameng', 'vastbase', 'opengauss', 'highgo', 'iris', 'tdengine', 'iotdb', 'chroma', 'qdrant', 'elasticsearch', 'custom',
'kingbase', 'dameng', 'vastbase', 'opengauss', 'gaussdb', 'highgo', 'iris', 'tdengine', 'iotdb', 'chroma', 'qdrant', 'elasticsearch', 'custom',
];
/** 该类型是否有品牌 SVG 文件 */
@@ -276,7 +281,7 @@ export const getDbIconLabel = (type: string): string => {
sqlserver: 'SQL Server', clickhouse: 'ClickHouse', sqlite: 'SQLite',
starrocks: 'StarRocks',
duckdb: 'DuckDB', kingbase: '金仓', dameng: '达梦',
vastbase: 'VastBase', opengauss: 'OpenGauss', highgo: '瀚高', iris: 'InterSystems IRIS', tdengine: 'TDengine', iotdb: 'Apache IoTDB',
vastbase: 'VastBase', opengauss: 'OpenGauss', gaussdb: 'GaussDB', highgo: '瀚高', iris: 'InterSystems IRIS', tdengine: 'TDengine', iotdb: 'Apache IoTDB',
chroma: 'Chroma',
qdrant: 'Qdrant',
elasticsearch: 'Elasticsearch',

View File

@@ -98,6 +98,7 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
if (driver === 'diros' || driver === 'doris') return 'mysql';
if (driver === 'oceanbase') return normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
if (driver === 'gaussdb' || driver === 'gauss_db' || driver === 'gauss-db') return 'gaussdb';
return driver;
}
if (type === 'oceanbase' && normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle') return 'oracle';
@@ -196,7 +197,8 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
case 'kingbase':
case 'highgo':
case 'vastbase':
case 'opengauss': {
case 'opengauss':
case 'gaussdb': {
const schemaRef = schema || 'public';
return [`SELECT pg_get_viewdef('${escapeSQLLiteral(schemaRef)}.${safeName}'::regclass, true) AS view_definition`];
}
@@ -244,7 +246,8 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
case 'kingbase':
case 'highgo':
case 'vastbase':
case 'opengauss': {
case 'opengauss':
case 'gaussdb': {
const schemaRef = schema || 'public';
return [`SELECT pg_get_functiondef(p.oid) AS routine_definition FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = '${escapeSQLLiteral(schemaRef)}' AND p.proname = '${safeName}' LIMIT 1`];
}

View File

@@ -4291,6 +4291,7 @@ describe('QueryEditor external SQL save', () => {
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'sqlserver',
'sqlite',
'duckdb',

View File

@@ -189,7 +189,7 @@ const isSystemMetadataQueryResult = (tableRef: QueryResultTableRef, dbType: stri
if (['mysql', 'mariadb', 'oceanbase', 'diros', 'starrocks', 'sphinx', 'tidb'].includes(normalizedDbType)) {
return MYSQL_SYSTEM_METADATA_SCHEMAS.has(metadataDbName);
}
if (['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss'].includes(normalizedDbType)) {
if (['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss', 'gaussdb'].includes(normalizedDbType)) {
return POSTGRES_SYSTEM_METADATA_SCHEMAS.has(metadataDbName);
}
if (normalizedDbType === 'sqlite' || normalizedDbType === 'duckdb') {
@@ -479,6 +479,7 @@ const buildCompletionTableCommentSQL = (dialect: string, dbName: string): string
case 'vastbase':
case 'highgo':
case 'opengauss':
case 'gaussdb':
return `SELECT n.nspname || '.' || c.relname AS table_name, obj_description(c.oid, 'pg_class') AS table_comment FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'p') AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg|_%' ESCAPE '|' ORDER BY n.nspname, c.relname`;
case 'sqlserver': {
const safeDb = quoteSqlServerDbIdentifier(db);
@@ -685,6 +686,7 @@ const buildCompletionViewsMetadataQuerySpecs = (dialect: string, dbName: string)
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return [{ sql: `SELECT schemaname AS schema_name, viewname AS view_name FROM pg_catalog.pg_views WHERE schemaname != 'information_schema' AND schemaname NOT LIKE 'pg|_%' ESCAPE '|' ORDER BY schemaname, viewname` }];
case 'sqlserver': {
const safeDb = quoteSqlServerDbIdentifier(dbName || 'master');
@@ -748,6 +750,7 @@ const buildCompletionTriggersMetadataQuerySpecs = (dialect: string, dbName: stri
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return [{ sql: `SELECT DISTINCT event_object_schema AS schema_name, event_object_table AS table_name, trigger_name FROM information_schema.triggers WHERE trigger_schema NOT IN ('pg_catalog', 'information_schema') AND trigger_schema NOT LIKE 'pg|_%' ESCAPE '|' ORDER BY event_object_schema, event_object_table, trigger_name` }];
case 'sqlserver': {
const safeDb = quoteSqlServerDbIdentifier(dbName || 'master');
@@ -790,6 +793,7 @@ const buildCompletionFunctionsMetadataQuerySpecs = (dialect: string, dbName: str
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return normalizeMetadataQuerySpecs([
{
sql: `SELECT n.nspname AS schema_name, p.proname AS routine_name, CASE WHEN p.prokind = 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS routine_type FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg|_%' ESCAPE '|' ORDER BY n.nspname, routine_type, p.proname`,

View File

@@ -1009,6 +1009,7 @@ const Sidebar: React.FC<{
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'open_gauss',
'open-gauss',
'sqlserver',
@@ -1023,6 +1024,7 @@ const Sidebar: React.FC<{
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'open_gauss',
'open-gauss',
'sqlserver',
@@ -1171,6 +1173,7 @@ const Sidebar: React.FC<{
case 'vastbase':
case 'highgo':
case 'opengauss':
case 'gaussdb':
return [
"SELECT n.nspname || '.' || c.relname AS table_name, c.reltuples::bigint AS table_rows",
'FROM pg_class c',
@@ -1292,6 +1295,7 @@ const Sidebar: React.FC<{
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return [{ sql: `SELECT schemaname AS schema_name, viewname AS view_name FROM pg_catalog.pg_views WHERE schemaname != 'information_schema' AND schemaname NOT LIKE 'pg|_%' ESCAPE '|' ORDER BY schemaname, viewname` }];
case 'sqlserver': {
const safeDb = quoteSqlServerIdentifier(dbName || 'master');
@@ -1338,6 +1342,7 @@ const Sidebar: React.FC<{
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return [{ sql: `SELECT DISTINCT event_object_schema AS schema_name, event_object_table AS table_name, trigger_name FROM information_schema.triggers WHERE trigger_schema NOT IN ('pg_catalog', 'information_schema') AND trigger_schema NOT LIKE 'pg|_%' ESCAPE '|' ORDER BY event_object_schema, event_object_table, trigger_name` }];
case 'sqlserver': {
const safeDb = quoteSqlServerIdentifier(dbName || 'master');
@@ -1387,6 +1392,7 @@ const Sidebar: React.FC<{
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return normalizeMetadataQuerySpecs([
{
// PostgreSQL 11+ / 部分 PG-like通过 prokind 区分 FUNCTION/PROCEDURE
@@ -4299,7 +4305,7 @@ const Sidebar: React.FC<{
case 'starrocks':
query = `SHOW CREATE VIEW \`${viewName.replace(/`/g, '``')}\``;
break;
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': {
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': case 'gaussdb': {
const parts = splitQualifiedName(viewName);
const schema = parts.schemaName || 'public';
const name = parts.objectName || viewName;
@@ -4356,7 +4362,7 @@ const Sidebar: React.FC<{
case 'starrocks':
template = `CREATE VIEW \`view_name\` AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;`;
break;
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss':
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': case 'gaussdb':
template = `CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;`;
break;
case 'sqlserver':
@@ -4583,7 +4589,7 @@ const Sidebar: React.FC<{
case 'starrocks':
query = `SHOW CREATE ${routineType} \`${name.replace(/`/g, '``')}\``;
break;
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': {
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': case 'gaussdb': {
const schemaRef = schema || 'public';
query = `SELECT pg_get_functiondef(p.oid) AS routine_definition FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = '${escapeSQLLiteral(schemaRef)}' AND p.proname = '${escapeSQLLiteral(name)}' LIMIT 1`;
break;
@@ -4654,7 +4660,7 @@ const Sidebar: React.FC<{
? `DELIMITER $$\nCREATE PROCEDURE proc_name(IN param1 INT)\nBEGIN\n SELECT * FROM table_name WHERE id = param1;\nEND$$\nDELIMITER ;`
: `DELIMITER $$\nCREATE FUNCTION func_name(param1 INT)\nRETURNS INT\nDETERMINISTIC\nBEGIN\n RETURN param1 * 2;\nEND$$\nDELIMITER ;`;
break;
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss':
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': case 'gaussdb':
template = isProc
? `CREATE OR REPLACE PROCEDURE proc_name(param1 integer)\nLANGUAGE plpgsql\nAS $$\nBEGIN\n -- procedure body\nEND;\n$$;`
: `CREATE OR REPLACE FUNCTION func_name(param1 integer)\nRETURNS integer\nLANGUAGE plpgsql\nAS $$\nBEGIN\n RETURN param1 * 2;\nEND;\n$$;`;
@@ -5655,6 +5661,7 @@ const Sidebar: React.FC<{
const iconType = resolveConnectionIconType(conn);
if (iconType === 'mysql' || iconType === 'mariadb' || iconType === 'oceanbase') return 'MY';
if (iconType === 'postgres') return 'PG';
if (iconType === 'gaussdb') return 'GS';
if (iconType === 'redis') return 'R';
if (iconType === 'mongodb') return 'MO';
if (iconType === 'oracle') return 'OR';
@@ -5810,7 +5817,8 @@ const Sidebar: React.FC<{
case 'kingbase':
case 'vastbase':
case 'highgo':
case 'opengauss': {
case 'opengauss':
case 'gaussdb': {
const schema = schemaName || 'public';
return [
"SELECT c.reltuples::bigint AS table_rows, pg_total_relation_size(c.oid) AS data_length, pg_indexes_size(c.oid) AS index_length, 'heap' AS engine",

View File

@@ -930,6 +930,7 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em
if (normalized === 'mssql' || normalized === 'sql_server' || normalized === 'sql-server') return 'sqlserver';
if (normalized === 'doris') return 'diros';
if (normalized === 'open_gauss' || normalized === 'open-gauss') return 'opengauss';
if (normalized === 'gauss_db' || normalized === 'gauss-db') return 'gaussdb';
return normalized;
};
@@ -981,6 +982,7 @@ END;`;
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return `CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER AS $$
BEGIN
@@ -1039,6 +1041,7 @@ END;`;
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return `DROP TRIGGER IF EXISTS "${triggerName}" ON "${tblName}"`;
case 'sqlserver':
return `DROP TRIGGER IF EXISTS [${triggerName}]`;

View File

@@ -122,6 +122,7 @@ const getMetadataDialect = (connType: string, driver?: string, oceanBaseProtocol
if (d === 'diros' || d === 'doris') return 'mysql';
if (d === 'oceanbase') return normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
if (d === 'opengauss' || d === 'open_gauss' || d === 'open-gauss') return 'opengauss';
if (d === 'gaussdb' || d === 'gauss_db' || d === 'gauss-db') return 'gaussdb';
return d;
}
if (type === 'oceanbase' && normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle') return 'oracle';
@@ -158,7 +159,8 @@ ORDER BY table_name`;
case 'kingbase':
case 'vastbase':
case 'highgo':
case 'opengauss': {
case 'opengauss':
case 'gaussdb': {
const schema = schemaName || 'public';
return `
SELECT

View File

@@ -138,6 +138,7 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
if (driver === 'diros' || driver === 'doris') return 'mysql';
if (driver === 'oceanbase') return normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
if (driver === 'gaussdb' || driver === 'gauss_db' || driver === 'gauss-db') return 'gaussdb';
return driver;
}
if (type === 'oceanbase' && normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle') return 'oracle';
@@ -175,6 +176,7 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return [`SELECT pg_get_triggerdef(t.oid, true) AS trigger_definition
FROM pg_trigger t
JOIN pg_class c ON t.tgrelid = c.oid
@@ -310,7 +312,8 @@ LIMIT 1`];
case 'kingbase':
case 'highgo':
case 'vastbase':
case 'opengauss': {
case 'opengauss':
case 'gaussdb': {
return row.trigger_definition || row.TRIGGER_DEFINITION || Object.values(row)[0] || '';
}
case 'sqlserver': {

View File

@@ -37,8 +37,10 @@ describe('sidebarCoreUtils', () => {
it('normalizes driver aliases used by sidebar metadata loaders', () => {
expect(normalizeDriverType('postgresql')).toBe('postgres');
expect(normalizeDriverType('open-gauss')).toBe('opengauss');
expect(normalizeDriverType('gauss-db')).toBe('gaussdb');
expect(normalizeDriverType('InterSystemsIRIS')).toBe('iris');
expect(isPostgresSchemaDialect('kingbase')).toBe(true);
expect(isPostgresSchemaDialect('gauss-db')).toBe(true);
});
it('resolves draggable object labels by object kind', () => {

View File

@@ -8,8 +8,10 @@ describe('tableDataDangerActions', () => {
expect(supportsTableTruncateAction('oceanbase')).toBe(true);
expect(supportsTableTruncateAction('postgres')).toBe(true);
expect(supportsTableTruncateAction('opengauss')).toBe(true);
expect(supportsTableTruncateAction('gaussdb')).toBe(true);
expect(supportsTableTruncateAction('iris')).toBe(true);
expect(supportsTableTruncateAction('custom', 'postgresql')).toBe(true);
expect(supportsTableTruncateAction('custom', 'gauss_db')).toBe(true);
expect(supportsTableTruncateAction('custom', 'kingbase8')).toBe(true);
expect(supportsTableTruncateAction('custom', 'intersystemsiris')).toBe(true);
});

View File

@@ -13,6 +13,10 @@ const resolveCustomDriverDialect = (driver: string): string => {
case 'open_gauss':
case 'open-gauss':
return 'opengauss';
case 'gaussdb':
case 'gauss_db':
case 'gauss-db':
return 'gaussdb';
case 'dm':
case 'dameng':
case 'dm8':
@@ -49,6 +53,7 @@ const resolveCustomDriverDialect = (driver: string): string => {
}
if (normalized.includes('opengauss') || normalized.includes('open_gauss') || normalized.includes('open-gauss')) return 'opengauss';
if (normalized.includes('gaussdb') || normalized.includes('gauss_db') || normalized.includes('gauss-db')) return 'gaussdb';
if (normalized.includes('postgres')) return 'postgres';
if (normalized.includes('oceanbase')) return 'oceanbase';
if (normalized.includes('kingbase')) return 'kingbase';
@@ -81,6 +86,7 @@ export const supportsTableTruncateAction = (type: string, driver?: string): bool
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
case 'sqlserver':
case 'iris':
case 'oracle':

View File

@@ -300,6 +300,7 @@ const SUPPORTED_CONNECTION_TYPES = new Set([
"highgo",
"vastbase",
"opengauss",
"gaussdb",
"jvm",
"sqlite",
"duckdb",
@@ -321,6 +322,7 @@ const SSL_SUPPORTED_CONNECTION_TYPES = new Set([
"highgo",
"vastbase",
"opengauss",
"gaussdb",
"mongodb",
"redis",
"elasticsearch",
@@ -349,6 +351,7 @@ const getDefaultPortByType = (type: string): number => {
case "postgres":
case "vastbase":
case "opengauss":
case "gaussdb":
return 5432;
case "redis":
return 6379;
@@ -519,6 +522,9 @@ const normalizeConnectionType = (value: unknown): string => {
) {
return "opengauss";
}
if (type === "gaussdb" || type === "gauss_db" || type === "gauss-db") {
return "gaussdb";
}
if (
type === "inter-systems" ||
type === "inter-systems-iris" ||

View File

@@ -20,6 +20,7 @@ describe('connectionDriverType', () => {
expect(normalizeDriverType('apache_iotdb')).toBe('iotdb');
expect(normalizeDriverType('doris')).toBe('diros');
expect(normalizeDriverType('open-gauss')).toBe('opengauss');
expect(normalizeDriverType('gauss-db')).toBe('gaussdb');
expect(normalizeDriverType('InterSystemsIRIS')).toBe('iris');
});
@@ -27,6 +28,7 @@ describe('connectionDriverType', () => {
expect(resolveConnectionDriverType('mysql', 'postgresql')).toBe('mysql');
expect(resolveConnectionDriverType('custom', 'postgresql')).toBe('postgres');
expect(resolveConnectionDriverType('custom', 'open_gauss')).toBe('opengauss');
expect(resolveConnectionDriverType('custom', 'gauss_db')).toBe('gaussdb');
expect(resolveConnectionDriverType('custom', '')).toBe('');
});
@@ -44,6 +46,7 @@ describe('connectionDriverType', () => {
expect(isPostgresSchemaDialect('postgres')).toBe(true);
expect(isPostgresSchemaDialect('kingbase')).toBe(true);
expect(isPostgresSchemaDialect('open-gauss')).toBe(true);
expect(isPostgresSchemaDialect('gauss-db')).toBe(true);
expect(isPostgresSchemaDialect('mysql')).toBe(false);
});
});

View File

@@ -24,6 +24,11 @@ export const normalizeDriverType = (value: string): string => {
normalized === 'open-gauss' ||
normalized === 'opengauss'
) return 'opengauss';
if (
normalized === 'gaussdb' ||
normalized === 'gauss_db' ||
normalized === 'gauss-db'
) return 'gaussdb';
if (
normalized === 'intersystems' ||
normalized === 'intersystemsiris' ||
@@ -46,5 +51,5 @@ export const resolveSavedConnectionDriverType = (conn: SavedConnection | undefin
};
export const isPostgresSchemaDialect = (dialect: string): boolean => (
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss'].includes(normalizeDriverType(dialect))
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss', 'gaussdb'].includes(normalizeDriverType(dialect))
);

View File

@@ -84,6 +84,7 @@ describe('connectionModalPresentation', () => {
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'iris',
'mongodb',
'elasticsearch',
@@ -183,6 +184,14 @@ describe('connectionModalPresentation', () => {
'credentials',
'databaseScope',
]);
expect(resolveConnectionConfigLayout('gaussdb').sections).toEqual([
'identity',
'uri',
'target',
'service',
'credentials',
'databaseScope',
]);
});
it('uses localized labels for layout kinds shown in the modal', () => {

View File

@@ -71,6 +71,7 @@ const postgresCompatibleTypes = new Set([
'highgo',
'vastbase',
'opengauss',
'gaussdb',
]);
const fileDatabaseTypes = new Set(['sqlite', 'duckdb']);

View File

@@ -15,6 +15,7 @@ describe('connectionTypeCapabilities', () => {
it('keeps single-host URI scheme aliases for URI parsing', () => {
expect(singleHostUriSchemesByType.postgres).toEqual(['postgresql', 'postgres']);
expect(singleHostUriSchemesByType.opengauss).toContain('jdbc:opengauss');
expect(singleHostUriSchemesByType.gaussdb).toEqual(['gaussdb', 'postgresql', 'postgres']);
expect(singleHostUriSchemesByType.dameng).toEqual(['dameng', 'dm']);
expect(singleHostUriSchemesByType.elasticsearch).toEqual(['http', 'https']);
expect(singleHostUriSchemesByType.chroma).toEqual(['http', 'https', 'chroma']);
@@ -27,6 +28,7 @@ describe('connectionTypeCapabilities', () => {
expect(supportsSSLForType('redis')).toBe(true);
expect(supportsSSLForType('MongoDB')).toBe(true);
expect(supportsSSLForType('elasticsearch')).toBe(true);
expect(supportsSSLForType('gaussdb')).toBe(true);
expect(supportsSSLForType('chroma')).toBe(true);
expect(supportsSSLForType('qdrant')).toBe(true);
expect(supportsSSLForType('tdengine')).toBe(true);
@@ -38,6 +40,8 @@ describe('connectionTypeCapabilities', () => {
it('keeps CA path and client certificate support distinct', () => {
expect(supportsSSLCAPathForType('dameng')).toBe(false);
expect(supportsSSLClientCertificateForType('dameng')).toBe(true);
expect(supportsSSLCAPathForType('gaussdb')).toBe(true);
expect(supportsSSLClientCertificateForType('gaussdb')).toBe(true);
expect(supportsSSLCAPathForType('sqlserver')).toBe(true);
expect(supportsSSLClientCertificateForType('sqlserver')).toBe(false);
expect(supportsSSLCAPathForType('redis')).toBe(true);
@@ -51,6 +55,7 @@ describe('connectionTypeCapabilities', () => {
it('detects postgres-compatible SSL parameter dialects', () => {
expect(isPostgresCompatibleSSLType('postgres')).toBe(true);
expect(isPostgresCompatibleSSLType('kingbase')).toBe(true);
expect(isPostgresCompatibleSSLType('gaussdb')).toBe(true);
expect(isPostgresCompatibleSSLType('HighGo')).toBe(true);
expect(isPostgresCompatibleSSLType('mysql')).toBe(false);
});
@@ -68,6 +73,7 @@ describe('connectionTypeCapabilities', () => {
it('keeps advanced connection params enabled only for supported database types', () => {
expect(supportsConnectionParamsForType('mysql')).toBe(true);
expect(supportsConnectionParamsForType('postgres')).toBe(true);
expect(supportsConnectionParamsForType('gaussdb')).toBe(true);
expect(supportsConnectionParamsForType('oracle')).toBe(true);
expect(supportsConnectionParamsForType('mongodb')).toBe(true);
expect(supportsConnectionParamsForType('dameng')).toBe(true);

View File

@@ -1,6 +1,7 @@
export const singleHostUriSchemesByType: Record<string, string[]> = {
postgres: ["postgresql", "postgres"],
opengauss: ["opengauss", "jdbc:opengauss", "postgresql", "postgres"],
gaussdb: ["gaussdb", "postgresql", "postgres"],
clickhouse: ["clickhouse"],
oracle: ["oracle"],
sqlserver: ["sqlserver"],
@@ -39,6 +40,7 @@ const sslSupportedTypes = new Set([
"highgo",
"vastbase",
"opengauss",
"gaussdb",
"mongodb",
"redis",
"tdengine",
@@ -64,6 +66,7 @@ const sslCAPathSupportedTypes = new Set([
"highgo",
"vastbase",
"opengauss",
"gaussdb",
"mongodb",
"redis",
"elasticsearch",
@@ -85,6 +88,7 @@ const sslClientCertificateSupportedTypes = new Set([
"highgo",
"vastbase",
"opengauss",
"gaussdb",
"mongodb",
"redis",
]);
@@ -102,6 +106,7 @@ export const isPostgresCompatibleSSLType = (type: string) =>
"highgo",
"vastbase",
"opengauss",
"gaussdb",
].includes(normalizeConnectionType(type));
export const isFileDatabaseType = (type: string) =>
@@ -123,6 +128,7 @@ export const supportsConnectionParamsForType = (type: string) =>
type === "highgo" ||
type === "vastbase" ||
type === "opengauss" ||
type === "gaussdb" ||
type === "oracle" ||
type === "sqlserver" ||
type === "iris" ||

View File

@@ -21,6 +21,7 @@ describe('connectionTypeCatalog', () => {
const keys = getAllConnectionTypeCatalogItems().map((item) => item.key);
expect(keys).toContain('mysql');
expect(keys).toContain('oceanbase');
expect(keys).toContain('gaussdb');
expect(keys).toContain('mongodb');
expect(keys).toContain('redis');
expect(keys).toContain('elasticsearch');
@@ -37,6 +38,7 @@ describe('connectionTypeCatalog', () => {
expect(getConnectionTypeDefaultPort('oceanbase')).toBe(2881);
expect(getConnectionTypeDefaultPort('diros')).toBe(9030);
expect(getConnectionTypeDefaultPort('postgres')).toBe(5432);
expect(getConnectionTypeDefaultPort('gaussdb')).toBe(5432);
expect(getConnectionTypeDefaultPort('redis')).toBe(6379);
expect(getConnectionTypeDefaultPort('oracle')).toBe(1521);
expect(getConnectionTypeDefaultPort('mongodb')).toBe(27017);

View File

@@ -35,6 +35,7 @@ export const CONNECTION_TYPE_GROUPS: ConnectionTypeCatalogGroup[] = [
{ key: 'highgo', name: 'HighGo (瀚高)' },
{ key: 'vastbase', name: 'Vastbase (海量)' },
{ key: 'opengauss', name: 'OpenGauss' },
{ key: 'gaussdb', name: 'GaussDB' },
],
},
{
@@ -86,6 +87,7 @@ export const getConnectionTypeDefaultPort = (type: string): number => {
return 9000;
case 'postgres':
case 'opengauss':
case 'gaussdb':
return 5432;
case 'redis':
return 6379;

View File

@@ -54,6 +54,25 @@ describe('dataSourceCapabilities', () => {
});
});
it('treats GaussDB as an editable PostgreSQL-family datasource with database-level DDL actions', () => {
expect(getDataSourceCapabilities({ type: 'gaussdb' })).toMatchObject({
type: 'gaussdb',
supportsQueryEditor: true,
supportsSqlQueryExport: true,
supportsCopyInsert: true,
supportsCreateDatabase: true,
supportsRenameDatabase: true,
supportsDropDatabase: true,
forceReadOnlyQueryResult: false,
});
expect(getDataSourceCapabilities({ type: 'custom', driver: 'gauss-db' })).toMatchObject({
type: 'gaussdb',
supportsQueryEditor: true,
supportsCopyInsert: true,
supportsRenameDatabase: true,
});
});
it('treats Elasticsearch as a queryable read-only datasource', () => {
expect(getDataSourceCapabilities({ type: 'elasticsearch' })).toMatchObject({
type: 'elasticsearch',

View File

@@ -16,6 +16,10 @@ const normalizeDataSourceToken = (raw: string): string => {
case 'open_gauss':
case 'open-gauss':
return 'opengauss';
case 'gaussdb':
case 'gauss_db':
case 'gauss-db':
return 'gaussdb';
case 'dm':
return 'dameng';
case 'elastic':
@@ -68,6 +72,7 @@ const SQL_QUERY_EXPORT_TYPES = new Set([
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'sqlserver',
'iris',
'sqlite',
@@ -90,6 +95,7 @@ const COPY_INSERT_TYPES = new Set([
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'sqlserver',
'iris',
'sqlite',
@@ -131,6 +137,7 @@ const CREATE_DATABASE_TYPES = new Set([
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'sqlserver',
'tdengine',
'clickhouse',
@@ -143,6 +150,7 @@ const RENAME_DATABASE_TYPES = new Set([
'highgo',
'vastbase',
'opengauss',
'gaussdb',
]);
const DROP_DATABASE_TYPES = new Set([
@@ -156,6 +164,7 @@ const DROP_DATABASE_TYPES = new Set([
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'tdengine',
'clickhouse',
]);

View File

@@ -12,6 +12,7 @@ const resolveDdlFormatterLanguage = (dbType: string): SqlLanguage => {
case 'kingbase':
case 'highgo':
case 'opengauss':
case 'gaussdb':
case 'vastbase':
return 'postgresql';
case 'mariadb':

View File

@@ -7,4 +7,4 @@ export const DRIVER_LOCAL_IMPORT_SINGLE_FILE_HELP =
'行内“导入驱动包”仅用于单个驱动文件/总包(如 `mariadb-driver-agent`、`mariadb-driver-agent.exe`、`GoNavi-DriverAgents.zip`),不支持直接导入 JDBC Jar批量导入请使用上方“导入驱动目录”。';
export const CUSTOM_CONNECTION_DRIVER_HELP =
'已支持: mysql, starrocks, oceanbase, postgres, opengauss, sqlite, oracle, dm, kingbase别名支持 postgresql/pgx、open_gauss/open-gauss、dm8、kingbase8/kingbasees/kingbasev8。请填写 GoNavi 已注册的 Go database/sql 驱动名,不能直接填写系统 ODBC/JDBC 驱动名或导入 JDBC Jar。';
'已支持: mysql, starrocks, oceanbase, postgres, opengauss, gaussdb, sqlite, oracle, dm, kingbase别名支持 postgresql/pgx、open_gauss/open-gauss、gauss_db/gauss-db、dm8、kingbase8/kingbasees/kingbasev8。请填写 GoNavi 已注册的 Go database/sql 驱动名,不能直接填写系统 ODBC/JDBC 驱动名或导入 JDBC Jar。';

View File

@@ -18,6 +18,7 @@ describe('applyQueryAutoLimit', () => {
'highgo',
'vastbase',
'opengauss',
'gaussdb',
'iris',
'intersystemsiris',
'sqlite',
@@ -63,6 +64,7 @@ describe('applyQueryAutoLimit', () => {
['dm8', 'SELECT * FROM (SELECT * FROM users) WHERE ROWNUM <= 500'],
['mssql', 'SELECT TOP 500 * FROM users'],
['postgresql', 'SELECT * FROM users LIMIT 500'],
['gauss-db', 'SELECT * FROM users LIMIT 500'],
['doris', 'SELECT * FROM users LIMIT 500'],
['starrocks', 'SELECT * FROM users LIMIT 500'],
['sqlite3', 'SELECT * FROM users LIMIT 500'],

View File

@@ -11,6 +11,7 @@ const normalizeSidebarConnectionDialect = (type: string, driver: string, oceanBa
const normalizedDriver = String(driver || '').trim().toLowerCase();
if (normalizedDriver === 'postgresql' || normalizedDriver === 'postgres' || normalizedDriver === 'pg') return 'postgres';
if (normalizedDriver === 'opengauss' || normalizedDriver === 'open_gauss' || normalizedDriver === 'open-gauss') return 'opengauss';
if (normalizedDriver === 'gaussdb' || normalizedDriver === 'gauss_db' || normalizedDriver === 'gauss-db') return 'gaussdb';
if (normalizedDriver === 'dameng' || normalizedDriver === 'dm' || normalizedDriver === 'dm8') return 'dm';
if (normalizedDriver === 'oceanbase') {
return normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
@@ -22,6 +23,7 @@ const normalizeSidebarConnectionDialect = (type: string, driver: string, oceanBa
return normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
}
if (normalizedType === 'open_gauss' || normalizedType === 'open-gauss') return 'opengauss';
if (normalizedType === 'gauss_db' || normalizedType === 'gauss-db') return 'gaussdb';
if (normalizedType === 'dameng') return 'dm';
return normalizedType;
};

View File

@@ -34,7 +34,7 @@ export const quoteIdentPart = (dbType: string, ident: string) => {
}
// 对于 KingBase/PostgreSQL只在必要时加引号
if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres' || dbTypeLower === 'opengauss') {
if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres' || dbTypeLower === 'opengauss' || dbTypeLower === 'gaussdb') {
if (needsQuote(raw)) {
return `"${raw.replace(/"/g, '""')}"`;
}

View File

@@ -15,6 +15,7 @@ describe('sqlDialect', () => {
it('normalizes datasource aliases without collapsing all dialects to mysql', () => {
expect(resolveSqlDialect('postgresql')).toBe('postgres');
expect(resolveSqlDialect('OpenGauss')).toBe('opengauss');
expect(resolveSqlDialect('GaussDB')).toBe('gaussdb');
expect(resolveSqlDialect('OceanBase')).toBe('oceanbase');
expect(resolveSqlDialect('doris')).toBe('diros');
expect(resolveSqlDialect('StarRocks')).toBe('starrocks');
@@ -28,6 +29,7 @@ describe('sqlDialect', () => {
expect(resolveSqlDialect('custom', 'goldendb')).toBe('mysql');
expect(resolveSqlDialect('custom', 'greatdb')).toBe('mysql');
expect(resolveSqlDialect('custom', 'open_gauss')).toBe('opengauss');
expect(resolveSqlDialect('custom', 'gauss_db')).toBe('gaussdb');
expect(resolveSqlDialect('Elasticsearch')).toBe('elasticsearch');
expect(resolveSqlDialect('custom', 'elastic')).toBe('elasticsearch');
expect(resolveSqlDialect('ChromaDB')).toBe('chroma');
@@ -50,6 +52,7 @@ describe('sqlDialect', () => {
expect(values(resolveColumnTypeOptions('dameng'))).toContain('VARCHAR2(255)');
expect(values(resolveColumnTypeOptions('kingbase'))).toContain('integer');
expect(values(resolveColumnTypeOptions('opengauss'))).toContain('integer');
expect(values(resolveColumnTypeOptions('gaussdb'))).toContain('integer');
expect(values(resolveColumnTypeOptions('oceanbase'))).toContain('varchar(255)');
expect(values(resolveColumnTypeOptions('kingbase'))).not.toContain('tinyint(1)');
expect(values(resolveColumnTypeOptions('diros'))).toContain('LARGEINT');
@@ -68,6 +71,12 @@ describe('sqlDialect', () => {
expect(resolveSqlKeywords('iotdb')).not.toEqual(expect.arrayContaining(['TAGS', 'USING']));
});
it('resolves GaussDB completion keywords and functions as a PostgreSQL-like dialect', () => {
expect(resolveSqlKeywords('gaussdb')).toEqual(expect.arrayContaining(['RETURNING', 'SERIAL', 'JSONB']));
expect(names(resolveSqlFunctions('gaussdb'))).toEqual(expect.arrayContaining(['STRING_AGG', 'TO_CHAR', 'CURRENT_DATABASE']));
expect(resolveSqlKeywords('gaussdb')).not.toEqual(expect.arrayContaining(['AUTO_INCREMENT', 'CHANGE']));
});
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']));

View File

@@ -19,6 +19,7 @@ export type SqlDialect =
| 'highgo'
| 'vastbase'
| 'opengauss'
| 'gaussdb'
| 'oracle'
| 'dameng'
| 'sqlserver'
@@ -69,6 +70,10 @@ export const resolveSqlDialect = (
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':
@@ -135,6 +140,7 @@ export const resolveSqlDialect = (
}
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';
@@ -167,7 +173,7 @@ export const isMysqlFamilyDialect = (dbType: string): boolean => (
);
export const isPgLikeDialect = (dbType: string): boolean => (
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss'].includes(resolveSqlDialect(dbType))
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss', 'gaussdb'].includes(resolveSqlDialect(dbType))
);
export const isOracleLikeDialect = (dbType: string): boolean => (