mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-22 17:00:21 +08:00
🐛 fix(oceanbase): 修复 OceanBase 协议模式识别与缓存隔离
- 支持 MySQL/Oracle 租户协议在前后端统一解析 - 拒绝 Native 协议并避免误回退为 MySQL - 修复 Oracle 模式下元数据、DDL、SQL 方言识别 - 修复连接缓存键与实际协议解析优先级不一致问题 - 补充前后端协议解析与缓存隔离回归测试
This commit is contained in:
@@ -64,6 +64,13 @@ import { getCustomConnectionDsnValidationMessage } from "../utils/customConnecti
|
||||
import { mergeParsedUriValuesForForm } from "../utils/connectionUriMerge";
|
||||
import { buildRpcConnectionConfig } from "../utils/connectionRpcConfig";
|
||||
import { CUSTOM_CONNECTION_DRIVER_HELP } from "../utils/driverImportGuidance";
|
||||
import {
|
||||
describeUnsupportedOceanBaseProtocol,
|
||||
normalizeOceanBaseProtocol,
|
||||
OCEANBASE_PROTOCOL_PARAM_KEYS,
|
||||
resolveOceanBaseProtocolFromQueryText as resolveOceanBaseProtocolQueryText,
|
||||
type OceanBaseProtocol,
|
||||
} from "../utils/oceanBaseProtocol";
|
||||
import {
|
||||
applyNoAutoCapAttributes,
|
||||
noAutoCapInputProps,
|
||||
@@ -98,7 +105,7 @@ type ChoiceCardOption = {
|
||||
description?: string;
|
||||
};
|
||||
type ClickHouseProtocolChoice = "auto" | "http" | "native";
|
||||
type OceanBaseProtocolChoice = "mysql" | "oracle";
|
||||
type OceanBaseProtocolChoice = OceanBaseProtocol;
|
||||
const MAX_URI_LENGTH = 4096;
|
||||
const MAX_CONNECTION_PARAMS_LENGTH = 4096;
|
||||
const MAX_URI_HOSTS = 32;
|
||||
@@ -122,15 +129,6 @@ 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,
|
||||
): ClickHouseProtocolChoice => {
|
||||
@@ -144,10 +142,7 @@ const normalizeClickHouseProtocolValue = (
|
||||
const normalizeOceanBaseProtocolValue = (
|
||||
value: unknown,
|
||||
): OceanBaseProtocolChoice => {
|
||||
const text = String(value || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return text === "oracle" ? "oracle" : "mysql";
|
||||
return normalizeOceanBaseProtocol(value) || "mysql";
|
||||
};
|
||||
const resolveOceanBaseProtocolValue = (
|
||||
value: unknown,
|
||||
@@ -156,29 +151,12 @@ const resolveOceanBaseProtocolValue = (
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!text) return undefined;
|
||||
return ["oracle", "oracle-mode", "oracle_mode", "oboracle"].includes(text)
|
||||
? "oracle"
|
||||
: "mysql";
|
||||
return normalizeOceanBaseProtocol(text);
|
||||
};
|
||||
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;
|
||||
return resolveOceanBaseProtocolQueryText(value).protocol;
|
||||
};
|
||||
const resolveOceanBaseProtocolForConfig = (
|
||||
config: Partial<ConnectionConfig>,
|
||||
@@ -1179,7 +1157,12 @@ const ConnectionModal: React.FC<{
|
||||
rawParams: unknown,
|
||||
selectedProtocol: OceanBaseProtocolChoice,
|
||||
) => {
|
||||
const params = new URLSearchParams(normalizeConnectionParamsText(rawParams));
|
||||
const normalizedParamsText = normalizeConnectionParamsText(rawParams);
|
||||
const protocolFromParams = resolveOceanBaseProtocolQueryText(normalizedParamsText);
|
||||
if (protocolFromParams.unsupportedValue) {
|
||||
throw new Error(describeUnsupportedOceanBaseProtocol(protocolFromParams.unsupportedValue));
|
||||
}
|
||||
const params = new URLSearchParams(normalizedParamsText);
|
||||
for (const key of OCEANBASE_PROTOCOL_PARAM_KEYS) {
|
||||
params.delete(key);
|
||||
}
|
||||
@@ -4775,7 +4758,7 @@ const ConnectionModal: React.FC<{
|
||||
<Form.Item
|
||||
name="oceanBaseProtocol"
|
||||
label="OceanBase 协议"
|
||||
help="MySQL 租户选择 MySQL;Oracle 租户选择 Oracle。该选择会同时影响连接测试、浏览表结构和 SQL 方言。"
|
||||
help="MySQL 租户选择 MySQL;Oracle 租户选择 Oracle。OceanBase 租户兼容模式不包含 Native,该选择会同时影响连接测试、浏览表结构和 SQL 方言。"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Select
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TabData } from '../types';
|
||||
import { useStore } from '../store';
|
||||
import { DBQuery } from '../../wailsjs/go/app/App';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
import { normalizeOceanBaseProtocol } from '../utils/oceanBaseProtocol';
|
||||
|
||||
interface DefinitionViewerProps {
|
||||
tab: TabData;
|
||||
@@ -43,11 +44,11 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
|
||||
if (type === 'custom') {
|
||||
const driver = String(conn?.config?.driver || '').trim().toLowerCase();
|
||||
if (driver === 'diros' || driver === 'doris') return 'mysql';
|
||||
if (driver === 'oceanbase') return 'mysql';
|
||||
if (driver === 'oceanbase') return normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
|
||||
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
|
||||
return driver;
|
||||
}
|
||||
if (type === 'oceanbase' && String(conn?.config?.oceanBaseProtocol || '').trim().toLowerCase() === 'oracle') return 'oracle';
|
||||
if (type === 'oceanbase' && normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle') return 'oracle';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
|
||||
@@ -48,6 +48,7 @@ import FindInDatabaseModal from './FindInDatabaseModal';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
import { noAutoCapInputProps } from '../utils/inputAutoCap';
|
||||
import { normalizeSidebarViewName, resolveSidebarRuntimeDatabase } from '../utils/sidebarMetadata';
|
||||
import { normalizeOceanBaseProtocol } from '../utils/oceanBaseProtocol';
|
||||
import { resolveConnectionHostTokens } from '../utils/tabDisplay';
|
||||
import {
|
||||
findSidebarNodePathByKey,
|
||||
@@ -686,11 +687,11 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
if (type === 'custom') {
|
||||
const driver = String(conn?.config?.driver || '').trim().toLowerCase();
|
||||
if (driver === 'diros' || driver === 'doris') return 'mysql';
|
||||
if (driver === 'oceanbase') return 'mysql';
|
||||
if (driver === 'oceanbase') return normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
|
||||
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
|
||||
return driver;
|
||||
}
|
||||
if (type === 'oceanbase' && String(conn?.config?.oceanBaseProtocol || '').trim().toLowerCase() === 'oracle') return 'oracle';
|
||||
if (type === 'oceanbase' && normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle') return 'oracle';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type TableOverviewSortField,
|
||||
type TableOverviewSortOrder,
|
||||
} from '../utils/tableOverviewFilter';
|
||||
import { normalizeOceanBaseProtocol } from '../utils/oceanBaseProtocol';
|
||||
|
||||
interface TableOverviewProps {
|
||||
tab: TabData;
|
||||
@@ -57,11 +58,11 @@ const getMetadataDialect = (connType: string, driver?: string, oceanBaseProtocol
|
||||
if (type === 'custom') {
|
||||
const d = (driver || '').trim().toLowerCase();
|
||||
if (d === 'diros' || d === 'doris') return 'mysql';
|
||||
if (d === 'oceanbase') return 'mysql';
|
||||
if (d === 'oceanbase') return normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
|
||||
if (d === 'opengauss' || d === 'open_gauss' || d === 'open-gauss') return 'opengauss';
|
||||
return d;
|
||||
}
|
||||
if (type === 'oceanbase' && String(oceanBaseProtocol || '').trim().toLowerCase() === 'oracle') return 'oracle';
|
||||
if (type === 'oceanbase' && normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle') return 'oracle';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TabData } from '../types';
|
||||
import { useStore } from '../store';
|
||||
import { DBQuery } from '../../wailsjs/go/app/App';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
import { normalizeOceanBaseProtocol } from '../utils/oceanBaseProtocol';
|
||||
|
||||
interface TriggerViewerProps {
|
||||
tab: TabData;
|
||||
@@ -29,11 +30,11 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
|
||||
if (type === 'custom') {
|
||||
const driver = String(conn?.config?.driver || '').trim().toLowerCase();
|
||||
if (driver === 'diros' || driver === 'doris') return 'mysql';
|
||||
if (driver === 'oceanbase') return 'mysql';
|
||||
if (driver === 'oceanbase') return normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
|
||||
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
|
||||
return driver;
|
||||
}
|
||||
if (type === 'oceanbase' && String(conn?.config?.oceanBaseProtocol || '').trim().toLowerCase() === 'oracle') return 'oracle';
|
||||
if (type === 'oceanbase' && normalizeOceanBaseProtocol(conn?.config?.oceanBaseProtocol) === 'oracle') return 'oracle';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
|
||||
@@ -119,6 +119,36 @@ describe('store appearance persistence', () => {
|
||||
expect(useStore.getState().connections[0]?.config.password).toBe('secret');
|
||||
});
|
||||
|
||||
it('does not fail hydration when persisted OceanBase connection uses unsupported native protocol', async () => {
|
||||
storage.setItem('lite-db-storage', JSON.stringify({
|
||||
state: {
|
||||
connections: [
|
||||
{
|
||||
id: 'oceanbase-native',
|
||||
name: 'OceanBase Native',
|
||||
config: {
|
||||
id: 'oceanbase-native',
|
||||
type: 'oceanbase',
|
||||
host: 'ob.local',
|
||||
port: 2881,
|
||||
user: 'root@test',
|
||||
oceanBaseProtocol: 'mysql',
|
||||
connectionParams: 'protocol=native',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
version: 9,
|
||||
}));
|
||||
|
||||
const { useStore } = await importStore();
|
||||
const config = useStore.getState().connections[0]?.config;
|
||||
|
||||
expect(useStore.getState().connections).toHaveLength(1);
|
||||
expect(config?.connectionParams).toBe('protocol=native');
|
||||
expect(config?.oceanBaseProtocol).toBe('mysql');
|
||||
});
|
||||
|
||||
it('preserves JVM Arthas diagnostic config when replacing saved connections', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
@@ -294,6 +324,32 @@ describe('store appearance persistence', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps saved OceanBase native protocol loadable for connect-time rejection', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
expect(() => useStore.getState().replaceConnections([
|
||||
{
|
||||
id: 'oceanbase-native',
|
||||
name: 'OceanBase Native',
|
||||
config: {
|
||||
id: 'oceanbase-native',
|
||||
type: 'oceanbase',
|
||||
host: 'ob.local',
|
||||
port: 2881,
|
||||
user: 'root@test',
|
||||
oceanBaseProtocol: 'mysql',
|
||||
connectionParams: 'protocol=native',
|
||||
},
|
||||
},
|
||||
])).not.toThrow();
|
||||
expect(useStore.getState().connections[0]?.config.connectionParams).toBe(
|
||||
'protocol=native',
|
||||
);
|
||||
expect(useStore.getState().connections[0]?.config.oceanBaseProtocol).toBe(
|
||||
'mysql',
|
||||
);
|
||||
});
|
||||
|
||||
it('normalizes OceanBase protocol when updating a saved connection', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ import {
|
||||
sanitizeDataGridDisplaySettings,
|
||||
type DataGridDisplaySettings,
|
||||
} from "./utils/dataGridDisplay";
|
||||
import {
|
||||
normalizeOceanBaseProtocol,
|
||||
resolveOceanBaseProtocolFromConfig,
|
||||
resolveOceanBaseProtocolFromQueryText,
|
||||
} from "./utils/oceanBaseProtocol";
|
||||
|
||||
export interface AppearanceSettings extends DataGridDisplaySettings {
|
||||
enabled: boolean;
|
||||
@@ -78,68 +83,26 @@ const DEFAULT_GLOBAL_PROXY: GlobalProxyConfig = {
|
||||
password: "",
|
||||
hasPassword: false,
|
||||
};
|
||||
const OCEANBASE_PROTOCOL_PARAM_KEYS = [
|
||||
"protocol",
|
||||
"oceanBaseProtocol",
|
||||
"oceanbaseProtocol",
|
||||
"tenantMode",
|
||||
"compatMode",
|
||||
"mode",
|
||||
];
|
||||
const normalizeOceanBaseProtocol = (
|
||||
value: unknown,
|
||||
): "mysql" | "oracle" | undefined => {
|
||||
const normalized = String(value ?? "").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
return normalized === "oracle" ||
|
||||
normalized === "oracle-mode" ||
|
||||
normalized === "oracle_mode" ||
|
||||
normalized === "oboracle"
|
||||
? "oracle"
|
||||
: "mysql";
|
||||
};
|
||||
const resolveOceanBaseProtocolFromQueryText = (
|
||||
value: unknown,
|
||||
): "mysql" | "oracle" | 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 = normalizeOceanBaseProtocol(params.get(key));
|
||||
if (protocol) {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const resolveOceanBaseProtocol = (
|
||||
raw: Record<string, unknown>,
|
||||
normalizedConnectionParams: string,
|
||||
normalizedUri: string,
|
||||
): "mysql" | "oracle" => {
|
||||
if (Object.prototype.hasOwnProperty.call(raw, "oceanBaseProtocol")) {
|
||||
const explicitProtocol = normalizeOceanBaseProtocol(raw.oceanBaseProtocol);
|
||||
if (explicitProtocol) {
|
||||
return explicitProtocol;
|
||||
}
|
||||
const normalizedConfig = {
|
||||
...raw,
|
||||
connectionParams: normalizedConnectionParams,
|
||||
uri: normalizedUri,
|
||||
};
|
||||
try {
|
||||
return resolveOceanBaseProtocolFromConfig(normalizedConfig);
|
||||
} catch {
|
||||
return (
|
||||
normalizeOceanBaseProtocol(raw.oceanBaseProtocol) ||
|
||||
resolveOceanBaseProtocolFromQueryText(normalizedConnectionParams).protocol ||
|
||||
resolveOceanBaseProtocolFromQueryText(normalizedUri).protocol ||
|
||||
"mysql"
|
||||
);
|
||||
}
|
||||
return (
|
||||
resolveOceanBaseProtocolFromQueryText(normalizedConnectionParams) ||
|
||||
resolveOceanBaseProtocolFromQueryText(normalizedUri) ||
|
||||
"mysql"
|
||||
);
|
||||
};
|
||||
const SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"mysql",
|
||||
|
||||
@@ -299,7 +299,7 @@ export interface ConnectionConfig {
|
||||
redisDB?: number; // Redis database index (0-15)
|
||||
uri?: string; // Connection URI for copy/paste
|
||||
clickHouseProtocol?: "auto" | "http" | "native"; // ClickHouse connection protocol override
|
||||
oceanBaseProtocol?: "mysql" | "oracle"; // OceanBase tenant protocol
|
||||
oceanBaseProtocol?: "mysql" | "oracle"; // OceanBase tenant compatibility protocol
|
||||
hosts?: string[]; // Multi-host addresses: host:port
|
||||
topology?: "single" | "replica" | "cluster";
|
||||
mysqlReplicaUser?: string;
|
||||
|
||||
@@ -96,7 +96,7 @@ const CONNECTION_CONFIG_SECTION_COPY: Record<
|
||||
},
|
||||
oceanBaseProtocol: {
|
||||
title: 'OceanBase 协议',
|
||||
description: '明确选择 MySQL 租户协议或 Oracle 租户协议。',
|
||||
description: '明确选择 MySQL 或 Oracle 租户兼容协议。',
|
||||
},
|
||||
mongoDiscovery: {
|
||||
title: 'MongoDB 寻址',
|
||||
|
||||
@@ -110,6 +110,31 @@ describe('buildRpcConnectionConfig', () => {
|
||||
expect(result.connectionParams).toBe('protocol=mysql');
|
||||
});
|
||||
|
||||
it('rejects unsupported OceanBase native protocol instead of falling back to MySQL', () => {
|
||||
expect(() => buildRpcConnectionConfig({
|
||||
id: 'conn-oceanbase-native',
|
||||
type: 'oceanbase',
|
||||
host: 'ob.local',
|
||||
port: 2881,
|
||||
user: 'root@test',
|
||||
database: 'app',
|
||||
connectionParams: 'protocol=native',
|
||||
} as any)).toThrow(/不支持.*native/);
|
||||
});
|
||||
|
||||
it('rejects unsupported OceanBase protocol even when form protocol is explicit MySQL', () => {
|
||||
expect(() => buildRpcConnectionConfig({
|
||||
id: 'conn-oceanbase-native-masked',
|
||||
type: 'oceanbase',
|
||||
host: 'ob.local',
|
||||
port: 2881,
|
||||
user: 'root@test',
|
||||
database: 'app',
|
||||
oceanBaseProtocol: 'mysql',
|
||||
connectionParams: 'protocol=native',
|
||||
} as any)).toThrow(/不支持.*native/);
|
||||
});
|
||||
|
||||
it('preserves extra connection params for RPC calls', () => {
|
||||
const result = buildRpcConnectionConfig({
|
||||
id: 'conn-mysql',
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { connection } from '../../wailsjs/go/models';
|
||||
import {
|
||||
OCEANBASE_PROTOCOL_PARAM_KEYS,
|
||||
resolveOceanBaseProtocolFromConfig,
|
||||
} from './oceanBaseProtocol';
|
||||
|
||||
export type RpcConnectionConfig = connection.ConnectionConfig & { id?: string };
|
||||
type ConnectionConfigInput = {
|
||||
@@ -11,15 +15,6 @@ type ConnectionConfigInput = {
|
||||
type SSHConfigInput = Record<string, any>;
|
||||
type ProxyConfigInput = Record<string, any>;
|
||||
type HttpTunnelConfigInput = Record<string, any>;
|
||||
type OceanBaseProtocol = 'mysql' | 'oracle';
|
||||
const OCEANBASE_PROTOCOL_PARAM_KEYS = [
|
||||
'protocol',
|
||||
'oceanBaseProtocol',
|
||||
'oceanbaseProtocol',
|
||||
'tenantMode',
|
||||
'compatMode',
|
||||
'mode',
|
||||
];
|
||||
|
||||
const toStringValue = (value: unknown, fallback = ''): string => {
|
||||
if (typeof value === 'string') {
|
||||
@@ -79,59 +74,12 @@ const normalizeHttpTunnelConfig = (value: unknown): connection.HTTPTunnelConfig
|
||||
});
|
||||
};
|
||||
|
||||
const normalizeOceanBaseProtocol = (value: unknown): OceanBaseProtocol | undefined => {
|
||||
const normalized = toStringValue(value).trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
return normalized === 'oracle' || normalized === 'oracle-mode' || normalized === 'oracle_mode' || normalized === 'oboracle'
|
||||
? 'oracle'
|
||||
: 'mysql';
|
||||
};
|
||||
|
||||
const resolveOceanBaseProtocolFromQueryText = (raw: unknown): OceanBaseProtocol | undefined => {
|
||||
let text = toStringValue(raw).trim();
|
||||
if (!text) {
|
||||
return undefined;
|
||||
}
|
||||
const queryStart = text.indexOf('?');
|
||||
if (queryStart >= 0) {
|
||||
text = text.slice(queryStart + 1);
|
||||
}
|
||||
const hashStart = text.indexOf('#');
|
||||
if (hashStart >= 0) {
|
||||
text = text.slice(0, hashStart);
|
||||
}
|
||||
const params = new URLSearchParams(text.replace(/^[?&]+/, ''));
|
||||
for (const key of OCEANBASE_PROTOCOL_PARAM_KEYS) {
|
||||
const protocol = normalizeOceanBaseProtocol(params.get(key));
|
||||
if (protocol) {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const resolveOceanBaseProtocol = (config: ConnectionConfigInput): OceanBaseProtocol => {
|
||||
if (Object.prototype.hasOwnProperty.call(config, 'oceanBaseProtocol')) {
|
||||
const explicitProtocol = normalizeOceanBaseProtocol(config.oceanBaseProtocol);
|
||||
if (explicitProtocol) {
|
||||
return explicitProtocol;
|
||||
}
|
||||
}
|
||||
return (
|
||||
resolveOceanBaseProtocolFromQueryText(config.connectionParams) ||
|
||||
resolveOceanBaseProtocolFromQueryText(config.uri) ||
|
||||
'mysql'
|
||||
);
|
||||
};
|
||||
|
||||
const withOceanBaseProtocolParam = (config: ConnectionConfigInput): ConnectionConfigInput => {
|
||||
const type = toStringValue(config.type).trim().toLowerCase();
|
||||
if (type !== 'oceanbase') {
|
||||
return config;
|
||||
}
|
||||
const selectedProtocol = resolveOceanBaseProtocol(config);
|
||||
const selectedProtocol = resolveOceanBaseProtocolFromConfig(config);
|
||||
const params = new URLSearchParams(toStringValue(config.connectionParams));
|
||||
for (const key of OCEANBASE_PROTOCOL_PARAM_KEYS) {
|
||||
params.delete(key);
|
||||
|
||||
@@ -40,4 +40,16 @@ describe('dataSourceCapabilities', () => {
|
||||
supportsApproximateTableCount: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats custom OceanBase Oracle driver as Oracle capabilities', () => {
|
||||
expect(getDataSourceCapabilities({
|
||||
type: 'custom',
|
||||
driver: 'oceanbase',
|
||||
oceanBaseProtocol: 'oracle',
|
||||
})).toMatchObject({
|
||||
type: 'oracle',
|
||||
preferManualTotalCount: true,
|
||||
supportsApproximateTableCount: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ConnectionConfig } from '../types';
|
||||
import { normalizeOceanBaseProtocol } from './oceanBaseProtocol';
|
||||
|
||||
type ConnectionLike = Pick<ConnectionConfig, 'type' | 'driver' | 'oceanBaseProtocol'> | null | undefined;
|
||||
|
||||
@@ -25,9 +26,12 @@ export const resolveDataSourceType = (config: ConnectionLike): string => {
|
||||
const type = normalizeDataSourceToken(String(config.type || ''));
|
||||
if (type === 'custom') {
|
||||
const driver = normalizeDataSourceToken(String(config.driver || ''));
|
||||
if (driver === 'oceanbase' && normalizeOceanBaseProtocol(config.oceanBaseProtocol) === 'oracle') {
|
||||
return 'oracle';
|
||||
}
|
||||
return driver || 'custom';
|
||||
}
|
||||
if (type === 'oceanbase' && String(config.oceanBaseProtocol || '').trim().toLowerCase() === 'oracle') {
|
||||
if (type === 'oceanbase' && normalizeOceanBaseProtocol(config.oceanBaseProtocol) === 'oracle') {
|
||||
return 'oracle';
|
||||
}
|
||||
return type;
|
||||
|
||||
109
frontend/src/utils/oceanBaseProtocol.ts
Normal file
109
frontend/src/utils/oceanBaseProtocol.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
export type OceanBaseProtocol = 'mysql' | 'oracle';
|
||||
|
||||
export const OCEANBASE_PROTOCOL_PARAM_KEYS = [
|
||||
'protocol',
|
||||
'oceanBaseProtocol',
|
||||
'oceanbaseProtocol',
|
||||
'tenantMode',
|
||||
'compatMode',
|
||||
'mode',
|
||||
];
|
||||
|
||||
type OceanBaseProtocolResolution = {
|
||||
protocol?: OceanBaseProtocol;
|
||||
unsupportedValue?: string;
|
||||
unsupportedKey?: string;
|
||||
};
|
||||
|
||||
const normalizeToken = (value: unknown): string => String(value ?? '').trim().toLowerCase();
|
||||
|
||||
export const normalizeOceanBaseProtocol = (value: unknown): OceanBaseProtocol | undefined => {
|
||||
const normalized = normalizeToken(value);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (normalized === 'oracle' || normalized === 'oracle-mode' || normalized === 'oracle_mode' || normalized === 'oboracle') {
|
||||
return 'oracle';
|
||||
}
|
||||
if (normalized === 'mysql' || normalized === 'mysql-compatible' || normalized === 'mysql_compatible' || normalized === 'mysql-mode' || normalized === 'mysql_mode' || normalized === 'obmysql') {
|
||||
return 'mysql';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const isUnsupportedOceanBaseProtocolValue = (value: unknown): boolean => {
|
||||
const normalized = normalizeToken(value);
|
||||
return normalized !== '' && !normalizeOceanBaseProtocol(normalized);
|
||||
};
|
||||
|
||||
export const describeUnsupportedOceanBaseProtocol = (value: unknown): string => {
|
||||
const raw = String(value ?? '').trim();
|
||||
const label = raw ? ` "${raw}"` : '';
|
||||
return `OceanBase 当前仅支持 MySQL/Oracle 租户协议,不支持${label};请改为 MySQL 或 Oracle。`;
|
||||
};
|
||||
|
||||
export const resolveOceanBaseProtocolFromQueryText = (raw: unknown): OceanBaseProtocolResolution => {
|
||||
let text = String(raw ?? '').trim();
|
||||
if (!text) {
|
||||
return {};
|
||||
}
|
||||
const queryStart = text.indexOf('?');
|
||||
if (queryStart >= 0) {
|
||||
text = text.slice(queryStart + 1);
|
||||
}
|
||||
const hashStart = text.indexOf('#');
|
||||
if (hashStart >= 0) {
|
||||
text = text.slice(0, hashStart);
|
||||
}
|
||||
const params = new URLSearchParams(text.replace(/^[?&]+/, ''));
|
||||
for (const key of OCEANBASE_PROTOCOL_PARAM_KEYS) {
|
||||
const value = params.get(key);
|
||||
if (value == null || String(value).trim() === '') {
|
||||
continue;
|
||||
}
|
||||
const protocol = normalizeOceanBaseProtocol(value);
|
||||
if (protocol) {
|
||||
return { protocol };
|
||||
}
|
||||
return { unsupportedValue: value, unsupportedKey: key };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const resolveOceanBaseProtocolFromConfig = (config: Record<string, unknown>): OceanBaseProtocol => {
|
||||
const paramsProtocol = resolveOceanBaseProtocolFromQueryText(config.connectionParams);
|
||||
const uriProtocol = resolveOceanBaseProtocolFromQueryText(config.uri);
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(config, 'oceanBaseProtocol')) {
|
||||
const value = config.oceanBaseProtocol;
|
||||
const protocol = normalizeOceanBaseProtocol(value);
|
||||
if (isUnsupportedOceanBaseProtocolValue(value)) {
|
||||
throw new Error(describeUnsupportedOceanBaseProtocol(value));
|
||||
}
|
||||
if (paramsProtocol.unsupportedValue) {
|
||||
throw new Error(describeUnsupportedOceanBaseProtocol(paramsProtocol.unsupportedValue));
|
||||
}
|
||||
if (uriProtocol.unsupportedValue) {
|
||||
throw new Error(describeUnsupportedOceanBaseProtocol(uriProtocol.unsupportedValue));
|
||||
}
|
||||
if (protocol) {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsProtocol.unsupportedValue) {
|
||||
throw new Error(describeUnsupportedOceanBaseProtocol(paramsProtocol.unsupportedValue));
|
||||
}
|
||||
if (paramsProtocol.protocol) {
|
||||
return paramsProtocol.protocol;
|
||||
}
|
||||
|
||||
if (uriProtocol.unsupportedValue) {
|
||||
throw new Error(describeUnsupportedOceanBaseProtocol(uriProtocol.unsupportedValue));
|
||||
}
|
||||
return uriProtocol.protocol || 'mysql';
|
||||
};
|
||||
|
||||
export const resolveOceanBaseProtocolForDialect = (value: unknown): OceanBaseProtocol => (
|
||||
normalizeOceanBaseProtocol(value) || 'mysql'
|
||||
);
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeOceanBaseProtocol } from './oceanBaseProtocol';
|
||||
|
||||
const splitQualifiedName = (qualifiedName: string): { schemaName: string; objectName: string } => {
|
||||
const raw = String(qualifiedName || '').trim();
|
||||
if (!raw) return { schemaName: '', objectName: '' };
|
||||
@@ -18,12 +20,14 @@ const normalizeSidebarConnectionDialect = (type: string, driver: string, oceanBa
|
||||
if (normalizedDriver === 'postgresql' || normalizedDriver === 'postgres' || normalizedDriver === 'pg') return 'postgres';
|
||||
if (normalizedDriver === 'opengauss' || normalizedDriver === 'open_gauss' || normalizedDriver === 'open-gauss') return 'opengauss';
|
||||
if (normalizedDriver === 'dameng' || normalizedDriver === 'dm' || normalizedDriver === 'dm8') return 'dm';
|
||||
if (normalizedDriver === 'oceanbase') return 'mysql';
|
||||
if (normalizedDriver === 'oceanbase') {
|
||||
return normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
|
||||
}
|
||||
if (normalizedDriver.includes('oracle')) return 'oracle';
|
||||
return normalizedDriver;
|
||||
}
|
||||
if (normalizedType === 'oceanbase') {
|
||||
return String(oceanBaseProtocol || '').trim().toLowerCase() === 'oracle' ? 'oracle' : 'mysql';
|
||||
return normalizeOceanBaseProtocol(oceanBaseProtocol) === 'oracle' ? 'oracle' : 'mysql';
|
||||
}
|
||||
if (normalizedType === 'open_gauss' || normalizedType === 'open-gauss') return 'opengauss';
|
||||
if (normalizedType === 'dameng') return 'dm';
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('sqlDialect', () => {
|
||||
expect(resolveSqlDialect('custom', 'mariadb')).toBe('mariadb');
|
||||
expect(resolveSqlDialect('custom', 'open_gauss')).toBe('opengauss');
|
||||
expect(resolveSqlDialect('OceanBase', '', { oceanBaseProtocol: 'oracle' })).toBe('oracle');
|
||||
expect(resolveSqlDialect('custom', 'oceanbase', { oceanBaseProtocol: 'oracle' })).toBe('oracle');
|
||||
expect(isMysqlFamilyDialect('mariadb')).toBe(true);
|
||||
expect(isMysqlFamilyDialect('oceanbase')).toBe(true);
|
||||
expect(isMysqlFamilyDialect('oracle')).toBe(false);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { resolveOceanBaseProtocolForDialect } from './oceanBaseProtocol';
|
||||
|
||||
export type ColumnTypeOption = { value: string };
|
||||
|
||||
export type SqlFunctionCompletion = {
|
||||
@@ -34,9 +36,7 @@ const optionValues = (values: string[]): ColumnTypeOption[] => values.map((value
|
||||
|
||||
const normalizeRawDialect = (value: string): string => String(value || '').trim().toLowerCase();
|
||||
|
||||
export const normalizeOceanBaseSqlProtocol = (value: unknown): 'mysql' | 'oracle' => (
|
||||
String(value || '').trim().toLowerCase() === 'oracle' ? 'oracle' : 'mysql'
|
||||
);
|
||||
export const normalizeOceanBaseSqlProtocol = resolveOceanBaseProtocolForDialect;
|
||||
|
||||
export const resolveSqlDialect = (
|
||||
rawType: string,
|
||||
|
||||
@@ -211,3 +211,50 @@ func TestGetCacheKey_OceanBaseProtocolParamWinsOverAliases(t *testing.T) {
|
||||
t.Fatalf("expected explicit protocol=mysql to win over alias, got %s vs %s", left, right)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCacheKey_OceanBaseExplicitProtocolOverridesConnectionParams(t *testing.T) {
|
||||
base := connection.ConnectionConfig{
|
||||
Type: "oceanbase",
|
||||
Host: "ob.local",
|
||||
Port: 2881,
|
||||
User: "root@test",
|
||||
Database: "app",
|
||||
ConnectionParams: "connectTimeout=10",
|
||||
}
|
||||
modified := base
|
||||
modified.OceanBaseProtocol = "mysql"
|
||||
modified.ConnectionParams = "protocol=oracle&connectTimeout=10"
|
||||
|
||||
left := getCacheKey(base)
|
||||
right := getCacheKey(modified)
|
||||
if left != right {
|
||||
t.Fatalf("expected explicit OceanBase protocol=mysql to override params protocol=oracle, got %s vs %s", left, right)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCacheKey_KeepOceanBaseUnsupportedProtocolIsolation(t *testing.T) {
|
||||
base := connection.ConnectionConfig{
|
||||
Type: "oceanbase",
|
||||
Host: "ob.local",
|
||||
Port: 2881,
|
||||
User: "root@test",
|
||||
Database: "app",
|
||||
ConnectionParams: "protocol=mysql",
|
||||
}
|
||||
modified := base
|
||||
modified.ConnectionParams = "protocol=native"
|
||||
|
||||
left := getCacheKey(base)
|
||||
right := getCacheKey(modified)
|
||||
if left == right {
|
||||
t.Fatalf("expected unsupported OceanBase protocol to stay isolated from MySQL cache key")
|
||||
}
|
||||
|
||||
masked := base
|
||||
masked.OceanBaseProtocol = "mysql"
|
||||
masked.ConnectionParams = "protocol=native"
|
||||
|
||||
if left == getCacheKey(masked) {
|
||||
t.Fatalf("expected unsupported OceanBase params protocol to stay isolated even with explicit mysql")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,29 +8,53 @@ import (
|
||||
)
|
||||
|
||||
func normalizeOceanBaseProtocolForApp(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
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":
|
||||
case "mysql", "mysql-compatible", "mysql_compatible", "mysql-mode", "mysql_mode", "obmysql":
|
||||
return "mysql"
|
||||
default:
|
||||
return "mysql"
|
||||
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 != "" {
|
||||
return normalizeOceanBaseProtocolForApp(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"
|
||||
}
|
||||
|
||||
@@ -57,7 +81,7 @@ func resolveOceanBaseProtocolParam(raw string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeOceanBaseConnectionParamsForCache(raw string) string {
|
||||
func stripOceanBaseConnectionParamsForCache(raw string) string {
|
||||
text := strings.TrimSpace(raw)
|
||||
if text == "" {
|
||||
return ""
|
||||
@@ -69,26 +93,40 @@ func normalizeOceanBaseConnectionParamsForCache(raw string) string {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
protocol := resolveOceanBaseProtocolParam(raw)
|
||||
for _, key := range []string{"protocol", "oceanBaseProtocol", "oceanbaseProtocol", "tenantMode", "compatMode", "mode"} {
|
||||
values.Del(key)
|
||||
}
|
||||
if strings.EqualFold(protocol, "oracle") {
|
||||
values.Set("protocol", "oracle")
|
||||
}
|
||||
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 {
|
||||
normalized := normalizeOceanBaseConnectionParamsForCache(raw)
|
||||
if !strings.EqualFold(protocol, "oracle") {
|
||||
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", "oracle")
|
||||
values.Set("protocol", resolvedProtocol)
|
||||
return values.Encode()
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +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
|
||||
OceanBaseProtocol string `json:"oceanBaseProtocol,omitempty"` // OceanBase tenant compatibility protocol: 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
|
||||
|
||||
@@ -151,36 +151,62 @@ func normalizeOceanBaseProtocol(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case oceanBaseProtocolOracle, "oracle-mode", "oracle_mode", "oboracle":
|
||||
return oceanBaseProtocolOracle
|
||||
case oceanBaseProtocolMySQL, "mysql-compatible", "mysql_compatible", "mysql-mode", "mysql_mode", "":
|
||||
case oceanBaseProtocolMySQL, "mysql-compatible", "mysql_compatible", "mysql-mode", "mysql_mode", "obmysql", "":
|
||||
return oceanBaseProtocolMySQL
|
||||
default:
|
||||
return oceanBaseProtocolMySQL
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func resolveOceanBaseProtocolFromValues(values url.Values) string {
|
||||
func unsupportedOceanBaseProtocolError(raw string) error {
|
||||
return fmt.Errorf("OceanBase 当前仅支持 MySQL/Oracle 租户协议,不支持 %q;请改为 MySQL 或 Oracle", strings.TrimSpace(raw))
|
||||
}
|
||||
|
||||
func resolveOceanBaseProtocolFromValues(values url.Values) (string, error) {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
for _, key := range []string{"protocol", "oceanBaseProtocol", "oceanbaseProtocol", "tenantMode", "compatMode", "mode"} {
|
||||
if value := strings.TrimSpace(values.Get(key)); value != "" {
|
||||
return normalizeOceanBaseProtocol(value)
|
||||
protocol := normalizeOceanBaseProtocol(value)
|
||||
if protocol == "" {
|
||||
return "", unsupportedOceanBaseProtocolError(value)
|
||||
}
|
||||
return protocol, nil
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func resolveOceanBaseProtocol(config connection.ConnectionConfig) string {
|
||||
func resolveOceanBaseProtocol(config connection.ConnectionConfig) (string, error) {
|
||||
explicitProtocol := ""
|
||||
if explicit := strings.TrimSpace(config.OceanBaseProtocol); explicit != "" {
|
||||
return normalizeOceanBaseProtocol(explicit)
|
||||
protocol := normalizeOceanBaseProtocol(explicit)
|
||||
if protocol == "" {
|
||||
return "", unsupportedOceanBaseProtocolError(explicit)
|
||||
}
|
||||
explicitProtocol = protocol
|
||||
}
|
||||
if protocol := resolveOceanBaseProtocolFromValues(connectionParamsFromText(config.ConnectionParams)); protocol != "" {
|
||||
return protocol
|
||||
if protocol, err := resolveOceanBaseProtocolFromValues(connectionParamsFromText(config.ConnectionParams)); err != nil {
|
||||
return "", err
|
||||
} else if protocol != "" {
|
||||
if explicitProtocol != "" {
|
||||
return explicitProtocol, nil
|
||||
}
|
||||
return protocol, nil
|
||||
}
|
||||
if protocol := resolveOceanBaseProtocolFromValues(connectionParamsFromURI(config.URI, "oceanbase", "mysql")); protocol != "" {
|
||||
return protocol
|
||||
if protocol, err := resolveOceanBaseProtocolFromValues(connectionParamsFromURI(config.URI, "oceanbase", "mysql")); err != nil {
|
||||
return "", err
|
||||
} else if protocol != "" {
|
||||
if explicitProtocol != "" {
|
||||
return explicitProtocol, nil
|
||||
}
|
||||
return protocol, nil
|
||||
}
|
||||
return oceanBaseProtocolMySQL
|
||||
if explicitProtocol != "" {
|
||||
return explicitProtocol, nil
|
||||
}
|
||||
return oceanBaseProtocolMySQL, nil
|
||||
}
|
||||
|
||||
func stripOceanBaseProtocolParams(raw string) string {
|
||||
@@ -256,7 +282,10 @@ func (o *OceanBaseDB) Connect(config connection.ConnectionConfig) error {
|
||||
o.oracle = nil
|
||||
o.protocol = oceanBaseProtocolMySQL
|
||||
appliedConfig := applyOceanBaseURI(config)
|
||||
protocol := resolveOceanBaseProtocol(appliedConfig)
|
||||
protocol, err := resolveOceanBaseProtocol(appliedConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runConfig := withoutOceanBaseProtocolParams(appliedConfig)
|
||||
if protocol == oceanBaseProtocolOracle {
|
||||
logger.Infof("OceanBase 使用 Oracle 协议连接:地址=%s:%d 用户=%s", runConfig.Host, runConfig.Port, runConfig.User)
|
||||
|
||||
@@ -79,13 +79,52 @@ func TestResolveOceanBaseProtocol(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := resolveOceanBaseProtocol(tt.config); got != tt.want {
|
||||
got, err := resolveOceanBaseProtocol(tt.config)
|
||||
if err != nil {
|
||||
t.Fatalf("resolveOceanBaseProtocol() unexpected error: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("resolveOceanBaseProtocol() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveOceanBaseProtocolRejectsUnsupportedNative(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config connection.ConnectionConfig
|
||||
}{
|
||||
{
|
||||
name: "params native",
|
||||
config: connection.ConnectionConfig{
|
||||
Type: "oceanbase",
|
||||
ConnectionParams: "protocol=native",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "explicit mysql does not mask params native",
|
||||
config: connection.ConnectionConfig{
|
||||
Type: "oceanbase",
|
||||
OceanBaseProtocol: "mysql",
|
||||
ConnectionParams: "protocol=native",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := resolveOceanBaseProtocol(tt.config)
|
||||
if err == nil || !strings.Contains(err.Error(), "不支持") {
|
||||
t.Fatalf("expected unsupported protocol error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithoutOceanBaseProtocolParamsStripsDriverMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user