mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-08 07:29:44 +08:00
✨ feat(connection): 支持多数据源额外连接参数配置
- 前端连接表单新增额外连接参数入口,支持 URI query 格式录入与解析回填 - MySQL 兼容驱动支持 JDBC 常见参数映射,修复 UTF-8 字符集与 serverTimezone 兼容问题 - 扩展 Oracle、PostgreSQL 兼容、SQL Server、ClickHouse、MongoDB、达梦、TDengine 参数合并 - 按不同驱动通道处理 DSN、URI、Options 与 Settings,避免统一透传导致连接异常 - 修复编辑已保存连接时解析无认证 URI 会清空已有账号密码的问题 - 补充连接参数透传、缓存隔离、DSN 合并与 URI 回填回归测试
This commit is contained in:
@@ -52,6 +52,19 @@ describe('buildRpcConnectionConfig', () => {
|
||||
expect(result.clickHouseProtocol).toBe('http');
|
||||
});
|
||||
|
||||
it('preserves extra connection params for RPC calls', () => {
|
||||
const result = buildRpcConnectionConfig({
|
||||
id: 'conn-mysql',
|
||||
type: 'mysql',
|
||||
host: 'db.local',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
connectionParams: 'characterEncoding=utf8&useSSL=false',
|
||||
} as any);
|
||||
|
||||
expect(result.connectionParams).toBe('characterEncoding=utf8&useSSL=false');
|
||||
});
|
||||
|
||||
it('fills default nested config blocks needed by RPC calls', () => {
|
||||
const result = buildRpcConnectionConfig({
|
||||
id: 'conn-redis',
|
||||
|
||||
77
frontend/src/utils/connectionUriMerge.test.ts
Normal file
77
frontend/src/utils/connectionUriMerge.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { mergeParsedUriValuesForForm } from "./connectionUriMerge";
|
||||
|
||||
describe("mergeParsedUriValuesForForm", () => {
|
||||
it("keeps saved credentials when parsed URI has no auth section", () => {
|
||||
const result = mergeParsedUriValuesForForm(
|
||||
{
|
||||
user: "root",
|
||||
password: "saved-password",
|
||||
host: "192.168.1.10",
|
||||
port: 3306,
|
||||
database: "old_db",
|
||||
connectionParams: "application_name=GoNavi",
|
||||
timeout: 30,
|
||||
},
|
||||
{
|
||||
host: "192.168.1.240",
|
||||
port: 3306,
|
||||
user: "",
|
||||
password: "",
|
||||
database: "mkefu_location_dev_local",
|
||||
connectionParams: "",
|
||||
timeout: undefined,
|
||||
useSSL: false,
|
||||
},
|
||||
"jdbc:mysql://192.168.1.240:3306/mkefu_location_dev_local?characterEncoding=UTF-8",
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
uri: "jdbc:mysql://192.168.1.240:3306/mkefu_location_dev_local?characterEncoding=UTF-8",
|
||||
host: "192.168.1.240",
|
||||
port: 3306,
|
||||
database: "mkefu_location_dev_local",
|
||||
useSSL: false,
|
||||
});
|
||||
expect(result).not.toHaveProperty("user");
|
||||
expect(result).not.toHaveProperty("password");
|
||||
expect(result).not.toHaveProperty("connectionParams");
|
||||
expect(result).not.toHaveProperty("timeout");
|
||||
});
|
||||
|
||||
it("allows URI credentials to replace existing credentials when provided", () => {
|
||||
const result = mergeParsedUriValuesForForm(
|
||||
{
|
||||
user: "root",
|
||||
password: "old-password",
|
||||
},
|
||||
{
|
||||
user: "uri_user",
|
||||
password: "uri-password",
|
||||
},
|
||||
"mysql://uri_user:uri-password@127.0.0.1:3306/app",
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
user: "uri_user",
|
||||
password: "uri-password",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps existing database when URI omits a database path", () => {
|
||||
const result = mergeParsedUriValuesForForm(
|
||||
{
|
||||
database: "saved_db",
|
||||
},
|
||||
{
|
||||
host: "127.0.0.1",
|
||||
database: "",
|
||||
},
|
||||
"mysql://127.0.0.1:3306",
|
||||
);
|
||||
|
||||
expect(result.database).toBeUndefined();
|
||||
expect(result.host).toBe("127.0.0.1");
|
||||
});
|
||||
});
|
||||
36
frontend/src/utils/connectionUriMerge.ts
Normal file
36
frontend/src/utils/connectionUriMerge.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
const EMPTY_PRESERVED_URI_FIELDS = new Set([
|
||||
"user",
|
||||
"password",
|
||||
"database",
|
||||
"connectionParams",
|
||||
]);
|
||||
|
||||
const isEmptyParsedValue = (value: unknown): boolean =>
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === "" ||
|
||||
(Array.isArray(value) && value.length === 0);
|
||||
|
||||
export const mergeParsedUriValuesForForm = (
|
||||
currentValues: Record<string, unknown>,
|
||||
parsedValues: Record<string, unknown>,
|
||||
uriText: string,
|
||||
): Record<string, unknown> => {
|
||||
const nextValues: Record<string, unknown> = { uri: uriText };
|
||||
|
||||
Object.entries(parsedValues).forEach(([key, value]) => {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
EMPTY_PRESERVED_URI_FIELDS.has(key) &&
|
||||
isEmptyParsedValue(value) &&
|
||||
!isEmptyParsedValue(currentValues[key])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
nextValues[key] = value;
|
||||
});
|
||||
|
||||
return nextValues;
|
||||
};
|
||||
Reference in New Issue
Block a user