diff --git a/frontend/src/components/ConnectionModal.edit-password.test.tsx b/frontend/src/components/ConnectionModal.edit-password.test.tsx index 59f285d..d97172c 100644 --- a/frontend/src/components/ConnectionModal.edit-password.test.tsx +++ b/frontend/src/components/ConnectionModal.edit-password.test.tsx @@ -101,6 +101,18 @@ describe('ConnectionModal data source registry', () => { expect(source).toContain('? "gaussdb"'); expect(source).toContain('dbType === "gaussdb"'); }); + + it('exposes GoldenDB in the create-connection picker with MySQL-compatible defaults', () => { + expect(source).toContain("case 'goldendb':"); + expect(source).toContain('return 1523;'); + expect(source).toContain("key: 'goldendb'"); + expect(source).toContain("name: 'GoldenDB'"); + expect(source).toContain('type === "goldendb"'); + expect(source).toContain("return 'MySQL 兼容 / 分布式事务';"); + expect(source).toContain('dbType === "goldendb" ? "goldendb" : "mysql"'); + expect(source).toContain('type === "goldendb" ? "goldendb" : "mysql"'); + expect(source).toContain('? "goldendb"'); + }); }); describe('ConnectionModal Redis Sentinel configuration', () => { diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index 6283d6c..f9f4f1c 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -1458,6 +1458,9 @@ const ConnectionModal: React.FC<{ const mysqlDefaultPort = getDefaultPortByType(type); const parsed = parseMultiHostUri(trimmedUri, "mysql") || + parseMultiHostUri(trimmedUri, "goldendb") || + parseMultiHostUri(trimmedUri, "greatdb") || + parseMultiHostUri(trimmedUri, "gdb") || parseMultiHostUri(trimmedUri, "jdbc:mysql") || parseMultiHostUri(trimmedUri, "oceanbase") || parseMultiHostUri(trimmedUri, "jdbc:oceanbase") || @@ -1909,7 +1912,7 @@ const ConnectionModal: React.FC<{ if (isMySQLCompatibleType(dbType)) { const defaultPort = getDefaultPortByType(dbType); const scheme = - dbType === "diros" ? "doris" : dbType === "starrocks" ? "starrocks" : dbType === "oceanbase" ? "oceanbase" : "mysql"; + dbType === "diros" ? "doris" : dbType === "starrocks" ? "starrocks" : dbType === "oceanbase" ? "oceanbase" : dbType === "goldendb" ? "goldendb" : "mysql"; if (dbType === "oceanbase") { return `${scheme}://sys%40oracle001:pass@127.0.0.1:${defaultPort}/SERVICE_NAME?protocol=oracle`; } @@ -2053,7 +2056,7 @@ const ConnectionModal: React.FC<{ const dbPath = database ? `/${encodeURIComponent(database)}` : "/"; const query = params.toString(); const scheme = - type === "diros" ? "doris" : type === "starrocks" ? "starrocks" : type === "oceanbase" ? "oceanbase" : "mysql"; + type === "diros" ? "doris" : type === "starrocks" ? "starrocks" : type === "oceanbase" ? "oceanbase" : type === "goldendb" ? "goldendb" : "mysql"; return `${scheme}://${encodedAuth}${hosts.join(",")}${dbPath}${query ? `?${query}` : ""}`; } @@ -2450,6 +2453,7 @@ const ConnectionModal: React.FC<{ : primaryAddress?.port || Number(config.port || defaultPort); const mysqlReplicaHosts = configType === "mysql" || + configType === "goldendb" || configType === "mariadb" || configType === "oceanbase" || configType === "diros" || diff --git a/frontend/src/components/DataViewer.tsx b/frontend/src/components/DataViewer.tsx index bbe5817..21cee7d 100644 --- a/frontend/src/components/DataViewer.tsx +++ b/frontend/src/components/DataViewer.tsx @@ -514,7 +514,7 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ const dbType = resolveDataSourceType(config); const dbTypeLower = String(dbType || '').trim().toLowerCase(); - const isMySQLFamily = dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros'; + const isMySQLFamily = dbTypeLower === 'mysql' || dbTypeLower === 'goldendb' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros'; const normalizedQuickWhereCondition = normalizeQuickWhereCondition(quickWhereCondition); const quickWhereValidation = validateQuickWhereCondition(normalizedQuickWhereCondition); if (!quickWhereValidation.ok) { diff --git a/frontend/src/components/DatabaseIcons.test.tsx b/frontend/src/components/DatabaseIcons.test.tsx index d8e2706..ed0b116 100644 --- a/frontend/src/components/DatabaseIcons.test.tsx +++ b/frontend/src/components/DatabaseIcons.test.tsx @@ -53,6 +53,13 @@ describe('DatabaseIcons', () => { expect(markup).toContain('>GS'); }); + it('includes GoldenDB in the selectable database icons', () => { + expect(DB_ICON_TYPES).toContain('goldendb'); + expect(getDbIconLabel('goldendb')).toBe('GoldenDB'); + const markup = renderToStaticMarkup(<>{getDbIcon('goldendb', undefined, 22)}); + expect(markup).toContain('>GD'); + }); + 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)}); diff --git a/frontend/src/components/DatabaseIcons.tsx b/frontend/src/components/DatabaseIcons.tsx index ea763ad..bc14bce 100644 --- a/frontend/src/components/DatabaseIcons.tsx +++ b/frontend/src/components/DatabaseIcons.tsx @@ -47,6 +47,7 @@ const DB_DEFAULT_COLORS: Record = { vastbase: '#0066CC', opengauss: '#2446A8', gaussdb: '#0B7FAB', + goldendb: '#D97706', highgo: '#00A86B', iris: '#1F6FEB', tdengine: '#2962FF', @@ -177,6 +178,9 @@ const OpenGaussIcon: React.FC = ({ size = 16, color }) => ( const GaussDBIcon: React.FC = ({ size = 16, color }) => ( ); +const GoldenDBIcon: React.FC = ({ size = 16, color }) => ( + +); const HighGoIcon: React.FC = ({ size = 16, color }) => ( ); @@ -249,6 +253,7 @@ const DB_ICON_MAP: Record> = { vastbase: VastBaseIcon, opengauss: OpenGaussIcon, gaussdb: GaussDBIcon, + goldendb: GoldenDBIcon, highgo: HighGoIcon, iris: IrisIcon, tdengine: TDengineIcon, @@ -264,7 +269,7 @@ const DB_ICON_MAP: Record> = { export const DB_ICON_TYPES: string[] = [ 'mysql', 'mariadb', 'oceanbase', 'postgres', 'redis', 'mongodb', 'jvm', 'oracle', 'sqlserver', 'sqlite', 'duckdb', 'clickhouse', 'starrocks', - 'kingbase', 'dameng', 'vastbase', 'opengauss', 'gaussdb', 'highgo', 'iris', 'tdengine', 'iotdb', 'kafka', 'chroma', 'qdrant', 'elasticsearch', 'custom', + 'kingbase', 'dameng', 'vastbase', 'opengauss', 'gaussdb', 'goldendb', 'highgo', 'iris', 'tdengine', 'iotdb', 'kafka', 'chroma', 'qdrant', 'elasticsearch', 'custom', ]; /** 该类型是否有品牌 SVG 文件 */ @@ -286,7 +291,7 @@ export const getDbIconLabel = (type: string): string => { sqlserver: 'SQL Server', clickhouse: 'ClickHouse', sqlite: 'SQLite', starrocks: 'StarRocks', duckdb: 'DuckDB', kingbase: '金仓', dameng: '达梦', - vastbase: 'VastBase', opengauss: 'OpenGauss', gaussdb: 'GaussDB', highgo: '瀚高', iris: 'InterSystems IRIS', tdengine: 'TDengine', iotdb: 'Apache IoTDB', kafka: 'Kafka', + vastbase: 'VastBase', opengauss: 'OpenGauss', gaussdb: 'GaussDB', goldendb: 'GoldenDB', highgo: '瀚高', iris: 'InterSystems IRIS', tdengine: 'TDengine', iotdb: 'Apache IoTDB', kafka: 'Kafka', chroma: 'Chroma', qdrant: 'Qdrant', elasticsearch: 'Elasticsearch', diff --git a/frontend/src/components/DefinitionViewer.tsx b/frontend/src/components/DefinitionViewer.tsx index 8f7d380..85d97e4 100644 --- a/frontend/src/components/DefinitionViewer.tsx +++ b/frontend/src/components/DefinitionViewer.tsx @@ -96,13 +96,14 @@ const DefinitionViewer: React.FC = ({ tab }) => { if (type === 'custom') { const driver = String(conn?.config?.driver || '').trim().toLowerCase(); if (driver === 'diros' || driver === 'doris') return 'mysql'; + if (driver === 'goldendb' || driver === 'greatdb' || driver === 'gdb') 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'; - if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql'; + if (type === 'goldendb' || type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql'; if (type === 'dameng') return 'dm'; return type; }; diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 6064bc4..9bc6d1c 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -186,7 +186,7 @@ const isSystemMetadataQueryResult = (tableRef: QueryResultTableRef, dbType: stri const metadataDbName = stripQueryIdentifierQuotes(tableRef.metadataDbName).toLowerCase(); const metadataTableName = stripQueryIdentifierQuotes(tableRef.metadataTableName).toLowerCase(); - if (['mysql', 'mariadb', 'oceanbase', 'diros', 'starrocks', 'sphinx', 'tidb'].includes(normalizedDbType)) { + if (['mysql', 'goldendb', 'mariadb', 'oceanbase', 'diros', 'starrocks', 'sphinx', 'tidb'].includes(normalizedDbType)) { return MYSQL_SYSTEM_METADATA_SCHEMAS.has(metadataDbName); } if (['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss', 'gaussdb'].includes(normalizedDbType)) { diff --git a/frontend/src/components/TableOverview.tsx b/frontend/src/components/TableOverview.tsx index 77b9014..1085424 100644 --- a/frontend/src/components/TableOverview.tsx +++ b/frontend/src/components/TableOverview.tsx @@ -120,13 +120,14 @@ const getMetadataDialect = (connType: string, driver?: string, oceanBaseProtocol if (type === 'custom') { const d = (driver || '').trim().toLowerCase(); if (d === 'diros' || d === 'doris') return 'mysql'; + if (d === 'goldendb' || d === 'greatdb' || d === 'gdb') 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'; - if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql'; + if (type === 'goldendb' || type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql'; if (type === 'dameng') return 'dm'; return type; }; diff --git a/frontend/src/components/TriggerViewer.tsx b/frontend/src/components/TriggerViewer.tsx index b605451..e28dcf5 100644 --- a/frontend/src/components/TriggerViewer.tsx +++ b/frontend/src/components/TriggerViewer.tsx @@ -136,13 +136,14 @@ const TriggerViewer: React.FC = ({ tab }) => { if (type === 'custom') { const driver = String(conn?.config?.driver || '').trim().toLowerCase(); if (driver === 'diros' || driver === 'doris') return 'mysql'; + if (driver === 'goldendb' || driver === 'greatdb' || driver === 'gdb') 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'; - if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql'; + if (type === 'goldendb' || type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql'; if (type === 'dameng') return 'dm'; return type; }; diff --git a/frontend/src/components/tableDataDangerActions.test.ts b/frontend/src/components/tableDataDangerActions.test.ts index f119c17..42d0196 100644 --- a/frontend/src/components/tableDataDangerActions.test.ts +++ b/frontend/src/components/tableDataDangerActions.test.ts @@ -5,12 +5,14 @@ import { supportsTableTruncateAction } from './tableDataDangerActions'; describe('tableDataDangerActions', () => { it('supports native truncate for known relational dialects', () => { expect(supportsTableTruncateAction('mysql')).toBe(true); + expect(supportsTableTruncateAction('goldendb')).toBe(true); 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', 'greatdb')).toBe(true); expect(supportsTableTruncateAction('custom', 'gauss_db')).toBe(true); expect(supportsTableTruncateAction('custom', 'kingbase8')).toBe(true); expect(supportsTableTruncateAction('custom', 'intersystemsiris')).toBe(true); diff --git a/frontend/src/components/tableDataDangerActions.ts b/frontend/src/components/tableDataDangerActions.ts index 1dceac6..cd4fa38 100644 --- a/frontend/src/components/tableDataDangerActions.ts +++ b/frontend/src/components/tableDataDangerActions.ts @@ -17,6 +17,10 @@ const resolveCustomDriverDialect = (driver: string): string => { case 'gauss_db': case 'gauss-db': return 'gaussdb'; + case 'goldendb': + case 'greatdb': + case 'gdb': + return 'goldendb'; case 'dm': case 'dameng': case 'dm8': @@ -54,6 +58,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('goldendb') || normalized.includes('greatdb')) return 'goldendb'; if (normalized.includes('postgres')) return 'postgres'; if (normalized.includes('oceanbase')) return 'oceanbase'; if (normalized.includes('kingbase')) return 'kingbase'; @@ -78,6 +83,7 @@ export const resolveTableDataActionDBType = (type: string, driver?: string): str export const supportsTableTruncateAction = (type: string, driver?: string): boolean => { switch (resolveTableDataActionDBType(type, driver)) { case 'mysql': + case 'goldendb': case 'mariadb': case 'oceanbase': case 'starrocks': diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 9aecad0..1c883cc 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -279,6 +279,7 @@ const resolveOceanBaseProtocol = ( }; const SUPPORTED_CONNECTION_TYPES = new Set([ "mysql", + "goldendb", "mariadb", "oceanbase", "doris", @@ -309,6 +310,7 @@ const SUPPORTED_CONNECTION_TYPES = new Set([ ]); const SSL_SUPPORTED_CONNECTION_TYPES = new Set([ "mysql", + "goldendb", "mariadb", "oceanbase", "diros", @@ -338,6 +340,8 @@ const getDefaultPortByType = (type: string): number => { case "mysql": case "mariadb": return 3306; + case "goldendb": + return 1523; case "oceanbase": return 2881; case "doris": @@ -529,6 +533,9 @@ const normalizeConnectionType = (value: unknown): string => { if (type === "gaussdb" || type === "gauss_db" || type === "gauss-db") { return "gaussdb"; } + if (type === "goldendb" || type === "greatdb" || type === "gdb") { + return "goldendb"; + } if (type === "kafka" || type === "apache-kafka" || type === "apache_kafka") { return "kafka"; } diff --git a/frontend/src/utils/connectionDriverType.test.ts b/frontend/src/utils/connectionDriverType.test.ts index 306ad4b..1fda875 100644 --- a/frontend/src/utils/connectionDriverType.test.ts +++ b/frontend/src/utils/connectionDriverType.test.ts @@ -23,6 +23,8 @@ describe('connectionDriverType', () => { expect(normalizeDriverType('doris')).toBe('diros'); expect(normalizeDriverType('open-gauss')).toBe('opengauss'); expect(normalizeDriverType('gauss-db')).toBe('gaussdb'); + expect(normalizeDriverType('greatdb')).toBe('goldendb'); + expect(normalizeDriverType('gdb')).toBe('goldendb'); expect(normalizeDriverType('InterSystemsIRIS')).toBe('iris'); }); @@ -31,6 +33,7 @@ describe('connectionDriverType', () => { expect(resolveConnectionDriverType('custom', 'postgresql')).toBe('postgres'); expect(resolveConnectionDriverType('custom', 'open_gauss')).toBe('opengauss'); expect(resolveConnectionDriverType('custom', 'gauss_db')).toBe('gaussdb'); + expect(resolveConnectionDriverType('custom', 'goldendb')).toBe('goldendb'); expect(resolveConnectionDriverType('custom', '')).toBe(''); }); diff --git a/frontend/src/utils/connectionDriverType.ts b/frontend/src/utils/connectionDriverType.ts index ab4a1b6..b53ef8f 100644 --- a/frontend/src/utils/connectionDriverType.ts +++ b/frontend/src/utils/connectionDriverType.ts @@ -30,6 +30,11 @@ export const normalizeDriverType = (value: string): string => { normalized === 'gauss_db' || normalized === 'gauss-db' ) return 'gaussdb'; + if ( + normalized === 'goldendb' || + normalized === 'greatdb' || + normalized === 'gdb' + ) return 'goldendb'; if ( normalized === 'intersystems' || normalized === 'intersystemsiris' || diff --git a/frontend/src/utils/connectionModalPresentation.test.ts b/frontend/src/utils/connectionModalPresentation.test.ts index 25e7794..97fd8fd 100644 --- a/frontend/src/utils/connectionModalPresentation.test.ts +++ b/frontend/src/utils/connectionModalPresentation.test.ts @@ -67,6 +67,7 @@ describe('connectionModalPresentation', () => { it('assigns card-based configuration sections to every supported data source type', () => { const allTypes = [ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'doris', @@ -117,6 +118,15 @@ describe('connectionModalPresentation', () => { 'credentials', 'databaseScope', ]); + expect(resolveConnectionConfigLayout('goldendb').sections).toEqual([ + 'identity', + 'uri', + 'target', + 'connectionMode', + 'replica', + 'credentials', + 'databaseScope', + ]); expect(resolveConnectionConfigLayout('mongodb').sections).toEqual([ 'identity', 'uri', diff --git a/frontend/src/utils/connectionModalPresentation.ts b/frontend/src/utils/connectionModalPresentation.ts index d7d6722..36e1d01 100644 --- a/frontend/src/utils/connectionModalPresentation.ts +++ b/frontend/src/utils/connectionModalPresentation.ts @@ -58,6 +58,7 @@ type ConnectionConfigSectionCopy = { const mysqlCompatibleTypes = new Set([ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'doris', diff --git a/frontend/src/utils/connectionTypeCapabilities.test.ts b/frontend/src/utils/connectionTypeCapabilities.test.ts index 6d890db..e60f39b 100644 --- a/frontend/src/utils/connectionTypeCapabilities.test.ts +++ b/frontend/src/utils/connectionTypeCapabilities.test.ts @@ -29,6 +29,7 @@ describe('connectionTypeCapabilities', () => { expect(supportsSSLForType('MongoDB')).toBe(true); expect(supportsSSLForType('elasticsearch')).toBe(true); expect(supportsSSLForType('gaussdb')).toBe(true); + expect(supportsSSLForType('greatdb')).toBe(true); expect(supportsSSLForType('chroma')).toBe(true); expect(supportsSSLForType('qdrant')).toBe(true); expect(supportsSSLForType('kafka')).toBe(true); @@ -68,6 +69,7 @@ describe('connectionTypeCapabilities', () => { expect(isFileDatabaseType('duckdb')).toBe(true); expect(isFileDatabaseType('DuckDB')).toBe(false); expect(isMySQLCompatibleType('mysql')).toBe(true); + expect(isMySQLCompatibleType('goldendb')).toBe(true); expect(isMySQLCompatibleType('oceanbase')).toBe(true); expect(isMySQLCompatibleType('diros')).toBe(true); expect(isMySQLCompatibleType('postgres')).toBe(false); @@ -75,6 +77,7 @@ describe('connectionTypeCapabilities', () => { it('keeps advanced connection params enabled only for supported database types', () => { expect(supportsConnectionParamsForType('mysql')).toBe(true); + expect(supportsConnectionParamsForType('gdb')).toBe(true); expect(supportsConnectionParamsForType('postgres')).toBe(true); expect(supportsConnectionParamsForType('gaussdb')).toBe(true); expect(supportsConnectionParamsForType('oracle')).toBe(true); diff --git a/frontend/src/utils/connectionTypeCapabilities.ts b/frontend/src/utils/connectionTypeCapabilities.ts index 4d76d0f..5f43194 100644 --- a/frontend/src/utils/connectionTypeCapabilities.ts +++ b/frontend/src/utils/connectionTypeCapabilities.ts @@ -19,12 +19,23 @@ export const singleHostUriSchemesByType: Record = { }; const normalizeConnectionType = (type: string) => - String(type || "") - .trim() - .toLowerCase(); + { + const normalized = String(type || "") + .trim() + .toLowerCase(); + switch (normalized) { + case "goldendb": + case "greatdb": + case "gdb": + return "goldendb"; + default: + return normalized; + } + }; const sslSupportedTypes = new Set([ "mysql", + "goldendb", "mariadb", "oceanbase", "doris", @@ -55,6 +66,7 @@ export const supportsSSLForType = (type: string) => const sslCAPathSupportedTypes = new Set([ "mysql", + "goldendb", "mariadb", "oceanbase", "diros", @@ -78,6 +90,7 @@ const sslCAPathSupportedTypes = new Set([ const sslClientCertificateSupportedTypes = new Set([ "mysql", + "goldendb", "mariadb", "oceanbase", "diros", @@ -116,13 +129,14 @@ export const isFileDatabaseType = (type: string) => type === "sqlite" || type === "duckdb"; export const isMySQLCompatibleType = (type: string) => - type === "mysql" || - type === "mariadb" || - type === "oceanbase" || - type === "doris" || - type === "diros" || - type === "starrocks" || - type === "sphinx"; + normalizeConnectionType(type) === "mysql" || + normalizeConnectionType(type) === "goldendb" || + normalizeConnectionType(type) === "mariadb" || + normalizeConnectionType(type) === "oceanbase" || + normalizeConnectionType(type) === "doris" || + normalizeConnectionType(type) === "diros" || + normalizeConnectionType(type) === "starrocks" || + normalizeConnectionType(type) === "sphinx"; export const supportsConnectionParamsForType = (type: string) => isMySQLCompatibleType(type) || diff --git a/frontend/src/utils/connectionTypeCatalog.test.ts b/frontend/src/utils/connectionTypeCatalog.test.ts index 45ff171..3a85f9c 100644 --- a/frontend/src/utils/connectionTypeCatalog.test.ts +++ b/frontend/src/utils/connectionTypeCatalog.test.ts @@ -23,6 +23,7 @@ describe('connectionTypeCatalog', () => { expect(keys).toContain('mysql'); expect(keys).toContain('oceanbase'); expect(keys).toContain('gaussdb'); + expect(keys).toContain('goldendb'); expect(keys).toContain('mongodb'); expect(keys).toContain('redis'); expect(keys).toContain('elasticsearch'); @@ -38,6 +39,7 @@ describe('connectionTypeCatalog', () => { it('returns the existing default port mapping for supported connection types', () => { expect(getConnectionTypeDefaultPort('mysql')).toBe(3306); expect(getConnectionTypeDefaultPort('oceanbase')).toBe(2881); + expect(getConnectionTypeDefaultPort('goldendb')).toBe(1523); expect(getConnectionTypeDefaultPort('diros')).toBe(9030); expect(getConnectionTypeDefaultPort('postgres')).toBe(5432); expect(getConnectionTypeDefaultPort('gaussdb')).toBe(5432); @@ -63,6 +65,7 @@ describe('connectionTypeCatalog', () => { expect(getConnectionTypeHint('iotdb')).toContain('Timeseries'); expect(getConnectionTypeHint('kafka')).toContain('Consumer Group'); expect(getConnectionTypeHint('oceanbase')).toBe('MySQL / Oracle 租户'); + expect(getConnectionTypeHint('goldendb')).toBe('MySQL 兼容 / 分布式事务'); expect(getConnectionTypeHint('duckdb')).toBe('本地文件连接'); expect(getConnectionTypeHint('mysql')).toBe('标准连接配置'); }); diff --git a/frontend/src/utils/connectionTypeCatalog.ts b/frontend/src/utils/connectionTypeCatalog.ts index 6d50225..0871b03 100644 --- a/frontend/src/utils/connectionTypeCatalog.ts +++ b/frontend/src/utils/connectionTypeCatalog.ts @@ -36,6 +36,7 @@ export const CONNECTION_TYPE_GROUPS: ConnectionTypeCatalogGroup[] = [ { key: 'vastbase', name: 'Vastbase (海量)' }, { key: 'opengauss', name: 'OpenGauss' }, { key: 'gaussdb', name: 'GaussDB' }, + { key: 'goldendb', name: 'GoldenDB' }, ], }, { @@ -83,6 +84,8 @@ export const getConnectionTypeDefaultPort = (type: string): number => { return 3306; case 'oceanbase': return 2881; + case 'goldendb': + return 1523; case 'doris': case 'diros': case 'starrocks': @@ -157,6 +160,8 @@ export const getConnectionTypeHint = (type: string): string => { return 'Broker / Topic / Consumer Group'; case 'oceanbase': return 'MySQL / Oracle 租户'; + case 'goldendb': + return 'MySQL 兼容 / 分布式事务'; case 'sqlite': case 'duckdb': return '本地文件连接'; diff --git a/frontend/src/utils/dataSourceCapabilities.test.ts b/frontend/src/utils/dataSourceCapabilities.test.ts index 4f3ed4f..ffc59db 100644 --- a/frontend/src/utils/dataSourceCapabilities.test.ts +++ b/frontend/src/utils/dataSourceCapabilities.test.ts @@ -30,6 +30,24 @@ describe('dataSourceCapabilities', () => { }); }); + it('treats GoldenDB as an editable MySQL-family datasource with database-level DDL actions', () => { + expect(getDataSourceCapabilities({ type: 'goldendb' })).toMatchObject({ + type: 'goldendb', + supportsQueryEditor: true, + supportsSqlQueryExport: true, + supportsCopyInsert: true, + supportsCreateDatabase: true, + supportsRenameDatabase: false, + supportsDropDatabase: true, + forceReadOnlyQueryResult: false, + }); + expect(getDataSourceCapabilities({ type: 'custom', driver: 'greatdb' })).toMatchObject({ + type: 'goldendb', + supportsQueryEditor: true, + supportsCopyInsert: true, + }); + }); + it('keeps StarRocks as an independent SQL datasource capability', () => { expect(getDataSourceCapabilities({ type: 'starrocks' })).toMatchObject({ type: 'starrocks', diff --git a/frontend/src/utils/dataSourceCapabilities.ts b/frontend/src/utils/dataSourceCapabilities.ts index 4b4d857..9bc3a65 100644 --- a/frontend/src/utils/dataSourceCapabilities.ts +++ b/frontend/src/utils/dataSourceCapabilities.ts @@ -20,6 +20,10 @@ const normalizeDataSourceToken = (raw: string): string => { case 'gauss_db': case 'gauss-db': return 'gaussdb'; + case 'goldendb': + case 'greatdb': + case 'gdb': + return 'goldendb'; case 'dm': return 'dameng'; case 'elastic': @@ -66,6 +70,7 @@ export const resolveDataSourceType = (config: ConnectionLike): string => { const SQL_QUERY_EXPORT_TYPES = new Set([ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'diros', @@ -89,6 +94,7 @@ const SQL_QUERY_EXPORT_TYPES = new Set([ const COPY_INSERT_TYPES = new Set([ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'diros', @@ -132,6 +138,7 @@ export type DataSourceCapabilities = { const CREATE_DATABASE_TYPES = new Set([ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'diros', @@ -159,6 +166,7 @@ const RENAME_DATABASE_TYPES = new Set([ const DROP_DATABASE_TYPES = new Set([ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'diros', diff --git a/frontend/src/utils/ddlFormat.ts b/frontend/src/utils/ddlFormat.ts index e50c9a1..0122818 100644 --- a/frontend/src/utils/ddlFormat.ts +++ b/frontend/src/utils/ddlFormat.ts @@ -18,6 +18,7 @@ const resolveDdlFormatterLanguage = (dbType: string): SqlLanguage => { case 'mariadb': return 'mariadb'; case 'mysql': + case 'goldendb': case 'sphinx': return 'mysql'; case 'sqlserver': diff --git a/frontend/src/utils/queryAutoLimit.test.ts b/frontend/src/utils/queryAutoLimit.test.ts index 8dc2ca8..dbaee51 100644 --- a/frontend/src/utils/queryAutoLimit.test.ts +++ b/frontend/src/utils/queryAutoLimit.test.ts @@ -5,6 +5,7 @@ import { applyQueryAutoLimit } from './queryAutoLimit'; describe('applyQueryAutoLimit', () => { const limitDialects = [ 'mysql', + 'goldendb', 'mariadb', 'oceanbase', 'diros', diff --git a/frontend/src/utils/sidebarMetadata.test.ts b/frontend/src/utils/sidebarMetadata.test.ts index dc8ed00..b09107a 100644 --- a/frontend/src/utils/sidebarMetadata.test.ts +++ b/frontend/src/utils/sidebarMetadata.test.ts @@ -25,6 +25,7 @@ describe('sidebarMetadata', () => { }); it('uses MySQL metadata queries for custom MySQL-compatible domestic drivers', () => { + expect(resolveSidebarMetadataDialect('goldendb')).toBe('mysql'); expect(resolveSidebarMetadataDialect('custom', 'gdb')).toBe('mysql'); expect(resolveSidebarMetadataDialect('custom', 'goldendb')).toBe('mysql'); expect(resolveSidebarMetadataDialect('custom', 'greatdb')).toBe('mysql'); diff --git a/frontend/src/utils/sql.test.ts b/frontend/src/utils/sql.test.ts index 88d6bc4..08cc1b2 100644 --- a/frontend/src/utils/sql.test.ts +++ b/frontend/src/utils/sql.test.ts @@ -64,6 +64,11 @@ describe('quoteQualifiedIdent', () => { .toBe('"logs.app-1"'); }); + it('quotes GoldenDB identifiers with MySQL-style backticks', () => { + expect(quoteQualifiedIdent('goldendb', 'ledger.entries')) + .toBe('`ledger`.`entries`'); + }); + it('does not split dots inside quoted DuckDB identifiers', () => { expect(quoteQualifiedIdent('duckdb', '"daily.events"."2026.06"')) .toBe('"daily.events"."2026.06"'); diff --git a/frontend/src/utils/sql.ts b/frontend/src/utils/sql.ts index 093d8c6..1281ea3 100644 --- a/frontend/src/utils/sql.ts +++ b/frontend/src/utils/sql.ts @@ -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 === 'iotdb' || dbTypeLower === 'clickhouse') { + if (dbTypeLower === 'mysql' || dbTypeLower === 'goldendb' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'starrocks' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'iotdb' || dbTypeLower === 'clickhouse') { return `\`${raw.replace(/`/g, '``')}\``; } @@ -149,7 +149,7 @@ export const buildOrderBySQL = ( // 部分数据源在无显式排序需求时强制 ORDER BY(即使按主键)会显著放大大表预览成本: // MySQL/MariaDB 可能触发 filesort 和 sort memory 错误,DuckDB 大文件可能被排序拖到连接超时。 // 因此仅在用户主动点击排序时下发 ORDER BY,默认分页查询不加兜底排序。 - if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'starrocks' || dbTypeLower === 'duckdb') { + if (dbTypeLower === 'mysql' || dbTypeLower === 'goldendb' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'starrocks' || dbTypeLower === 'duckdb') { return ''; } diff --git a/internal/app/db_context.go b/internal/app/db_context.go index f230952..7335062 100644 --- a/internal/app/db_context.go +++ b/internal/app/db_context.go @@ -22,7 +22,7 @@ func normalizeRunConfig(config connection.ConnectionConfig, dbName string) conne if !isOceanBaseOracleProtocol(config) { runConfig.Database = name } - case "mysql", "mariadb", "diros", "starrocks", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "gaussdb", "sqlserver", "iris", "intersystems", "intersystemsiris", "inter-systems", "inter-systems-iris", "mongodb", "tdengine", "iotdb", "clickhouse": + case "mysql", "mariadb", "goldendb", "greatdb", "gdb", "diros", "starrocks", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "gaussdb", "sqlserver", "iris", "intersystems", "intersystemsiris", "inter-systems", "inter-systems-iris", "mongodb", "tdengine", "iotdb", "clickhouse": // 这些类型的 dbName 表示"数据库",需要写入连接配置以选择目标库。 runConfig.Database = name case "dameng": diff --git a/internal/app/db_context_test.go b/internal/app/db_context_test.go index 64916fd..8b786bd 100644 --- a/internal/app/db_context_test.go +++ b/internal/app/db_context_test.go @@ -216,6 +216,19 @@ func TestNormalizeRunConfig_StarRocksUsesDatabaseFromTree(t *testing.T) { } } +func TestNormalizeRunConfig_GoldenDBUsesDatabaseFromTree(t *testing.T) { + t.Parallel() + + runConfig := normalizeRunConfig(connection.ConnectionConfig{ + Type: "goldendb", + Database: "legacy_default", + }, "finance_core") + + if runConfig.Database != "finance_core" { + t.Fatalf("expected GoldenDB database from tree, got %q", runConfig.Database) + } +} + func TestNormalizeRunConfig_IRISUsesNamespaceFromTree(t *testing.T) { t.Parallel() diff --git a/internal/app/db_proxy.go b/internal/app/db_proxy.go index 6a3e772..fd07e80 100644 --- a/internal/app/db_proxy.go +++ b/internal/app/db_proxy.go @@ -203,6 +203,8 @@ func defaultPortByType(driverType string) int { switch strings.ToLower(strings.TrimSpace(driverType)) { case "mysql", "mariadb": return 3306 + case "goldendb", "greatdb", "gdb": + return 1523 case "oceanbase": return 2881 case "diros": diff --git a/internal/app/methods_db.go b/internal/app/methods_db.go index 293389c..55764e5 100644 --- a/internal/app/methods_db.go +++ b/internal/app/methods_db.go @@ -224,6 +224,9 @@ func resolveDDLDBType(config connection.ConnectionConfig) string { if dbType == "gauss_db" || dbType == "gauss-db" { return "gaussdb" } + if dbType == "goldendb" || dbType == "greatdb" || dbType == "gdb" { + return "mysql" + } if dbType == "kingbase8" || dbType == "kingbasees" || dbType == "kingbasev8" { return "kingbase" } @@ -245,6 +248,8 @@ func resolveDDLDBType(config connection.ConnectionConfig) string { return "opengauss" case "gaussdb", "gauss_db", "gauss-db": return "gaussdb" + case "goldendb", "greatdb", "gdb": + return "mysql" case "dm", "dameng", "dm8": return "dameng" case "sqlite3", "sqlite": @@ -274,6 +279,8 @@ func resolveDDLDBType(config connection.ConnectionConfig) string { return "opengauss" case strings.Contains(driver, "gaussdb"), strings.Contains(driver, "gauss_db"), strings.Contains(driver, "gauss-db"): return "gaussdb" + case strings.Contains(driver, "goldendb"), strings.Contains(driver, "greatdb"): + return "mysql" case strings.Contains(driver, "postgres"): return "postgres" case strings.Contains(driver, "kingbase"): diff --git a/internal/app/methods_db_create_statement_test.go b/internal/app/methods_db_create_statement_test.go index 83476b5..6488db3 100644 --- a/internal/app/methods_db_create_statement_test.go +++ b/internal/app/methods_db_create_statement_test.go @@ -115,6 +115,20 @@ func TestResolveDDLDBType_IRISTypeAlias(t *testing.T) { } } +func TestResolveDDLDBType_GoldenDBUsesMySQLDialect(t *testing.T) { + t.Parallel() + + if got := resolveDDLDBType(connection.ConnectionConfig{Type: "goldendb"}); got != "mysql" { + t.Fatalf("expected goldendb type to resolve to mysql, got %q", got) + } + if got := resolveDDLDBType(connection.ConnectionConfig{Type: "custom", Driver: "greatdb"}); got != "mysql" { + t.Fatalf("expected greatdb custom driver to resolve to mysql, got %q", got) + } + if got := resolveDDLDBType(connection.ConnectionConfig{Type: "custom", Driver: "gdb"}); got != "mysql" { + t.Fatalf("expected gdb custom driver to resolve to mysql, got %q", got) + } +} + func TestNormalizeSchemaAndTableByType_PGLikeQuotedQualifiedName(t *testing.T) { t.Parallel() diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index a1f3a27..73b505f 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -338,6 +338,7 @@ const builtinDriverManifestJSON = `{ "engine": "go", "drivers": { "mysql": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off" }, + "goldendb": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off" }, "mariadb": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/mariadb" }, "oceanbase": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/oceanbase" }, "doris": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/doris" }, @@ -402,6 +403,7 @@ var pinnedDriverPackageMap = map[string]pinnedDriverPackage{ var latestDriverVersionMap = map[string]string{ "mysql": "1.9.3", + "goldendb": "1.9.3", "mariadb": "1.9.3", "oceanbase": "1.9.3", "diros": "1.9.3", @@ -428,6 +430,7 @@ var latestDriverVersionMap = map[string]string{ } var driverGoModulePathMap = map[string]string{ + "goldendb": "github.com/go-sql-driver/mysql", "mariadb": "github.com/go-sql-driver/mysql", "oceanbase": "github.com/go-sql-driver/mysql", "diros": "github.com/go-sql-driver/mysql", @@ -1427,6 +1430,8 @@ func normalizeDriverType(driverType string) string { return "opengauss" case "gaussdb", "gauss_db", "gauss-db": return "gaussdb" + case "goldendb", "greatdb", "gdb": + return "goldendb" case "kafka", "apache-kafka", "apache_kafka": return "kafka" case "intersystems", "intersystemsiris", "inter-systems-iris", "inter-systems": @@ -1494,6 +1499,7 @@ func resolveDriverDefinitionWithPackages(driverType string, packages map[string] func allDriverDefinitionsWithPackages(packages map[string]pinnedDriverPackage) []driverDefinition { return []driverDefinition{ {Type: "mysql", Name: "MySQL", Engine: driverEngineGo, BuiltIn: true}, + {Type: "goldendb", Name: "GoldenDB", Engine: driverEngineGo, BuiltIn: true}, {Type: "oracle", Name: "Oracle", Engine: driverEngineGo, BuiltIn: true}, {Type: "redis", Name: "Redis", Engine: driverEngineGo, BuiltIn: true}, {Type: "postgres", Name: "PostgreSQL", Engine: driverEngineGo, BuiltIn: true}, diff --git a/internal/app/methods_driver_version_test.go b/internal/app/methods_driver_version_test.go index 1220b11..f13fd38 100644 --- a/internal/app/methods_driver_version_test.go +++ b/internal/app/methods_driver_version_test.go @@ -518,6 +518,28 @@ func TestKafkaDriverDefinitionIsBuiltIn(t *testing.T) { } } +func TestGoldenDBDriverDefinitionIsBuiltIn(t *testing.T) { + definition, ok := resolveDriverDefinition("greatdb") + if !ok { + t.Fatal("expected goldendb driver definition") + } + if definition.Name != "GoldenDB" { + t.Fatalf("unexpected goldendb driver name: %q", definition.Name) + } + if !definition.BuiltIn { + t.Fatal("expected goldendb to be a built-in driver") + } + if definition.PinnedVersion != "" || definition.DefaultDownloadURL != "" { + t.Fatalf("expected goldendb builtin definition to omit optional metadata: %#v", definition) + } + if latestDriverVersionMap["goldendb"] != "1.9.3" { + t.Fatalf("unexpected goldendb pinned version: %q", latestDriverVersionMap["goldendb"]) + } + if driverGoModulePathMap["goldendb"] != "github.com/go-sql-driver/mysql" { + t.Fatalf("unexpected goldendb go module path: %q", driverGoModulePathMap["goldendb"]) + } +} + func TestGaussDBDriverDefinitionUsesOptionalAgent(t *testing.T) { definition, ok := resolveDriverDefinition("gaussdb") if !ok { diff --git a/internal/db/database.go b/internal/db/database.go index 7befe8d..c71c68e 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -471,6 +471,9 @@ var databaseFactories = map[string]databaseFactory{ "mysql": func() Database { return &MySQLDB{} }, + "goldendb": func() Database { + return &MySQLDB{} + }, "postgres": func() Database { return &PostgresDB{} }, @@ -521,6 +524,8 @@ func normalizeDatabaseType(dbType string) string { return "opengauss" case "gaussdb", "gauss_db", "gauss-db": return "gaussdb" + case "goldendb", "greatdb", "gdb": + return "goldendb" case "intersystems", "intersystemsiris", "inter-systems-iris", "inter-systems": return "iris" case "chromadb", "chroma-db": diff --git a/internal/db/driver_support.go b/internal/db/driver_support.go index 4980b45..f48283e 100644 --- a/internal/db/driver_support.go +++ b/internal/db/driver_support.go @@ -13,6 +13,7 @@ import ( // coreBuiltinDrivers 是始终内置可用的核心驱动,无需额外安装即可使用。 var coreBuiltinDrivers = map[string]struct{}{ "mysql": {}, + "goldendb": {}, "redis": {}, "oracle": {}, "postgres": {}, @@ -69,6 +70,8 @@ func normalizeRuntimeDriverType(driverType string) string { return "opengauss" case "gaussdb", "gauss_db", "gauss-db": return "gaussdb" + case "goldendb", "greatdb", "gdb": + return "goldendb" case "intersystems", "intersystemsiris", "inter-systems-iris", "inter-systems": return "iris" case "elastic": @@ -90,6 +93,8 @@ func driverDisplayName(driverType string) string { switch normalizeRuntimeDriverType(driverType) { case "mysql": return "MySQL" + case "goldendb": + return "GoldenDB" case "oracle": return "Oracle" case "redis": diff --git a/internal/db/driver_support_test.go b/internal/db/driver_support_test.go index 673dab5..ff32e4b 100644 --- a/internal/db/driver_support_test.go +++ b/internal/db/driver_support_test.go @@ -34,6 +34,11 @@ func TestBuiltinLikeDriversRemainAvailable(t *testing.T) { if !supported { t.Fatalf("kafka 应始终可用,reason=%s", reason) } + + supported, reason = DriverRuntimeSupportStatus("goldendb") + if !supported { + t.Fatalf("goldendb 应始终可用,reason=%s", reason) + } } func TestOptionalDriverAgentRevisionsGeneratedForOptionalDrivers(t *testing.T) { @@ -58,6 +63,12 @@ func TestKingbaseRuntimeAliasesNormalizeToKingbase(t *testing.T) { if got := normalizeDatabaseType("kingbasees"); got != "kingbase" { t.Fatalf("expected kingbasees database alias to normalize to kingbase, got %q", got) } + if got := normalizeRuntimeDriverType("greatdb"); got != "goldendb" { + t.Fatalf("expected greatdb runtime alias to normalize to goldendb, got %q", got) + } + if got := normalizeDatabaseType("gdb"); got != "goldendb" { + t.Fatalf("expected gdb database alias to normalize to goldendb, got %q", got) + } } func TestManagedDriverRequiresInstallMarker(t *testing.T) { @@ -145,3 +156,13 @@ func TestMySQLBuiltinRuntimeSupportAvailable(t *testing.T) { t.Fatalf("mysql 属于免安装内置驱动,应可用,reason=%s", reason) } } + +func TestGoldenDBBuiltinDatabaseFactoryUsesMySQLImplementation(t *testing.T) { + dbInst, err := NewDatabase("goldendb") + if err != nil { + t.Fatalf("expected goldendb database factory, got err=%v", err) + } + if _, ok := dbInst.(*MySQLDB); !ok { + t.Fatalf("expected goldendb to reuse MySQLDB implementation, got %T", dbInst) + } +} diff --git a/internal/db/mysql_connection_params_test.go b/internal/db/mysql_connection_params_test.go index 9d852c3..bf7135a 100644 --- a/internal/db/mysql_connection_params_test.go +++ b/internal/db/mysql_connection_params_test.go @@ -472,3 +472,54 @@ func TestMySQLDSN_URIParamsAndExplicitParamsPrecedence(t *testing.T) { t.Fatalf("internal topology parameter should not be passed to driver: %v", query) } } + +func TestApplyMySQLURI_GoldenDBSchemeUsesDefaultPort(t *testing.T) { + t.Parallel() + + config := applyMySQLURI(connection.ConnectionConfig{ + Type: "goldendb", + URI: "goldendb://glzc:secret@gdb.local/core_ledger?characterEncoding=utf8", + }) + + if config.Host != "gdb.local" { + t.Fatalf("expected goldendb host from URI, got %q", config.Host) + } + if config.Port != 1523 { + t.Fatalf("expected goldendb default port 1523, got %d", config.Port) + } + if config.Database != "core_ledger" { + t.Fatalf("expected goldendb database from URI, got %q", config.Database) + } + if config.User != "glzc" { + t.Fatalf("expected goldendb user from URI, got %q", config.User) + } + if config.Password != "secret" { + t.Fatalf("expected goldendb password from URI, got %q", config.Password) + } +} + +func TestMySQLDSN_AcceptsGoldenDBURICompatibilityParams(t *testing.T) { + t.Parallel() + + m := &MySQLDB{} + dsn, err := m.getDSN(connection.ConnectionConfig{ + Type: "goldendb", + Host: "gdb.local", + Port: 1523, + User: "glzc", + Password: "secret", + Database: "core_ledger", + URI: "goldendb://glzc:secret@gdb.local/core_ledger?characterEncoding=utf8&useSSL=false", + }) + if err != nil { + t.Fatalf("getDSN failed: %v", err) + } + + query := parseMySQLDSNQueryForTest(t, dsn) + if got := query.Get("charset"); got != "utf8" { + t.Fatalf("goldendb URI characterEncoding should map to charset=utf8, got=%q", got) + } + if got := query.Get("tls"); got != "false" { + t.Fatalf("goldendb URI useSSL=false should map to tls=false, got=%q", got) + } +} diff --git a/internal/db/mysql_impl.go b/internal/db/mysql_impl.go index 5b715e7..2efcf17 100644 --- a/internal/db/mysql_impl.go +++ b/internal/db/mysql_impl.go @@ -27,14 +27,39 @@ type MySQLDB struct { const ( defaultMySQLPort = 3306 + defaultGoldenDBPort = 1523 defaultMySQLInsertBatchSize = 1000 maxMySQLInsertBatchArgs = 60000 ) +var mysqlCompatibleURISchemes = []string{ + "mysql", + "mariadb", + "doris", + "diros", + "oceanbase", + "starrocks", + "goldendb", + "greatdb", + "gdb", +} + func parseMySQLCompatibleURI(raw string, allowedSchemes ...string) (*url.URL, bool) { return parseConnectionURI(raw, allowedSchemes...) } +func resolveMySQLCompatibleDefaultPort(config connection.ConnectionConfig) int { + if config.Port > 0 { + return config.Port + } + switch strings.ToLower(strings.TrimSpace(config.Type)) { + case "goldendb", "greatdb", "gdb": + return defaultGoldenDBPort + default: + return defaultMySQLPort + } +} + func mysqlConnectionParamsFromText(raw string) url.Values { return connectionParamsFromText(raw) } @@ -319,7 +344,7 @@ func hasMySQLConnectionParam(config connection.ConnectionConfig, names ...string return false } - if parsed, ok := parseMySQLCompatibleURI(config.URI, "mysql", "mariadb", "doris", "diros", "oceanbase", "starrocks"); ok && hasMatchingKey(parsed.Query()) { + if parsed, ok := parseMySQLCompatibleURI(config.URI, mysqlCompatibleURISchemes...); ok && hasMatchingKey(parsed.Query()) { return true } return hasMatchingKey(mysqlConnectionParamsFromText(config.ConnectionParams)) @@ -368,7 +393,7 @@ func buildMySQLCompatibleDSNWithOptions(config connection.ConnectionConfig, prot defaultMultiStatements = *options.defaultMultiStatements } params.Set("multiStatements", strconv.FormatBool(defaultMultiStatements)) - if parsed, ok := parseMySQLCompatibleURI(config.URI, "mysql", "doris", "diros", "oceanbase"); ok { + if parsed, ok := parseMySQLCompatibleURI(config.URI, mysqlCompatibleURISchemes...); ok { mergeMySQLConnectionParams(params, parsed.Query()) } mergeMySQLConnectionParams(params, mysqlConnectionParamsFromText(config.ConnectionParams)) @@ -625,7 +650,7 @@ func applyMySQLURI(config connection.ConnectionConfig) connection.ConnectionConf if uriText == "" { return config } - parsed, ok := parseMySQLCompatibleURI(uriText, "mysql") + parsed, ok := parseMySQLCompatibleURI(uriText, mysqlCompatibleURISchemes...) if !ok { return config } @@ -643,10 +668,7 @@ func applyMySQLURI(config connection.ConnectionConfig) connection.ConnectionConf config.Database = dbName } - defaultPort := config.Port - if defaultPort <= 0 { - defaultPort = defaultMySQLPort - } + defaultPort := resolveMySQLCompatibleDefaultPort(config) hostsFromURI := make([]string, 0, 4) hostText := strings.TrimSpace(parsed.Host) @@ -682,10 +704,7 @@ func applyMySQLURI(config connection.ConnectionConfig) connection.ConnectionConf } func collectMySQLAddresses(config connection.ConnectionConfig) []string { - defaultPort := config.Port - if defaultPort <= 0 { - defaultPort = defaultMySQLPort - } + defaultPort := resolveMySQLCompatibleDefaultPort(config) candidates := make([]string, 0, len(config.Hosts)+1) if len(config.Hosts) > 0 { @@ -757,11 +776,12 @@ func (m *MySQLDB) Connect(config connection.ConnectionConfig) error { if len(addresses) == 0 { return fmt.Errorf("连接建立后验证失败:未找到可用的 MySQL 地址") } + defaultPort := resolveMySQLCompatibleDefaultPort(runConfig) var errorDetails []string for index, address := range addresses { candidateConfig := runConfig - host, port, ok := parseHostPortWithDefault(address, defaultMySQLPort) + host, port, ok := parseHostPortWithDefault(address, defaultPort) if !ok { continue }