🐛 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

@@ -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))))