🐛 fix(oceanbase): 修复 Oracle 协议保存与连接链路

- 测试连接统一走 RPC 配置构造,确保 OceanBase Oracle 协议生效

- 保存连接时同步写入 oceanBaseProtocol 与 protocol 参数

- 编辑回显支持从显式字段、连接参数和 URI 恢复协议

- 双击连接时清理旧树缓存,避免复用 MySQL 协议子节点

- 补充 OceanBase 协议解析与缓存 key 隔离测试
This commit is contained in:
Syngnat
2026-04-30 17:27:17 +08:00
parent 5f9adcac37
commit 3c68325132
14 changed files with 289 additions and 31 deletions

View File

@@ -180,7 +180,9 @@ func normalizeCacheKeyConfig(config connection.ConnectionConfig) connection.Conn
normalized.ID = ""
normalized.Type = strings.ToLower(strings.TrimSpace(normalized.Type))
if normalized.Type == "oceanbase" {
normalized.ConnectionParams = normalizeOceanBaseConnectionParamsForCache(normalized.ConnectionParams)
protocol := resolveOceanBaseProtocolForApp(normalized)
normalized.ConnectionParams = normalizeOceanBaseConnectionParamsForCacheWithProtocol(normalized.ConnectionParams, protocol)
normalized.OceanBaseProtocol = ""
}
// timeout 仅用于 Query/Ping 控制,不应作为物理连接复用键的一部分。
normalized.Timeout = 0

View File

@@ -139,6 +139,24 @@ func TestGetCacheKey_KeepOceanBaseProtocolIsolation(t *testing.T) {
}
}
func TestGetCacheKey_KeepOceanBaseExplicitProtocolIsolation(t *testing.T) {
base := connection.ConnectionConfig{
Type: "oceanbase",
Host: "ob.local",
Port: 2881,
User: "sys@oracle001",
Database: "ORCL",
}
modified := base
modified.OceanBaseProtocol = "oracle"
left := getCacheKey(base)
right := getCacheKey(modified)
if left == right {
t.Fatalf("expected different cache key for explicit OceanBase Oracle protocol")
}
}
func TestGetCacheKey_KeepOceanBaseDefaultProtocolEquivalentToMySQL(t *testing.T) {
base := connection.ConnectionConfig{
Type: "oceanbase",
@@ -157,6 +175,24 @@ func TestGetCacheKey_KeepOceanBaseDefaultProtocolEquivalentToMySQL(t *testing.T)
}
}
func TestGetCacheKey_KeepOceanBaseDefaultProtocolEquivalentToExplicitMySQL(t *testing.T) {
base := connection.ConnectionConfig{
Type: "oceanbase",
Host: "ob.local",
Port: 2881,
User: "root@test",
Database: "app",
}
modified := base
modified.OceanBaseProtocol = "mysql"
left := getCacheKey(base)
right := getCacheKey(modified)
if left != right {
t.Fatalf("expected default OceanBase protocol to equal explicit mysql, got %s vs %s", left, right)
}
}
func TestGetCacheKey_OceanBaseProtocolParamWinsOverAliases(t *testing.T) {
base := connection.ConnectionConfig{
Type: "oceanbase",

View File

@@ -54,9 +54,9 @@ func TestNormalizeRunConfig_OceanBaseOracleKeepsServiceName(t *testing.T) {
t.Parallel()
config := connection.ConnectionConfig{
Type: "oceanbase",
Database: "OBORCL",
ConnectionParams: "protocol=oracle",
Type: "oceanbase",
Database: "OBORCL",
OceanBaseProtocol: "oracle",
}
runConfig := normalizeRunConfig(config, "SYS")
@@ -69,8 +69,8 @@ func TestNormalizeSchemaAndTable_OceanBaseOracleUsesSchemaFromDatabaseTree(t *te
t.Parallel()
schema, table := normalizeSchemaAndTable(connection.ConnectionConfig{
Type: "oceanbase",
ConnectionParams: "protocol=oracle",
Type: "oceanbase",
OceanBaseProtocol: "oracle",
}, "SYS", "ORDERS")
if schema != "SYS" || table != "ORDERS" {

View File

@@ -90,8 +90,8 @@ func TestResolveDDLDBType_OceanBaseOracleProtocol(t *testing.T) {
t.Parallel()
cfg := connection.ConnectionConfig{
Type: "oceanbase",
ConnectionParams: "protocol=oracle",
Type: "oceanbase",
OceanBaseProtocol: "oracle",
}
if got := resolveDDLDBType(cfg); got != "oracle" {
t.Fatalf("expected OceanBase Oracle protocol to use oracle DDL dialect, got %q", got)

View File

@@ -11,11 +11,29 @@ func normalizeOceanBaseProtocolForApp(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "oracle", "oracle-mode", "oracle_mode", "oboracle":
return "oracle"
case "mysql", "mysql-compatible", "mysql_compatible", "mysql-mode", "mysql_mode":
return "mysql"
default:
return "mysql"
}
}
func resolveOceanBaseProtocolForApp(config connection.ConnectionConfig) string {
if !strings.EqualFold(strings.TrimSpace(config.Type), "oceanbase") {
return ""
}
if explicit := strings.TrimSpace(config.OceanBaseProtocol); explicit != "" {
return normalizeOceanBaseProtocolForApp(explicit)
}
if protocol := resolveOceanBaseProtocolParam(config.ConnectionParams); protocol != "" {
return protocol
}
if protocol := resolveOceanBaseProtocolParam(config.URI); protocol != "" {
return protocol
}
return "mysql"
}
func resolveOceanBaseProtocolParam(raw string) string {
text := strings.TrimSpace(raw)
if text == "" {
@@ -61,15 +79,19 @@ func normalizeOceanBaseConnectionParamsForCache(raw string) string {
return values.Encode()
}
func isOceanBaseOracleProtocol(config connection.ConnectionConfig) bool {
if !strings.EqualFold(strings.TrimSpace(config.Type), "oceanbase") {
return false
func normalizeOceanBaseConnectionParamsForCacheWithProtocol(raw string, protocol string) string {
normalized := normalizeOceanBaseConnectionParamsForCache(raw)
if !strings.EqualFold(protocol, "oracle") {
return normalized
}
if protocol := resolveOceanBaseProtocolParam(config.ConnectionParams); protocol != "" {
return protocol == "oracle"
values, err := url.ParseQuery(strings.TrimLeft(strings.TrimSpace(normalized), "?&"))
if err != nil {
values = url.Values{}
}
if protocol := resolveOceanBaseProtocolParam(config.URI); protocol != "" {
return protocol == "oracle"
}
return false
values.Set("protocol", "oracle")
return values.Encode()
}
func isOceanBaseOracleProtocol(config connection.ConnectionConfig) bool {
return resolveOceanBaseProtocolForApp(config) == "oracle"
}

View File

@@ -104,6 +104,7 @@ type ConnectionConfig struct {
RedisDB int `json:"redisDB,omitempty"` // Redis database index (0-15)
URI string `json:"uri,omitempty"` // Connection URI for copy/paste
ClickHouseProtocol string `json:"clickHouseProtocol,omitempty"` // auto | http | native
OceanBaseProtocol string `json:"oceanBaseProtocol,omitempty"` // mysql | oracle
Hosts []string `json:"hosts,omitempty"` // Multi-host addresses: host:port
Topology string `json:"topology,omitempty"` // single | replica | cluster
MySQLReplicaUser string `json:"mysqlReplicaUser,omitempty"` // MySQL replica auth user

View File

@@ -171,6 +171,9 @@ func resolveOceanBaseProtocolFromValues(values url.Values) string {
}
func resolveOceanBaseProtocol(config connection.ConnectionConfig) string {
if explicit := strings.TrimSpace(config.OceanBaseProtocol); explicit != "" {
return normalizeOceanBaseProtocol(explicit)
}
if protocol := resolveOceanBaseProtocolFromValues(connectionParamsFromText(config.ConnectionParams)); protocol != "" {
return protocol
}
@@ -213,6 +216,7 @@ func stripOceanBaseProtocolURI(raw string) string {
func withoutOceanBaseProtocolParams(config connection.ConnectionConfig) connection.ConnectionConfig {
next := config
next.OceanBaseProtocol = ""
next.ConnectionParams = stripOceanBaseProtocolParams(config.ConnectionParams)
next.URI = stripOceanBaseProtocolURI(config.URI)
return next

View File

@@ -56,6 +56,15 @@ func TestResolveOceanBaseProtocol(t *testing.T) {
},
want: oceanBaseProtocolMySQL,
},
{
name: "explicit config protocol wins over params",
config: connection.ConnectionConfig{
Type: "oceanbase",
OceanBaseProtocol: "oracle",
ConnectionParams: "protocol=mysql",
},
want: oceanBaseProtocolOracle,
},
{
name: "protocol key wins over compatibility aliases",
config: connection.ConnectionConfig{