🐛 fix(oceanbase): 修复 OceanBase 协议模式识别与缓存隔离

- 支持 MySQL/Oracle 租户协议在前后端统一解析
- 拒绝 Native 协议并避免误回退为 MySQL
- 修复 Oracle 模式下元数据、DDL、SQL 方言识别
- 修复连接缓存键与实际协议解析优先级不一致问题
- 补充前后端协议解析与缓存隔离回归测试
This commit is contained in:
Syngnat
2026-05-13 22:51:01 +08:00
parent 01eb2c25e0
commit f8abe60dc2
22 changed files with 454 additions and 192 deletions

View File

@@ -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 租户选择 MySQLOracle 租户选择 Oracle。该选择会同时影响连接测试、浏览表结构和 SQL 方言。"
help="MySQL 租户选择 MySQLOracle 租户选择 Oracle。OceanBase 租户兼容模式不包含 Native该选择会同时影响连接测试、浏览表结构和 SQL 方言。"
style={{ marginBottom: 0 }}
>
<Select

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",

View File

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

View File

@@ -96,7 +96,7 @@ const CONNECTION_CONFIG_SECTION_COPY: Record<
},
oceanBaseProtocol: {
title: 'OceanBase 协议',
description: '明确选择 MySQL 租户协议或 Oracle 租户协议。',
description: '明确选择 MySQL 或 Oracle 租户兼容协议。',
},
mongoDiscovery: {
title: 'MongoDB 寻址',

View File

@@ -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',

View File

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

View File

@@ -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,
});
});
});

View File

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

View 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'
);

View File

@@ -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';

View File

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

View File

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