diff --git a/internal/dms/pkg/constant/const.go b/internal/dms/pkg/constant/const.go index 86362553..7377ed0e 100644 --- a/internal/dms/pkg/constant/const.go +++ b/internal/dms/pkg/constant/const.go @@ -258,6 +258,8 @@ func ParseDBType(s string) (DBType, error) { return DBTypePolarDBForMySQL, nil case "MongoDB": return DBTypeMongoDB, nil + case "Redis": + return DBTypeRedis, nil case "OceanBase For Oracle": return DBTypeOceanBaseOracle, nil @@ -284,6 +286,7 @@ const ( DBTypeHANA DBType = "HANA" DBTypePolarDBForMySQL DBType = "PolarDB For MySQL" DBTypeMongoDB DBType = "MongoDB" + DBTypeRedis DBType = "Redis" DBTypeOceanBaseOracle DBType = "OceanBase For Oracle" ) diff --git a/internal/dms/pkg/constant/const_test.go b/internal/dms/pkg/constant/const_test.go index eb161075..1958f259 100644 --- a/internal/dms/pkg/constant/const_test.go +++ b/internal/dms/pkg/constant/const_test.go @@ -128,6 +128,7 @@ func TestParseDBType(t *testing.T) { // PolarDB-MySQL 新增 (Issue #826) "PolarDB For MySQL": {input: "PolarDB For MySQL", expected: DBTypePolarDBForMySQL}, "MongoDB": {input: "MongoDB", expected: DBTypeMongoDB}, + "Redis": {input: "Redis", expected: DBTypeRedis}, // "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 ef71e630..8b17f172 100644 --- a/internal/sql_workbench/service/sql_workbench_service.go +++ b/internal/sql_workbench/service/sql_workbench_service.go @@ -863,6 +863,12 @@ const ( mongoTLSEnabledParam = "tls" mongoDirectConnectionParam = "direct_connection" mongoTLSSkipVerifyParam = "tls_skip_verify" + redisDefaultDatabaseParam = "default_database" + redisTLSEnabledParam = "tls" + redisTLSInsecureParam = "tls_insecure" + redisScanCountParam = "scan_count" + redisKeySeparatorParam = "key_separator" + redisCommandTimeoutParam = "command_timeout_ms" ) // buildDatasourceBaseInfo 构建数据源基础信息 @@ -898,6 +904,10 @@ func (sqlWorkbenchService *SqlWorkbenchService) fillDatasourceBaseInfo(datasourc baseInfo.DefaultSchema, baseInfo.Properties, baseInfo.JDBCParams = buildMongoDatasourceOptions(dbService) } + if dbService.DBType == string(pkgConst.DBTypeRedis) { + baseInfo.DefaultSchema, baseInfo.Properties, baseInfo.JDBCParams = buildRedisDatasourceOptions(dbService) + } + // DB2 特殊处理:从 AdditionalParams.database_name 取默认 schema 透传到 ODC if dbService.DBType == "DB2" { databaseNameParam := dbService.AdditionalParams.GetParam("database_name") @@ -993,6 +1003,8 @@ func (sqlWorkbenchService *SqlWorkbenchService) convertDBType(dmsDBType string) return "MYSQL" case "MongoDB": return "MONGODB" + case "Redis": + return "REDIS" case "DB2": return "DB2" default: @@ -1010,7 +1022,8 @@ func (sqlWorkbenchService *SqlWorkbenchService) SupportDBType(dbType pkgConst.DB dbType == pkgConst.DBTypeGoldenDB || dbType == pkgConst.DBTypePolarDBForMySQL || dbType == pkgConst.DBTypeGaussDB || - dbType == pkgConst.DBTypePostgreSQL + dbType == pkgConst.DBTypePostgreSQL || + dbType == pkgConst.DBTypeRedis } func buildMongoDatasourceOptions(dbService *biz.DBService) (*string, interface{}, map[string]interface{}) { @@ -1050,6 +1063,35 @@ func buildMongoDatasourceOptions(dbService *biz.DBService) (*string, interface{} return defaultSchema, nil, jdbcParams } +func buildRedisDatasourceOptions(dbService *biz.DBService) (*string, interface{}, map[string]interface{}) { + defaultDatabase := dbService.AdditionalParams.GetParam(redisDefaultDatabaseParam).String() + var defaultSchema *string + jdbcParams := map[string]interface{}{} + if defaultDatabase != "" { + defaultSchema = &defaultDatabase + jdbcParams["defaultDatabase"] = defaultDatabase + } + if tlsParam := dbService.AdditionalParams.GetParam(redisTLSEnabledParam); tlsParam != nil && tlsParam.String() != "" { + jdbcParams["tls"] = tlsParam.Bool() + } + if dbService.AdditionalParams.GetParam(redisTLSInsecureParam).Bool() { + jdbcParams["tlsInsecure"] = true + } + if scanCount := dbService.AdditionalParams.GetParam(redisScanCountParam).String(); scanCount != "" { + jdbcParams["scanCount"] = scanCount + } + if keySeparator := dbService.AdditionalParams.GetParam(redisKeySeparatorParam).String(); keySeparator != "" { + jdbcParams["keySeparator"] = keySeparator + } + if timeout := dbService.AdditionalParams.GetParam(redisCommandTimeoutParam).String(); timeout != "" { + jdbcParams["commandTimeoutMs"] = timeout + } + if len(jdbcParams) == 0 { + return defaultSchema, nil, nil + } + return defaultSchema, nil, jdbcParams +} + func interfacePtr(v interface{}) *interface{} { if v == nil { return nil diff --git a/internal/sql_workbench/service/sql_workbench_service_test.go b/internal/sql_workbench/service/sql_workbench_service_test.go index 065087b3..b1cda30e 100644 --- a/internal/sql_workbench/service/sql_workbench_service_test.go +++ b/internal/sql_workbench/service/sql_workbench_service_test.go @@ -28,6 +28,7 @@ func Test_convertDBType(t *testing.T) { "PolarDB For MySQL": {input: "PolarDB For MySQL", expected: "MYSQL"}, "GaussDB": {input: "GaussDB", expected: "GAUSSDB"}, "MongoDB": {input: "MongoDB", expected: "MONGODB"}, + "Redis": {input: "Redis", expected: "REDIS"}, "DB2": {input: "DB2", expected: "DB2"}, "Unknown passthrough": {input: "UnknownDB", expected: "UnknownDB"}, } @@ -55,6 +56,7 @@ func Test_SupportDBType(t *testing.T) { "TDSQL supported": {input: pkgConst.DBTypeTDSQLForInnoDB, expected: true}, "GoldenDB supported": {input: pkgConst.DBTypeGoldenDB, expected: true}, "MongoDB unsupported": {input: pkgConst.DBTypeMongoDB, expected: false}, + "Redis supported": {input: pkgConst.DBTypeRedis, expected: true}, "PostgreSQL supported": {input: pkgConst.DBTypePostgreSQL, expected: true}, "SQL Server unsupported": {input: pkgConst.DBTypeSQLServer, expected: false}, "PolarDB For MySQL supported": {input: pkgConst.DBTypePolarDBForMySQL, expected: true}, @@ -140,6 +142,52 @@ func Test_buildMongoDatasourceOptions_tlsOnly(t *testing.T) { } } +func Test_buildRedisDatasourceOptions(t *testing.T) { + defaultDB := "2" + defaultSchema, propertiesValue, jdbcParams := buildRedisDatasourceOptions(&biz.DBService{ + DBType: string(pkgConst.DBTypeRedis), + Host: "127.0.0.1", + Port: "6379", + AdditionalParams: pkgParams.Params{ + &pkgParams.Param{Key: redisDefaultDatabaseParam, Value: defaultDB, Type: pkgParams.ParamTypeString}, + &pkgParams.Param{Key: redisTLSEnabledParam, Value: "true", Type: pkgParams.ParamTypeBool}, + &pkgParams.Param{Key: redisTLSInsecureParam, Value: "true", Type: pkgParams.ParamTypeBool}, + &pkgParams.Param{Key: redisScanCountParam, Value: "200", Type: pkgParams.ParamTypeString}, + &pkgParams.Param{Key: redisKeySeparatorParam, Value: ":", Type: pkgParams.ParamTypeString}, + &pkgParams.Param{Key: redisCommandTimeoutParam, Value: "3000", Type: pkgParams.ParamTypeString}, + }, + }) + if defaultSchema == nil || *defaultSchema != defaultDB { + t.Fatalf("unexpected default schema: %#v", defaultSchema) + } + if propertiesValue != nil { + t.Fatalf("expected nil properties, got %#v", propertiesValue) + } + if jdbcParams["defaultDatabase"] != defaultDB || jdbcParams["tls"] != true || jdbcParams["tlsInsecure"] != true { + t.Fatalf("unexpected redis base params: %#v", jdbcParams) + } + if jdbcParams["scanCount"] != "200" || jdbcParams["keySeparator"] != ":" || jdbcParams["commandTimeoutMs"] != "3000" { + t.Fatalf("unexpected redis tuning params: %#v", jdbcParams) + } +} + +func Test_buildRedisDatasourceOptions_noSensitiveProperties(t *testing.T) { + _, propertiesValue, jdbcParams := buildRedisDatasourceOptions(&biz.DBService{ + DBType: string(pkgConst.DBTypeRedis), + User: "default", + Password: "secret", + AdditionalParams: pkgParams.Params{ + &pkgParams.Param{Key: redisTLSEnabledParam, Value: "false", Type: pkgParams.ParamTypeBool}, + }, + }) + if propertiesValue != nil { + t.Fatalf("expected redis properties to stay nil, got %#v", propertiesValue) + } + if _, ok := jdbcParams["password"]; ok { + t.Fatalf("redis password leaked into jdbc params: %#v", jdbcParams) + } +} + // Test_buildDatasourceBaseInfo_DB2 覆盖 buildDatasourceBaseInfo 中 DB2 / 回归 4 组 case: // // (a) DB2 正例:AdditionalParam database_name=testdb → baseInfo.DefaultSchema=="testdb" @@ -154,11 +202,11 @@ func Test_buildDatasourceBaseInfo_DB2(t *testing.T) { const datasourceName = "proj:ds" cases := map[string]struct { - dbService *biz.DBService - expectErr bool - expectErrSubstr string - expectDefaultSchema *string - expectServiceName *string + dbService *biz.DBService + expectErr bool + expectErrSubstr string + expectDefaultSchema *string + expectServiceName *string }{ "DB2 happy path": { dbService: &biz.DBService{