feat(driver): 新增 OceanBase 与 OpenGauss Agent 数据源

- 数据源支持:新增 OceanBase 与 OpenGauss optional driver-agent 实现
- 连接适配:复用 MySQL/PostgreSQL 兼容链路并补齐查询、DDL、同步能力
- 前端入口:补充连接表单、侧边栏、图标、SQL 方言和危险操作识别
- 驱动管理:更新 driver manifest、安装提示和 revision 自动生成链路
- 构建发布:支持多平台 driver-agent 打包并优化 release 构建失败提示
This commit is contained in:
Syngnat
2026-04-30 13:13:01 +08:00
parent c92959f3e8
commit 7fd6d78c83
55 changed files with 859 additions and 178 deletions

View File

@@ -68,6 +68,7 @@ describe('connectionModalPresentation', () => {
const allTypes = [
'mysql',
'mariadb',
'oceanbase',
'doris',
'diros',
'sphinx',
@@ -81,6 +82,7 @@ describe('connectionModalPresentation', () => {
'kingbase',
'highgo',
'vastbase',
'opengauss',
'mongodb',
'redis',
'tdengine',

View File

@@ -55,6 +55,7 @@ type ConnectionConfigSectionCopy = {
const mysqlCompatibleTypes = new Set([
'mysql',
'mariadb',
'oceanbase',
'doris',
'diros',
'sphinx',
@@ -64,6 +65,7 @@ const postgresCompatibleTypes = new Set([
'kingbase',
'highgo',
'vastbase',
'opengauss',
]);
const fileDatabaseTypes = new Set(['sqlite', 'duckdb']);

View File

@@ -9,6 +9,10 @@ const normalizeDataSourceToken = (raw: string): string => {
return 'diros';
case 'postgresql':
return 'postgres';
case 'opengauss':
case 'open_gauss':
case 'open-gauss':
return 'opengauss';
case 'dm':
return 'dameng';
default:
@@ -29,12 +33,14 @@ export const resolveDataSourceType = (config: ConnectionLike): string => {
const SQL_QUERY_EXPORT_TYPES = new Set([
'mysql',
'mariadb',
'oceanbase',
'diros',
'sphinx',
'postgres',
'kingbase',
'highgo',
'vastbase',
'opengauss',
'sqlserver',
'sqlite',
'duckdb',
@@ -47,12 +53,14 @@ const SQL_QUERY_EXPORT_TYPES = new Set([
const COPY_INSERT_TYPES = new Set([
'mysql',
'mariadb',
'oceanbase',
'diros',
'sphinx',
'postgres',
'kingbase',
'highgo',
'vastbase',
'opengauss',
'sqlserver',
'sqlite',
'duckdb',

View File

@@ -17,6 +17,8 @@ describe('driver import guidance', () => {
it('documents custom driver aliases for kingbase and related fallbacks', () => {
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('kingbase8');
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('pgx');
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('open_gauss');
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('oceanbase');
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('JDBC Jar');
});
});

View File

@@ -7,4 +7,4 @@ export const DRIVER_LOCAL_IMPORT_SINGLE_FILE_HELP =
'行内“导入驱动包”仅用于单个驱动文件/总包(如 `mariadb-driver-agent`、`mariadb-driver-agent.exe`、`GoNavi-DriverAgents.zip`),不支持直接导入 JDBC Jar批量导入请使用上方“导入驱动目录”。';
export const CUSTOM_CONNECTION_DRIVER_HELP =
'已支持: mysql, postgres, sqlite, oracle, dm, kingbase别名支持 postgresql/pgx、dm8、kingbase8/kingbasees/kingbasev8。当前不支持通过 JDBC Jar 扩展驱动。';
'已支持: mysql, oceanbase, postgres, opengauss, sqlite, oracle, dm, kingbase别名支持 postgresql/pgx、open_gauss/open-gauss、dm8、kingbase8/kingbasees/kingbasev8。当前不支持通过 JDBC Jar 扩展驱动。';

View File

@@ -6,6 +6,7 @@ describe('applyQueryAutoLimit', () => {
const limitDialects = [
'mysql',
'mariadb',
'oceanbase',
'diros',
'doris',
'sphinx',
@@ -15,6 +16,7 @@ describe('applyQueryAutoLimit', () => {
'kingbase8',
'highgo',
'vastbase',
'opengauss',
'sqlite',
'sqlite3',
'duckdb',

View File

@@ -16,10 +16,14 @@ const normalizeSidebarConnectionDialect = (type: string, driver: string): string
if (normalizedType === 'custom') {
const normalizedDriver = String(driver || '').trim().toLowerCase();
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.includes('oracle')) return 'oracle';
return normalizedDriver;
}
if (normalizedType === 'oceanbase') return 'mysql';
if (normalizedType === 'open_gauss' || normalizedType === 'open-gauss') return 'opengauss';
if (normalizedType === 'dameng') return 'dm';
return normalizedType;
};

View File

@@ -37,12 +37,12 @@ export const quoteIdentPart = (dbType: string, ident: string) => {
if (!raw) return raw;
const dbTypeLower = (dbType || '').toLowerCase();
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'diros' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'clickhouse') {
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'clickhouse') {
return `\`${raw.replace(/`/g, '``')}\``;
}
// 对于 KingBase/PostgreSQL只在必要时加引号
if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres') {
if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres' || dbTypeLower === 'opengauss') {
if (needsQuote(raw)) {
return `"${raw.replace(/"/g, '""')}"`;
}
@@ -153,7 +153,7 @@ export const buildOrderBySQL = (
// MySQL/MariaDB 大表在无显式排序需求时强制 ORDER BY即使按主键可能触发 filesort
// 导致 `Error 1038 (HY001): Out of sort memory`。
// 因此仅在用户主动点击排序时下发 ORDER BY默认分页查询不加兜底排序。
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'diros') {
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros') {
return '';
}

View File

@@ -14,12 +14,16 @@ const names = (items: Array<{ name: string }>) => items.map((item) => item.name)
describe('sqlDialect', () => {
it('normalizes datasource aliases without collapsing all dialects to mysql', () => {
expect(resolveSqlDialect('postgresql')).toBe('postgres');
expect(resolveSqlDialect('OpenGauss')).toBe('opengauss');
expect(resolveSqlDialect('OceanBase')).toBe('oceanbase');
expect(resolveSqlDialect('doris')).toBe('diros');
expect(resolveSqlDialect('dameng')).toBe('dameng');
expect(resolveSqlDialect('custom', 'kingbase8')).toBe('kingbase');
expect(resolveSqlDialect('custom', 'dm8')).toBe('dameng');
expect(resolveSqlDialect('custom', 'mariadb')).toBe('mariadb');
expect(resolveSqlDialect('custom', 'open_gauss')).toBe('opengauss');
expect(isMysqlFamilyDialect('mariadb')).toBe(true);
expect(isMysqlFamilyDialect('oceanbase')).toBe(true);
expect(isMysqlFamilyDialect('oracle')).toBe(false);
});
@@ -28,6 +32,8 @@ describe('sqlDialect', () => {
expect(values(resolveColumnTypeOptions('oracle'))).not.toContain('tinyint(1)');
expect(values(resolveColumnTypeOptions('dameng'))).toContain('VARCHAR2(255)');
expect(values(resolveColumnTypeOptions('kingbase'))).toContain('integer');
expect(values(resolveColumnTypeOptions('opengauss'))).toContain('integer');
expect(values(resolveColumnTypeOptions('oceanbase'))).toContain('varchar(255)');
expect(values(resolveColumnTypeOptions('kingbase'))).not.toContain('tinyint(1)');
expect(values(resolveColumnTypeOptions('diros'))).toContain('LARGEINT');
expect(values(resolveColumnTypeOptions('sphinx'))).toContain('text');

View File

@@ -8,12 +8,14 @@ export type SqlFunctionCompletion = {
export type SqlDialect =
| 'mysql'
| 'mariadb'
| 'oceanbase'
| 'diros'
| 'sphinx'
| 'postgres'
| 'kingbase'
| 'highgo'
| 'vastbase'
| 'opengauss'
| 'oracle'
| 'dameng'
| 'sqlserver'
@@ -46,6 +48,10 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
case 'pq':
case 'pgx':
return 'postgres';
case 'opengauss':
case 'open_gauss':
case 'open-gauss':
return 'opengauss';
case 'mssql':
case 'sql_server':
case 'sql-server':
@@ -67,6 +73,7 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
case 'kingbasev8':
return 'kingbase';
case 'mariadb':
case 'oceanbase':
case 'mysql':
case 'sphinx':
case 'kingbase':
@@ -83,7 +90,9 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
break;
}
if (source.includes('opengauss') || source.includes('open_gauss') || source.includes('open-gauss')) return 'opengauss';
if (source.includes('postgres')) return 'postgres';
if (source.includes('oceanbase')) return 'oceanbase';
if (source.includes('mariadb')) return 'mariadb';
if (source.includes('mysql')) return 'mysql';
if (source.includes('doris') || source.includes('diros')) return 'diros';
@@ -103,11 +112,11 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
};
export const isMysqlFamilyDialect = (dbType: string): boolean => (
['mysql', 'mariadb', 'diros', 'sphinx', 'tidb', 'oceanbase', 'starrocks'].includes(resolveSqlDialect(dbType))
['mysql', 'mariadb', 'oceanbase', 'diros', 'sphinx', 'tidb', 'starrocks'].includes(resolveSqlDialect(dbType))
);
export const isPgLikeDialect = (dbType: string): boolean => (
['postgres', 'kingbase', 'highgo', 'vastbase'].includes(resolveSqlDialect(dbType))
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss'].includes(resolveSqlDialect(dbType))
);
export const isOracleLikeDialect = (dbType: string): boolean => (
@@ -423,9 +432,9 @@ const COMMON_TYPES = optionValues(['int', 'varchar(255)', 'text', 'datetime', 'd
export const resolveColumnTypeOptions = (dbType: string): ColumnTypeOption[] => {
const dialect = resolveSqlDialect(dbType);
if (dialect === 'mariadb' || dialect === 'mysql') return MYSQL_TYPES;
if (dialect === 'diros') return DORIS_TYPES;
if (dialect === 'sphinx') return SPHINX_TYPES;
if (isMysqlFamilyDialect(dialect)) return MYSQL_TYPES;
if (isPgLikeDialect(dialect)) return PG_TYPES;
if (dialect === 'oracle') return ORACLE_TYPES;
if (dialect === 'dameng') return DAMENG_TYPES;