Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/dms/pkg/constant/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ func ParseDBType(s string) (DBType, error) {
return DBTypeRedis, nil
case "OceanBase For Oracle":
return DBTypeOceanBaseOracle, nil
case "KingBase":
return DBTypeKingBase, nil
case "GBase-8a":
return DBTypeGBase8a, nil

default:
return "", fmt.Errorf("invalid db type: %s", s)
Expand All @@ -288,6 +292,8 @@ const (
DBTypeMongoDB DBType = "MongoDB"
DBTypeRedis DBType = "Redis"
DBTypeOceanBaseOracle DBType = "OceanBase For Oracle"
DBTypeKingBase DBType = "KingBase"
DBTypeGBase8a DBType = "GBase-8a"
)

var supportedDataExportDBTypes = map[DBType]struct{}{
Expand Down
2 changes: 2 additions & 0 deletions internal/dms/pkg/constant/const_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ 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},
"GBase-8a": {input: "GBase-8a", expected: DBTypeGBase8a},
// "PolarDB" 单独不应匹配
"PolarDB only": {input: "PolarDB", expectError: true},
"invalid type": {input: "UnknownDB", expectError: true},
Expand Down
50 changes: 49 additions & 1 deletion internal/sql_workbench/service/sql_workbench_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 构建数据源基础信息
Expand Down Expand Up @@ -1096,9 +1103,44 @@ 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
}

// 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)
Expand Down Expand Up @@ -1187,6 +1229,10 @@ func (sqlWorkbenchService *SqlWorkbenchService) convertDBType(dmsDBType string)
return "REDIS"
case "DB2":
return "DB2"
case "KingBase":
return "KINGBASE"
case "GBase-8a":
return "GBASE_8A"
default:
return dmsDBType
}
Expand All @@ -1203,7 +1249,9 @@ 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 ||
dbType == pkgConst.DBTypeGBase8a
}

func buildMongoDatasourceOptions(dbService *biz.DBService) (*string, interface{}, map[string]interface{}) {
Expand Down
144 changes: 144 additions & 0 deletions internal/sql_workbench/service/sql_workbench_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ 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"},
"GBase-8a": {input: "GBase-8a", expected: "GBASE_8A"},
"Unknown passthrough": {input: "UnknownDB", expected: "UnknownDB"},
}
for name, tc := range cases {
Expand Down Expand Up @@ -183,6 +185,8 @@ 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},
"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},
}
Expand Down Expand Up @@ -310,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{
Expand Down Expand Up @@ -354,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 {
Expand Down Expand Up @@ -386,6 +438,98 @@ 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)
}
}
})
}
}

// 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)
}
})
}
}
Expand Down