mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
🐛 fix(oceanbase): 修复 Oracle 协议保存与连接链路
- 测试连接统一走 RPC 配置构造,确保 OceanBase Oracle 协议生效 - 保存连接时同步写入 oceanBaseProtocol 与 protocol 参数 - 编辑回显支持从显式字段、连接参数和 URI 恢复协议 - 双击连接时清理旧树缓存,避免复用 MySQL 协议子节点 - 补充 OceanBase 协议解析与缓存 key 隔离测试
This commit is contained in:
@@ -62,6 +62,7 @@ import {
|
||||
import { resolveConnectionSecretDraft } from "../utils/connectionSecretDraft";
|
||||
import { getCustomConnectionDsnValidationMessage } from "../utils/customConnectionDsn";
|
||||
import { mergeParsedUriValuesForForm } from "../utils/connectionUriMerge";
|
||||
import { buildRpcConnectionConfig } from "../utils/connectionRpcConfig";
|
||||
import { CUSTOM_CONNECTION_DRIVER_HELP } from "../utils/driverImportGuidance";
|
||||
import {
|
||||
applyNoAutoCapAttributes,
|
||||
@@ -121,6 +122,14 @@ const OCEANBASE_PROTOCOL_OPTIONS: Array<{
|
||||
{ value: "mysql", label: "MySQL" },
|
||||
{ value: "oracle", label: "Oracle" },
|
||||
];
|
||||
const OCEANBASE_PROTOCOL_PARAM_KEYS = [
|
||||
"protocol",
|
||||
"oceanBaseProtocol",
|
||||
"oceanbaseProtocol",
|
||||
"tenantMode",
|
||||
"compatMode",
|
||||
"mode",
|
||||
];
|
||||
|
||||
const normalizeClickHouseProtocolValue = (
|
||||
value: unknown,
|
||||
@@ -140,6 +149,47 @@ const normalizeOceanBaseProtocolValue = (
|
||||
.toLowerCase();
|
||||
return text === "oracle" ? "oracle" : "mysql";
|
||||
};
|
||||
const resolveOceanBaseProtocolValue = (
|
||||
value: unknown,
|
||||
): OceanBaseProtocolChoice | undefined => {
|
||||
const text = String(value || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!text) return undefined;
|
||||
return ["oracle", "oracle-mode", "oracle_mode", "oboracle"].includes(text)
|
||||
? "oracle"
|
||||
: "mysql";
|
||||
};
|
||||
const resolveOceanBaseProtocolFromQueryText = (
|
||||
value: unknown,
|
||||
): OceanBaseProtocolChoice | undefined => {
|
||||
let text = String(value || "").trim();
|
||||
if (!text) return undefined;
|
||||
const queryIndex = text.indexOf("?");
|
||||
if (queryIndex >= 0) {
|
||||
text = text.slice(queryIndex + 1);
|
||||
}
|
||||
const hashIndex = text.indexOf("#");
|
||||
if (hashIndex >= 0) {
|
||||
text = text.slice(0, hashIndex);
|
||||
}
|
||||
const params = new URLSearchParams(text.replace(/^[?&]+/, ""));
|
||||
for (const key of OCEANBASE_PROTOCOL_PARAM_KEYS) {
|
||||
const protocol = resolveOceanBaseProtocolValue(params.get(key));
|
||||
if (protocol) return protocol;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const resolveOceanBaseProtocolForConfig = (
|
||||
config: Partial<ConnectionConfig>,
|
||||
): OceanBaseProtocolChoice => {
|
||||
return (
|
||||
resolveOceanBaseProtocolValue(config.oceanBaseProtocol) ||
|
||||
resolveOceanBaseProtocolFromQueryText(config.connectionParams) ||
|
||||
resolveOceanBaseProtocolFromQueryText(config.uri) ||
|
||||
"mysql"
|
||||
);
|
||||
};
|
||||
type ConnectionSecretKey =
|
||||
| "primaryPassword"
|
||||
| "sshPassword"
|
||||
@@ -1125,6 +1175,18 @@ const ConnectionModal: React.FC<{
|
||||
return cloned.toString().slice(0, MAX_CONNECTION_PARAMS_LENGTH);
|
||||
};
|
||||
|
||||
const normalizeOceanBaseConnectionParamsText = (
|
||||
rawParams: unknown,
|
||||
selectedProtocol: OceanBaseProtocolChoice,
|
||||
) => {
|
||||
const params = new URLSearchParams(normalizeConnectionParamsText(rawParams));
|
||||
for (const key of OCEANBASE_PROTOCOL_PARAM_KEYS) {
|
||||
params.delete(key);
|
||||
}
|
||||
params.set("protocol", selectedProtocol);
|
||||
return params.toString().slice(0, MAX_CONNECTION_PARAMS_LENGTH);
|
||||
};
|
||||
|
||||
const mergeConnectionParams = (
|
||||
params: URLSearchParams,
|
||||
rawParams: unknown,
|
||||
@@ -2260,7 +2322,7 @@ const ConnectionModal: React.FC<{
|
||||
: "auto",
|
||||
oceanBaseProtocol:
|
||||
configType === "oceanbase"
|
||||
? normalizeOceanBaseProtocolValue(config.oceanBaseProtocol)
|
||||
? resolveOceanBaseProtocolForConfig(config)
|
||||
: "mysql",
|
||||
includeDatabases: initialValues.includeDatabases,
|
||||
includeRedisDatabases: initialValues.includeRedisDatabases,
|
||||
@@ -2780,12 +2842,14 @@ const ConnectionModal: React.FC<{
|
||||
// Use different API for Redis / JVM
|
||||
const isRedisType = values.type === "redis";
|
||||
const isJVMType = values.type === "jvm";
|
||||
const dbTestConfig =
|
||||
!isRedisType && !isJVMType ? buildRpcConnectionConfig(config as any) : config;
|
||||
const res = await withClientTimeout(
|
||||
isJVMType
|
||||
? TestJVMConnection(config as any)
|
||||
: isRedisType
|
||||
? RedisConnect(config as any)
|
||||
: TestConnection(config as any),
|
||||
: TestConnection(dbTestConfig as any),
|
||||
rpcTimeoutMs,
|
||||
`连接测试超时(>${timeoutSeconds} 秒),请检查网络/代理/SSH配置后重试`,
|
||||
);
|
||||
@@ -2798,7 +2862,7 @@ const ConnectionModal: React.FC<{
|
||||
} else if (!isJVMType) {
|
||||
// Other databases: fetch database list
|
||||
const dbRes = await withClientTimeout(
|
||||
DBGetDatabases(config as any),
|
||||
DBGetDatabases(dbTestConfig as any),
|
||||
rpcTimeoutMs,
|
||||
`连接成功但拉取数据库列表超时(>${timeoutSeconds} 秒)`,
|
||||
);
|
||||
@@ -3297,6 +3361,14 @@ const ConnectionModal: React.FC<{
|
||||
}
|
||||
|
||||
const keepPassword = !forPersist || savePassword;
|
||||
const normalizedConnectionParams = supportsConnectionParamsForType(type)
|
||||
? type === "oceanbase"
|
||||
? normalizeOceanBaseConnectionParamsText(
|
||||
mergedValues.connectionParams,
|
||||
selectedOceanBaseProtocol,
|
||||
)
|
||||
: normalizeConnectionParamsText(mergedValues.connectionParams)
|
||||
: "";
|
||||
|
||||
return {
|
||||
type: mergedValues.type,
|
||||
@@ -3318,9 +3390,7 @@ const ConnectionModal: React.FC<{
|
||||
httpTunnel: httpTunnelConfig,
|
||||
driver: mergedValues.driver,
|
||||
dsn: mergedValues.dsn,
|
||||
connectionParams: supportsConnectionParamsForType(type)
|
||||
? normalizeConnectionParamsText(mergedValues.connectionParams)
|
||||
: "",
|
||||
connectionParams: normalizedConnectionParams,
|
||||
timeout: Number(mergedValues.timeout || 30),
|
||||
redisDB: Number.isFinite(Number(mergedValues.redisDB))
|
||||
? Math.max(0, Math.min(15, Math.trunc(Number(mergedValues.redisDB))))
|
||||
|
||||
@@ -91,6 +91,20 @@ type DriverStatusSnapshot = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
const buildConnectionReloadSignature = (conn?: SavedConnection | null): string => {
|
||||
if (!conn) return '';
|
||||
return JSON.stringify({
|
||||
config: conn.config || {},
|
||||
includeDatabases: conn.includeDatabases || [],
|
||||
includeRedisDatabases: conn.includeRedisDatabases || [],
|
||||
});
|
||||
};
|
||||
|
||||
const isConnectionTreeKey = (key: React.Key, connectionId: string): boolean => {
|
||||
const text = String(key);
|
||||
return text === connectionId || text.startsWith(`${connectionId}-`);
|
||||
};
|
||||
|
||||
const DRIVER_STATUS_CACHE_TTL_MS = 30_000;
|
||||
|
||||
const normalizeDriverType = (value: string): string => {
|
||||
@@ -248,6 +262,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const driverStatusCacheRef = useRef<{ fetchedAt: number; items: Record<string, DriverStatusSnapshot> } | null>(null);
|
||||
const driverUpdateWarningKeysRef = useRef<Set<string>>(new Set());
|
||||
const connectionReloadSignaturesRef = useRef<Record<string, string>>({});
|
||||
const [contextMenu, setContextMenu] = useState<{ x: number, y: number, items: MenuProps['items'] } | null>(null);
|
||||
|
||||
// Virtual Scroll State
|
||||
@@ -375,6 +390,47 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
}, [autoFetchVisible, externalSQLDirectories, savedQueries]);
|
||||
|
||||
useEffect(() => {
|
||||
const previousSignatures = connectionReloadSignaturesRef.current;
|
||||
const nextSignatures: Record<string, string> = {};
|
||||
const staleConnectionIds = new Set<string>();
|
||||
|
||||
connections.forEach((conn) => {
|
||||
const signature = buildConnectionReloadSignature(conn);
|
||||
nextSignatures[conn.id] = signature;
|
||||
if (previousSignatures[conn.id] && previousSignatures[conn.id] !== signature) {
|
||||
staleConnectionIds.add(conn.id);
|
||||
}
|
||||
});
|
||||
connectionReloadSignaturesRef.current = nextSignatures;
|
||||
|
||||
if (staleConnectionIds.size > 0) {
|
||||
const staleIds = Array.from(staleConnectionIds);
|
||||
setLoadedKeys((prev) =>
|
||||
prev.filter((key) => !staleIds.some((id) => isConnectionTreeKey(key, id))),
|
||||
);
|
||||
setExpandedKeys((prev) =>
|
||||
prev.filter((key) => !staleIds.some((id) => isConnectionTreeKey(key, id))),
|
||||
);
|
||||
setConnectionStates((prev) => {
|
||||
const next = { ...prev };
|
||||
staleIds.forEach((id) => {
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (isConnectionTreeKey(key, id)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
return next;
|
||||
});
|
||||
staleIds.forEach((id) => {
|
||||
Array.from(loadingNodesRef.current).forEach((key) => {
|
||||
if (key === `dbs-${id}` || key.startsWith(`tables-${id}-`)) {
|
||||
loadingNodesRef.current.delete(key);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setTreeData((prev) => {
|
||||
const prevMap = new Map<string, TreeNode>();
|
||||
|
||||
@@ -395,6 +451,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
const existing = prevMap.get(conn.id);
|
||||
const iconType = resolveConnectionIconType(conn);
|
||||
const iconColor = resolveConnectionAccentColor(conn);
|
||||
const preserveChildren = existing && !staleConnectionIds.has(conn.id);
|
||||
return {
|
||||
title: conn.name,
|
||||
key: conn.id,
|
||||
@@ -402,7 +459,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
type: 'connection',
|
||||
dataRef: conn,
|
||||
isLeaf: false,
|
||||
children: existing?.children,
|
||||
children: preserveChildren ? existing.children : undefined,
|
||||
} as TreeNode;
|
||||
};
|
||||
|
||||
|
||||
@@ -294,6 +294,42 @@ describe('store appearance persistence', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('normalizes OceanBase protocol when updating a saved connection', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
useStore.getState().replaceConnections([
|
||||
{
|
||||
id: 'oceanbase-existing',
|
||||
name: 'OceanBase Existing',
|
||||
config: {
|
||||
id: 'oceanbase-existing',
|
||||
type: 'oceanbase',
|
||||
host: 'ob.local',
|
||||
port: 2881,
|
||||
user: 'root@test',
|
||||
connectionParams: 'protocol=mysql',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
useStore.getState().updateConnection({
|
||||
id: 'oceanbase-existing',
|
||||
name: 'OceanBase Existing',
|
||||
config: {
|
||||
id: 'oceanbase-existing',
|
||||
type: 'oceanbase',
|
||||
host: 'ob.local',
|
||||
port: 2881,
|
||||
user: 'sys@oracle001',
|
||||
connectionParams: 'protocol=oracle',
|
||||
},
|
||||
});
|
||||
|
||||
expect(useStore.getState().connections[0]?.config.oceanBaseProtocol).toBe(
|
||||
'oracle',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps legacy global proxy password during hydration until explicit cleanup', async () => {
|
||||
storage.setItem('lite-db-storage', JSON.stringify({
|
||||
state: {
|
||||
|
||||
@@ -1423,13 +1423,31 @@ export const useStore = create<AppState>()(
|
||||
jvmDiagnosticOutputs: {},
|
||||
|
||||
addConnection: (conn) =>
|
||||
set((state) => ({ connections: [...state.connections, conn] })),
|
||||
set((state) => {
|
||||
const sanitized = sanitizeSavedConnection(
|
||||
conn,
|
||||
state.connections.length,
|
||||
);
|
||||
if (!sanitized) {
|
||||
return { connections: state.connections };
|
||||
}
|
||||
return { connections: [...state.connections, sanitized] };
|
||||
}),
|
||||
updateConnection: (conn) =>
|
||||
set((state) => ({
|
||||
connections: state.connections.map((c) =>
|
||||
c.id === conn.id ? conn : c,
|
||||
),
|
||||
})),
|
||||
set((state) => {
|
||||
const sanitized = sanitizeSavedConnection(
|
||||
conn,
|
||||
state.connections.length,
|
||||
);
|
||||
if (!sanitized) {
|
||||
return { connections: state.connections };
|
||||
}
|
||||
return {
|
||||
connections: state.connections.map((c) =>
|
||||
c.id === conn.id ? sanitized : c,
|
||||
),
|
||||
};
|
||||
}),
|
||||
removeConnection: (id) =>
|
||||
set((state) => ({
|
||||
connections: state.connections.filter((c) => c.id !== id),
|
||||
|
||||
@@ -167,13 +167,14 @@ export function buildRpcConnectionConfig(
|
||||
httpTunnel: mergedHttpTunnel,
|
||||
};
|
||||
const rpcMerged = withOceanBaseProtocolParam(merged);
|
||||
const { oceanBaseProtocol: _oceanBaseProtocol, ...rpcPayload } = rpcMerged;
|
||||
|
||||
const baseId = toStringValue(config.id).trim() || toStringValue(overrides.id).trim() || undefined;
|
||||
const timeout = toOptionalInteger(rpcMerged.timeout, toOptionalInteger(config.timeout));
|
||||
const redisDB = toOptionalInteger(rpcMerged.redisDB, toOptionalInteger(config.redisDB));
|
||||
|
||||
const rpcConfig = new connection.ConnectionConfig({
|
||||
...rpcMerged,
|
||||
...rpcPayload,
|
||||
type: toStringValue(rpcMerged.type),
|
||||
host: toStringValue(rpcMerged.host),
|
||||
port: toOptionalInteger(rpcMerged.port, toOptionalInteger(config.port, 0)) ?? 0,
|
||||
|
||||
@@ -674,6 +674,7 @@ export namespace connection {
|
||||
redisDB?: number;
|
||||
uri?: string;
|
||||
clickHouseProtocol?: string;
|
||||
oceanBaseProtocol?: string;
|
||||
hosts?: string[];
|
||||
topology?: string;
|
||||
mysqlReplicaUser?: string;
|
||||
@@ -718,6 +719,7 @@ export namespace connection {
|
||||
this.redisDB = source["redisDB"];
|
||||
this.uri = source["uri"];
|
||||
this.clickHouseProtocol = source["clickHouseProtocol"];
|
||||
this.oceanBaseProtocol = source["oceanBaseProtocol"];
|
||||
this.hosts = source["hosts"];
|
||||
this.topology = source["topology"];
|
||||
this.mysqlReplicaUser = source["mysqlReplicaUser"];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user