Files
MyGoNavi/internal/app/oceanbase_protocol.go
Syngnat f94a0429d5 🐛 fix(oceanbase): 解决 Oracle 租户 MySQL wire 下双引号被误解析与列元数据静默失败
- DSN 注入 sql_mode='ANSI_QUOTES':让元数据查询的 AS "OWNER" 与 ApplyChanges 的 "schema"."table" 在 MySQL wire 上被识别为标识符
- sql_mode 加入 mysql driver 参数白名单,避免被 mergeMySQLConnectionParam 过滤丢弃
- 加载 Oracle 列元数据失败不再静默,改为返回带 ALL_TAB_COLUMNS 诊断提示的明确错误
- 修复 stripOceanBaseConnectionParamsForCache 未剥离 # 片段导致与 resolveOceanBaseProtocolParam 行为不一致
- 锁定 mysql ParseDSN 对 sys@oracle001#cluster:p@ss 类租户凭据切分的 invariant,防止未来误加 url.QueryEscape
- 同步 OceanBase agent revision,强制旧 driver-agent 被运行时校验拒绝
2026-05-14 12:09:19 +08:00

142 lines
3.9 KiB
Go

package app
import (
"net/url"
"strings"
"GoNavi-Wails/internal/connection"
)
func normalizeOceanBaseProtocolForApp(raw string) string {
normalized := strings.ToLower(strings.TrimSpace(raw))
switch normalized {
case "oracle", "oracle-mode", "oracle_mode", "oboracle":
return "oracle"
case "mysql", "mysql-compatible", "mysql_compatible", "mysql-mode", "mysql_mode", "obmysql":
return "mysql"
default:
return normalized
}
}
func isSupportedOceanBaseProtocolForApp(protocol string) bool {
return protocol == "mysql" || protocol == "oracle"
}
func resolveOceanBaseProtocolForApp(config connection.ConnectionConfig) string {
if !strings.EqualFold(strings.TrimSpace(config.Type), "oceanbase") {
return ""
}
explicitProtocol := ""
if explicit := strings.TrimSpace(config.OceanBaseProtocol); explicit != "" {
explicitProtocol = normalizeOceanBaseProtocolForApp(explicit)
if !isSupportedOceanBaseProtocolForApp(explicitProtocol) {
return explicitProtocol
}
}
if protocol := resolveOceanBaseProtocolParam(config.ConnectionParams); protocol != "" {
if !isSupportedOceanBaseProtocolForApp(protocol) {
return protocol
}
if explicitProtocol != "" {
return explicitProtocol
}
return protocol
}
if protocol := resolveOceanBaseProtocolParam(config.URI); protocol != "" {
if !isSupportedOceanBaseProtocolForApp(protocol) {
return protocol
}
if explicitProtocol != "" {
return explicitProtocol
}
return protocol
}
if explicitProtocol != "" {
return explicitProtocol
}
return "mysql"
}
func resolveOceanBaseProtocolParam(raw string) string {
text := strings.TrimSpace(raw)
if text == "" {
return ""
}
if queryIndex := strings.Index(text, "?"); queryIndex >= 0 {
text = text[queryIndex+1:]
}
if hashIndex := strings.Index(text, "#"); hashIndex >= 0 {
text = text[:hashIndex]
}
values, err := url.ParseQuery(strings.TrimLeft(strings.TrimSpace(text), "?&"))
if err != nil {
return ""
}
for _, key := range []string{"protocol", "oceanBaseProtocol", "oceanbaseProtocol", "tenantMode", "compatMode", "mode"} {
if value := strings.TrimSpace(values.Get(key)); value != "" {
return normalizeOceanBaseProtocolForApp(value)
}
}
return ""
}
func stripOceanBaseConnectionParamsForCache(raw string) string {
text := strings.TrimSpace(raw)
if text == "" {
return ""
}
if queryIndex := strings.Index(text, "?"); queryIndex >= 0 {
text = text[queryIndex+1:]
}
if hashIndex := strings.Index(text, "#"); hashIndex >= 0 {
text = text[:hashIndex]
}
values, err := url.ParseQuery(strings.TrimLeft(text, "?&"))
if err != nil {
return text
}
if len(values) == 0 {
return ""
}
for _, key := range []string{"protocol", "oceanBaseProtocol", "oceanbaseProtocol", "tenantMode", "compatMode", "mode"} {
values.Del(key)
}
return values.Encode()
}
func normalizeOceanBaseConnectionParamsForCache(raw string) string {
normalized := stripOceanBaseConnectionParamsForCache(raw)
protocol := resolveOceanBaseProtocolParam(raw)
if protocol != "" && !strings.EqualFold(protocol, "mysql") {
values, err := url.ParseQuery(strings.TrimLeft(strings.TrimSpace(normalized), "?&"))
if err != nil {
values = url.Values{}
}
values.Set("protocol", protocol)
return values.Encode()
}
return normalized
}
func normalizeOceanBaseConnectionParamsForCacheWithProtocol(raw string, protocol string) string {
resolvedProtocol := normalizeOceanBaseProtocolForApp(protocol)
if resolvedProtocol == "" {
return normalizeOceanBaseConnectionParamsForCache(raw)
}
normalized := stripOceanBaseConnectionParamsForCache(raw)
if strings.EqualFold(resolvedProtocol, "mysql") {
return normalized
}
values, err := url.ParseQuery(strings.TrimLeft(strings.TrimSpace(normalized), "?&"))
if err != nil {
values = url.Values{}
}
values.Set("protocol", resolvedProtocol)
return values.Encode()
}
func isOceanBaseOracleProtocol(config connection.ConnectionConfig) bool {
return resolveOceanBaseProtocolForApp(config) == "oracle"
}