From ebe4c18316db7f0cf6fb4d1cb60a5e8aa5c3ee9e Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 13 Aug 2026 20:20:45 +0800 Subject: [PATCH 1/3] feat: sync KingBase datasource type and default schema to ODC Map KingBase to KINGBASE, require database_name, and pass it as DefaultSchema when opening the SQL workbench. Co-authored-by: Cursor --- internal/dms/pkg/constant/const.go | 3 + internal/dms/pkg/constant/const_test.go | 1 + .../service/sql_workbench_service.go | 15 +++- .../service/sql_workbench_service_test.go | 85 +++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/internal/dms/pkg/constant/const.go b/internal/dms/pkg/constant/const.go index 7377ed0e..c7c5c5c7 100644 --- a/internal/dms/pkg/constant/const.go +++ b/internal/dms/pkg/constant/const.go @@ -262,6 +262,8 @@ func ParseDBType(s string) (DBType, error) { return DBTypeRedis, nil case "OceanBase For Oracle": return DBTypeOceanBaseOracle, nil + case "KingBase": + return DBTypeKingBase, nil default: return "", fmt.Errorf("invalid db type: %s", s) @@ -288,6 +290,7 @@ const ( DBTypeMongoDB DBType = "MongoDB" DBTypeRedis DBType = "Redis" DBTypeOceanBaseOracle DBType = "OceanBase For Oracle" + DBTypeKingBase DBType = "KingBase" ) var supportedDataExportDBTypes = map[DBType]struct{}{ diff --git a/internal/dms/pkg/constant/const_test.go b/internal/dms/pkg/constant/const_test.go index 1958f259..bdd5dc14 100644 --- a/internal/dms/pkg/constant/const_test.go +++ b/internal/dms/pkg/constant/const_test.go @@ -129,6 +129,7 @@ func TestParseDBType(t *testing.T) { "PolarDB For MySQL": {input: "PolarDB For MySQL", expected: DBTypePolarDBForMySQL}, "MongoDB": {input: "MongoDB", expected: DBTypeMongoDB}, "Redis": {input: "Redis", expected: DBTypeRedis}, + "KingBase": {input: "KingBase", expected: DBTypeKingBase}, // "PolarDB" 单独不应匹配 "PolarDB only": {input: "PolarDB", expectError: true}, "invalid type": {input: "UnknownDB", expectError: true}, diff --git a/internal/sql_workbench/service/sql_workbench_service.go b/internal/sql_workbench/service/sql_workbench_service.go index ee177152..a6c20793 100644 --- a/internal/sql_workbench/service/sql_workbench_service.go +++ b/internal/sql_workbench/service/sql_workbench_service.go @@ -1096,6 +1096,16 @@ func (sqlWorkbenchService *SqlWorkbenchService) fillDatasourceBaseInfo(datasourc baseInfo.DefaultSchema = &databaseName } + // KingBase:database_name → ODC defaultSchema;缺失则失败,禁止静默空默认库 + if dbService.DBType == string(pkgConst.DBTypeKingBase) { + databaseNameParam := dbService.AdditionalParams.GetParam("database_name") + if databaseNameParam == nil || databaseNameParam.Value == "" { + return nil, fmt.Errorf("KingBase 数据源 %s 缺少 AdditionalParam database_name,请在数据源 AdditionalParams 中补充", dbService.Name) + } + databaseName := databaseNameParam.Value + baseInfo.DefaultSchema = &databaseName + } + return baseInfo, nil } @@ -1187,6 +1197,8 @@ func (sqlWorkbenchService *SqlWorkbenchService) convertDBType(dmsDBType string) return "REDIS" case "DB2": return "DB2" + case "KingBase": + return "KINGBASE" default: return dmsDBType } @@ -1203,7 +1215,8 @@ func (sqlWorkbenchService *SqlWorkbenchService) SupportDBType(dbType pkgConst.DB dbType == pkgConst.DBTypePolarDBForMySQL || dbType == pkgConst.DBTypeGaussDB || dbType == pkgConst.DBTypePostgreSQL || - dbType == pkgConst.DBTypeRedis + dbType == pkgConst.DBTypeRedis || + dbType == pkgConst.DBTypeKingBase } func buildMongoDatasourceOptions(dbService *biz.DBService) (*string, interface{}, map[string]interface{}) { diff --git a/internal/sql_workbench/service/sql_workbench_service_test.go b/internal/sql_workbench/service/sql_workbench_service_test.go index af0d8408..9fe1c308 100644 --- a/internal/sql_workbench/service/sql_workbench_service_test.go +++ b/internal/sql_workbench/service/sql_workbench_service_test.go @@ -150,6 +150,7 @@ func Test_convertDBType(t *testing.T) { "MongoDB": {input: "MongoDB", expected: "MONGODB"}, "Redis": {input: "Redis", expected: "REDIS"}, "DB2": {input: "DB2", expected: "DB2"}, + "KingBase": {input: "KingBase", expected: "KINGBASE"}, "Unknown passthrough": {input: "UnknownDB", expected: "UnknownDB"}, } for name, tc := range cases { @@ -183,6 +184,7 @@ func Test_SupportDBType(t *testing.T) { "GaussDB supported": {input: pkgConst.DBTypeGaussDB, expected: true}, "GaussDBForMySQL unsupported": {input: pkgConst.DBTypeGaussDBForMySQL, expected: false}, "DB2 unsupported": {input: pkgConst.DBTypeDB2, expected: false}, + "KingBase supported": {input: pkgConst.DBTypeKingBase, expected: true}, "empty string unsupported": {input: pkgConst.DBType(""), expected: false}, "unknown type unsupported": {input: pkgConst.DBType("UnknownDBType"), expected: false}, } @@ -390,6 +392,89 @@ func Test_buildDatasourceBaseInfo_DB2(t *testing.T) { } } +// Test_buildDatasourceBaseInfo_KingBase 覆盖 KingBase → defaultSchema 契约(S1 / AC-1): +// +// (a) 正例:database_name=test → DefaultSchema=="test" 且 Type 经 convert 为 KINGBASE +// (b) 负例:缺 database_name → err 含 "database_name" +// (c) MySQL 回归:DefaultSchema == nil +func Test_buildDatasourceBaseInfo_KingBase(t *testing.T) { + svc := &SqlWorkbenchService{} + const envID = int64(1) + const datasourceName = "proj:kingbase_odc_test" + + cases := map[string]struct { + dbService *biz.DBService + expectErr bool + expectErrSubstr string + expectDefaultSchema *string + expectType string + }{ + "KingBase happy path": { + dbService: &biz.DBService{ + Name: "kingbase_odc_test", + DBType: string(pkgConst.DBTypeKingBase), + Host: "10.186.16.126", + Port: "1522", + User: "kb_dev", + AdditionalParams: pkgParams.Params{ + {Key: "database_name", Value: "test"}, + }, + }, + expectErr: false, + expectDefaultSchema: strPtr("test"), + expectType: "KINGBASE", + }, + "KingBase missing database_name": { + dbService: &biz.DBService{ + Name: "kingbase-missing-db", + DBType: string(pkgConst.DBTypeKingBase), + AdditionalParams: pkgParams.Params{}, + }, + expectErr: true, + expectErrSubstr: "database_name", + }, + "MySQL regression still no DefaultSchema": { + dbService: &biz.DBService{ + Name: "mysql-1", + DBType: "MySQL", + AdditionalParams: pkgParams.Params{}, + }, + expectErr: false, + expectDefaultSchema: nil, + expectType: "MYSQL", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got, err := svc.fillDatasourceBaseInfo(datasourceName, tc.dbService, envID) + if tc.expectErr { + if err == nil { + t.Fatalf("expected error, got nil; baseInfo=%+v", got) + } + if tc.expectErrSubstr != "" && !strings.Contains(err.Error(), tc.expectErrSubstr) { + t.Errorf("error %q does not contain %q", err.Error(), tc.expectErrSubstr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got == nil { + t.Fatalf("expected non-nil baseInfo") + } + if tc.expectType != "" && got.Type != tc.expectType { + t.Errorf("Type = %q, want %q", got.Type, tc.expectType) + } + if (got.DefaultSchema == nil) != (tc.expectDefaultSchema == nil) { + t.Errorf("DefaultSchema nil mismatch: got=%v, want=%v", got.DefaultSchema, tc.expectDefaultSchema) + } else if got.DefaultSchema != nil && tc.expectDefaultSchema != nil && *got.DefaultSchema != *tc.expectDefaultSchema { + t.Errorf("DefaultSchema = %q, want %q", *got.DefaultSchema, *tc.expectDefaultSchema) + } + }) + } +} + func strPtr(s string) *string { return &s } From 4e5ef7b50ff0d228810c25507c5307dfcf6cf058 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 13 Aug 2026 21:41:04 +0800 Subject: [PATCH 2/3] feat: sync GBase-8a datasource type and default schema to ODC Map product type GBase-8a to GBASE_8A alongside KingBase, require AdditionalParams.database as DefaultSchema, and default vcName to vc1. Co-authored-by: Cursor --- internal/dms/pkg/constant/const.go | 2 + internal/dms/pkg/constant/const_test.go | 1 + .../service/sql_workbench_service.go | 37 +++++++++++- .../service/sql_workbench_service_test.go | 59 +++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/internal/dms/pkg/constant/const.go b/internal/dms/pkg/constant/const.go index c7c5c5c7..859aa034 100644 --- a/internal/dms/pkg/constant/const.go +++ b/internal/dms/pkg/constant/const.go @@ -264,6 +264,8 @@ func ParseDBType(s string) (DBType, error) { return DBTypeOceanBaseOracle, nil case "KingBase": return DBTypeKingBase, nil + case "GBase-8a": + return DBTypeGBase8a, nil default: return "", fmt.Errorf("invalid db type: %s", s) diff --git a/internal/dms/pkg/constant/const_test.go b/internal/dms/pkg/constant/const_test.go index bdd5dc14..f54d823e 100644 --- a/internal/dms/pkg/constant/const_test.go +++ b/internal/dms/pkg/constant/const_test.go @@ -130,6 +130,7 @@ func TestParseDBType(t *testing.T) { "MongoDB": {input: "MongoDB", expected: DBTypeMongoDB}, "Redis": {input: "Redis", expected: DBTypeRedis}, "KingBase": {input: "KingBase", expected: DBTypeKingBase}, + "GBase-8a": {input: "GBase-8a", expected: DBTypeGBase8a}, // "PolarDB" 单独不应匹配 "PolarDB only": {input: "PolarDB", expectError: true}, "invalid type": {input: "UnknownDB", expectError: true}, diff --git a/internal/sql_workbench/service/sql_workbench_service.go b/internal/sql_workbench/service/sql_workbench_service.go index a6c20793..50483546 100644 --- a/internal/sql_workbench/service/sql_workbench_service.go +++ b/internal/sql_workbench/service/sql_workbench_service.go @@ -1047,6 +1047,13 @@ const ( mongoAuthDatabaseParam = "auth_source" mongoReplicaSetParam = "replica_set" redisDefaultDatabaseParam = "default_database" + + // GBase-8a:DMS 附加参数 → ODC 字段 + gbase8aDatabaseParam = "database" + gbase8aVcNameParam = "vc_name" + gbase8aVcNameParamAlias = "vcName" + gbase8aJdbcVcNameKey = "vcName" + gbase8aDefaultSessionVC = "vc1" // 与 DSN / session_vc 对齐;表单暂未暴露 VC 时的会话硬门槛缺省 ) // buildDatasourceBaseInfo 构建数据源基础信息 @@ -1106,9 +1113,34 @@ func (sqlWorkbenchService *SqlWorkbenchService) fillDatasourceBaseInfo(datasourc baseInfo.DefaultSchema = &databaseName } + // GBase-8a:AdditionalParams.database → DefaultSchema;VC → jdbcUrlParameters.vcName + if dbService.DBType == string(pkgConst.DBTypeGBase8a) { + databaseParam := dbService.AdditionalParams.GetParam(gbase8aDatabaseParam) + if databaseParam == nil || databaseParam.Value == "" { + return nil, fmt.Errorf("GBase-8a 数据源 %s 缺少 AdditionalParam database,请在数据源 AdditionalParams 中补充", dbService.Name) + } + database := databaseParam.Value + baseInfo.DefaultSchema = &database + baseInfo.JDBCParams = buildGBase8aJdbcUrlParameters(dbService) + } + return baseInfo, nil } +// buildGBase8aJdbcUrlParameters 将会话 VC 写入 ODC jdbcUrlParameters(键 vcName)。 +// 优先 AdditionalParams.vc_name / vcName;缺省注入 vc1(S2 会话硬门槛;插件 metas 暂仅暴露 database)。 +func buildGBase8aJdbcUrlParameters(dbService *biz.DBService) map[string]interface{} { + vcName := gbase8aDefaultSessionVC + if p := dbService.AdditionalParams.GetParam(gbase8aVcNameParam); p != nil && p.Value != "" { + vcName = p.Value + } else if p := dbService.AdditionalParams.GetParam(gbase8aVcNameParamAlias); p != nil && p.Value != "" { + vcName = p.Value + } + return map[string]interface{}{ + gbase8aJdbcVcNameKey: vcName, + } +} + // buildCreateDatasourceRequest 构建创建数据源请求 func (sqlWorkbenchService *SqlWorkbenchService) buildCreateDatasourceRequest(ctx context.Context, dbService *biz.DBService, sqlWorkbenchUser *biz.SqlWorkbenchUser, environmentID int64) (client.CreateDatasourceRequest, error) { baseInfo, err := sqlWorkbenchService.buildDatasourceBaseInfo(ctx, dbService, environmentID) @@ -1199,6 +1231,8 @@ func (sqlWorkbenchService *SqlWorkbenchService) convertDBType(dmsDBType string) return "DB2" case "KingBase": return "KINGBASE" + case "GBase-8a": + return "GBASE_8A" default: return dmsDBType } @@ -1216,7 +1250,8 @@ func (sqlWorkbenchService *SqlWorkbenchService) SupportDBType(dbType pkgConst.DB dbType == pkgConst.DBTypeGaussDB || dbType == pkgConst.DBTypePostgreSQL || dbType == pkgConst.DBTypeRedis || - dbType == pkgConst.DBTypeKingBase + dbType == pkgConst.DBTypeKingBase || + dbType == pkgConst.DBTypeGBase8a } func buildMongoDatasourceOptions(dbService *biz.DBService) (*string, interface{}, map[string]interface{}) { diff --git a/internal/sql_workbench/service/sql_workbench_service_test.go b/internal/sql_workbench/service/sql_workbench_service_test.go index 9fe1c308..8c672d97 100644 --- a/internal/sql_workbench/service/sql_workbench_service_test.go +++ b/internal/sql_workbench/service/sql_workbench_service_test.go @@ -151,6 +151,7 @@ func Test_convertDBType(t *testing.T) { "Redis": {input: "Redis", expected: "REDIS"}, "DB2": {input: "DB2", expected: "DB2"}, "KingBase": {input: "KingBase", expected: "KINGBASE"}, + "GBase-8a": {input: "GBase-8a", expected: "GBASE_8A"}, "Unknown passthrough": {input: "UnknownDB", expected: "UnknownDB"}, } for name, tc := range cases { @@ -185,6 +186,7 @@ func Test_SupportDBType(t *testing.T) { "GaussDBForMySQL unsupported": {input: pkgConst.DBTypeGaussDBForMySQL, expected: false}, "DB2 unsupported": {input: pkgConst.DBTypeDB2, expected: false}, "KingBase supported": {input: pkgConst.DBTypeKingBase, expected: true}, + "GBase-8a supported": {input: pkgConst.DBTypeGBase8a, expected: true}, "empty string unsupported": {input: pkgConst.DBType(""), expected: false}, "unknown type unsupported": {input: pkgConst.DBType("UnknownDBType"), expected: false}, } @@ -312,6 +314,7 @@ func Test_buildDatasourceBaseInfo_DB2(t *testing.T) { expectErrSubstr string expectDefaultSchema *string expectServiceName *string + expectJdbcVcName *string // nil = 不要求 JDBCParams;非 nil = 断言 jdbcUrlParameters.vcName }{ "DB2 happy path": { dbService: &biz.DBService{ @@ -356,6 +359,53 @@ func Test_buildDatasourceBaseInfo_DB2(t *testing.T) { expectDefaultSchema: nil, expectServiceName: strPtr("ORCL"), }, + "GBase-8a happy path defaults vc1": { + dbService: &biz.DBService{ + Name: "gbase8a-1", + DBType: string(pkgConst.DBTypeGBase8a), + AdditionalParams: pkgParams.Params{ + {Key: "database", Value: "gbase"}, + }, + }, + expectErr: false, + expectDefaultSchema: strPtr("gbase"), + expectServiceName: nil, + expectJdbcVcName: strPtr("vc1"), + }, + "GBase-8a vc_name override": { + dbService: &biz.DBService{ + Name: "gbase8a-vc", + DBType: string(pkgConst.DBTypeGBase8a), + AdditionalParams: pkgParams.Params{ + {Key: "database", Value: "gbase"}, + {Key: "vc_name", Value: "vc_custom"}, + }, + }, + expectErr: false, + expectDefaultSchema: strPtr("gbase"), + expectServiceName: nil, + expectJdbcVcName: strPtr("vc_custom"), + }, + "GBase-8a missing database": { + dbService: &biz.DBService{ + Name: "gbase8a-2", + DBType: string(pkgConst.DBTypeGBase8a), + AdditionalParams: pkgParams.Params{}, + }, + expectErr: true, + expectErrSubstr: "database", + }, + "GBase-8a ignores database_name": { + dbService: &biz.DBService{ + Name: "gbase8a-3", + DBType: string(pkgConst.DBTypeGBase8a), + AdditionalParams: pkgParams.Params{ + {Key: "database_name", Value: "wrong"}, + }, + }, + expectErr: true, + expectErrSubstr: "database", + }, } for name, tc := range cases { @@ -388,6 +438,15 @@ func Test_buildDatasourceBaseInfo_DB2(t *testing.T) { } else if got.ServiceName != nil && tc.expectServiceName != nil && *got.ServiceName != *tc.expectServiceName { t.Errorf("ServiceName = %q, want %q", *got.ServiceName, *tc.expectServiceName) } + if tc.expectJdbcVcName != nil { + if got.JDBCParams == nil { + t.Fatalf("expected JDBCParams with vcName=%q, got nil", *tc.expectJdbcVcName) + } + gotVC, _ := got.JDBCParams[gbase8aJdbcVcNameKey].(string) + if gotVC != *tc.expectJdbcVcName { + t.Errorf("JDBCParams.vcName = %q, want %q; params=%v", gotVC, *tc.expectJdbcVcName, got.JDBCParams) + } + } }) } } From e9b19484fec7c1dfda59266b8fd40f6a3677e51d Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 13 Aug 2026 21:58:23 +0800 Subject: [PATCH 3/3] fix: declare DBTypeGBase8a constant for GBase-8a support --- internal/dms/pkg/constant/const.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/dms/pkg/constant/const.go b/internal/dms/pkg/constant/const.go index 859aa034..ab8340ce 100644 --- a/internal/dms/pkg/constant/const.go +++ b/internal/dms/pkg/constant/const.go @@ -293,6 +293,7 @@ const ( DBTypeRedis DBType = "Redis" DBTypeOceanBaseOracle DBType = "OceanBase For Oracle" DBTypeKingBase DBType = "KingBase" + DBTypeGBase8a DBType = "GBase-8a" ) var supportedDataExportDBTypes = map[DBType]struct{}{