feat(dameng): support dm6 connections via custom jdbc drivers

This commit is contained in:
zipg authored and GitHub committed 2026-08-26 12:43:31 +08:00
1 parent c8c9870dba
commit 5b61f17c49
20 files changed
+560 -21

No files matched your search

@@ -50,6 +50,7 @@ import { mongodbAuthFailureHint, mongoConnectionUsesOidc, mongoUrlParam, mongoUr
import { isMongoLegacyDriverProfile } from "@/lib/mongo/mongoCapabilities";
import { mysqlCleartextPasswordAuthEnabled, setMysqlCleartextPasswordAuthEnabled } from "@/lib/database/mysqlConnectionOptions";
import { applyDamengSslUrlParams, damengSslFormConfig } from "@/lib/database/damengSslOptions";
import { DAMENG_BUILTIN_DRIVER_PROFILE, DAMENG_CUSTOM_DRIVER_PROFILE, DAMENG_DEFAULT_JDBC_DRIVER_CLASS, damengCustomJdbcUrl, damengDriverModeForConfig, defaultDamengJdbcUrl, type DamengDriverMode } from "@/lib/database/damengDriverOptions";
import { doltSystemTablesVisible, isDoltDriverProfile, setDoltSystemTablesVisible } from "@/lib/database/doltProfile";
import { DamengJvmSystemPropertyError, damengJvmSystemPropertiesText, parseDamengJvmSystemProperties } from "@/lib/database/damengJvmOptions";
import { copyToClipboard } from "@/lib/common/clipboard";
@@ -2315,6 +2316,13 @@ function applyProfile(val: string, preserveConnectionFields = false) {
form.value.port = 0;
form.value.connection_string = undefined;
}
if (profile.type === "dameng") {
form.value.connection_string = undefined;
form.value.jdbc_driver_class = undefined;
form.value.jdbc_driver_paths = [];
jdbcDriverPathsInput.value = "";
jdbcManualClasspathOpen.value = false;
}
if (profile.type === "jdbc") {
form.value.host = "";
form.value.connection_string = "";
@@ -2752,6 +2760,29 @@ function switchH2DriverProfile(profile: "h2" | "h2-v1" | "h2-v2" | "h2-v3" | "h2
resetTestState();
}
const damengDriverMode = computed(() => damengDriverModeForConfig(form.value));
const isDamengCustomDriver = computed(() => form.value.db_type === "dameng" && damengDriverMode.value === "custom");
function setDamengDriverMode(mode: DamengDriverMode) {
if (mode === "builtin") {
form.value.driver_profile = DAMENG_BUILTIN_DRIVER_PROFILE;
form.value.connection_string = undefined;
form.value.jdbc_driver_class = undefined;
form.value.jdbc_driver_paths = [];
jdbcDriverPathsInput.value = "";
selectedJdbcDriverPath.value = "";
jdbcManualClasspathOpen.value = false;
} else {
form.value.driver_profile = DAMENG_CUSTOM_DRIVER_PROFILE;
// Do not materialize connection_string here: submit regenerates it from
// the live form, and freezing the URL at switch time would keep the
// connection on the old host/port after later edits.
form.value.jdbc_driver_class = form.value.jdbc_driver_class?.trim() || DAMENG_DEFAULT_JDBC_DRIVER_CLASS;
jdbcManualClasspathOpen.value = true;
}
resetTestState();
}
function isH2FileJdbcUrlLikePath(value: string): boolean {
return /\.(mv|h2)\.db$/i.test(value.trim()) || value.includes("/") || value.includes("\\");
}
@@ -2856,7 +2887,7 @@ function dbCategoryForOption(value: string): DbCategoryKey | undefined {
const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selectedProfile().icon || selectedType.value);
function supportsNativeAgentJdbcDriverConfigType(dbType: DatabaseType): boolean {
return dbType === "prestosql" || dbType === "bigquery";
return dbType === "prestosql" || dbType === "bigquery" || dbType === "dameng";
}
const jdbcBackedDatabaseTypes = new Set<DatabaseType>(["jdbc", "prestosql", "bigquery"]);
@@ -2871,7 +2902,7 @@ const jdbcxHighPrivilegeExtensionsAllowed = computed({
resetTestState();
},
});
const supportsNativeAgentJdbcDriverConfig = computed(() => supportsNativeAgentJdbcDriverConfigType(form.value.db_type));
const supportsNativeAgentJdbcDriverConfig = computed(() => supportsNativeAgentJdbcDriverConfigType(form.value.db_type) && (form.value.db_type !== "dameng" || isDamengCustomDriver.value));
const isH2FileMode = computed(() => form.value.db_type === "h2" && h2ConnectionMode.value === "file");
const isH2CustomDriver = computed(() => form.value.db_type === "h2" && form.value.driver_profile === "h2-custom");
const usesLocalFilePathInput = computed(() => isLocalFileTypeDb(form.value.db_type) && (form.value.db_type !== "h2" || isH2FileMode.value));
@@ -3941,6 +3972,22 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
} else {
config.ca_cert_path = config.ca_cert_path?.trim() || "";
}
if (config.db_type === "dameng") {
if (damengDriverModeForConfig(config) === "custom") {
config.driver_profile = DAMENG_CUSTOM_DRIVER_PROFILE;
config.connection_string = damengCustomJdbcUrl(config);
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || DAMENG_DEFAULT_JDBC_DRIVER_CLASS;
config.jdbc_driver_paths = parsedJdbcDriverPaths();
if (config.jdbc_driver_paths.length === 0) {
throw new Error(t("connection.damengCustomDriverRequired"));
}
} else {
config.driver_profile = DAMENG_BUILTIN_DRIVER_PROFILE;
config.connection_string = undefined;
config.jdbc_driver_class = undefined;
config.jdbc_driver_paths = [];
}
}
if (jdbcBackedDatabaseTypes.has(config.db_type) || gaussdbConnectionMode(config) === "m-jdbc") {
if (config.db_type === "jdbc") {
if (config.driver_profile === "dremio") {
@@ -7131,6 +7178,26 @@ function openExternalUrl(url: string) {
</div>
</div>
<div v-if="form.db_type === 'dameng'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.driverMode") }}</Label>
<div class="col-span-3 flex items-center gap-2">
<Button size="sm" :variant="damengDriverMode === 'builtin' ? 'default' : 'outline'" @click="setDamengDriverMode('builtin')">
{{ t("connection.damengBuiltinDriver") }}
</Button>
<Button size="sm" :variant="damengDriverMode === 'custom' ? 'default' : 'outline'" @click="setDamengDriverMode('custom')">
{{ t("connection.damengCustomDriver") }}
</Button>
<Tooltip>
<TooltipTrigger as-child>
<CircleHelp class="h-3.5 w-3.5 cursor-help text-muted-foreground hover:text-foreground" />
</TooltipTrigger>
<TooltipContent side="top" align="center" class="max-w-[320px] text-xs leading-relaxed">
{{ t("connection.damengDriverModeHint") }}
</TooltipContent>
</Tooltip>
</div>
</div>
<!-- GaussDB: multi-host dynamic list -->
<template v-if="form.db_type === 'gaussdb'">
<div class="grid grid-cols-4 items-start gap-4">
@@ -7160,6 +7227,11 @@ function openExternalUrl(url: string) {
<Input v-model.number="form.port" type="number" class="col-span-1" @input="markSqlServerPortExplicit" />
</div>
<div v-if="isDamengCustomDriver" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelClass">{{ t("connection.jdbcUrl") }}</Label>
<Input v-model="form.connection_string" class="col-span-3" :placeholder="defaultDamengJdbcUrl(form)" />
</div>
<div v-if="form.db_type === 'elasticsearch' && elasticsearchConnectionMode === 'kibana'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.elasticsearchKibanaBasePath") }}</Label>
<Input v-model="elasticsearchKibanaBasePath" class="col-span-3" placeholder="/kibana/s/default" @input="resetTestState" />
@@ -7469,14 +7541,14 @@ function openExternalUrl(url: string) {
<span />
<div class="col-span-3 space-y-2">
<p class="text-xs text-muted-foreground">
{{ t("connection.jdbcPluginHint") }}
{{ form.db_type === "dameng" ? t("connection.damengCustomDriverHint") : t("connection.jdbcPluginHint") }}
</p>
<div class="flex flex-wrap gap-2">
<Button type="button" variant="outline" size="sm" @click="emit('openDriverStore', { target: 'tab', tab: 'jdbc' })">
<FolderOpen class="h-3.5 w-3.5" />
{{ t("toolbar.driverManager") }}
</Button>
<Button type="button" variant="outline" size="sm" @click="openExternalUrl('https://dbxio.com')">
<Button v-if="form.db_type !== 'dameng'" type="button" variant="outline" size="sm" @click="openExternalUrl('https://dbxio.com')">
<ExternalLink class="h-3.5 w-3.5" />
{{ t("connection.jdbcDocs") }}
</Button>
+5
View File
@@ -560,6 +560,11 @@ export default {
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "One -Dkey or -Dkey=value per line. Values with spaces do not need shell quotes.",
damengJvmOptionsInvalid: "Line {line} must be a -Dkey or -Dkey=value system property without shell quotes.",
damengBuiltinDriver: "Bundled DM8",
damengCustomDriver: "DM6 / Custom JAR",
damengDriverModeHint: "Use the bundled driver for DM8. For DM6 or another compatible version, select its official JDBC JAR.",
damengCustomDriverHint: "Select a Dameng JDBC JAR from a licensed source. The custom driver is isolated inside the Dameng agent and does not require the generic JDBC plugin.",
damengCustomDriverRequired: "Select a Dameng JDBC driver JAR.",
redisTlsInsecure: "Skip certificate verification",
redisTlsInsecureHint: "Equivalent to redis-cli --tls --insecure, for self-signed certificates or private CAs.",
mysqlTlsMode: "TLS Mode",
+5
View File
@@ -1142,6 +1142,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "Una propiedad -Dkey o -Dkey=value por línea. Los valores con espacios no necesitan comillas del shell.",
damengJvmOptionsInvalid: "La línea {line} debe ser una propiedad -Dkey o -Dkey=value sin comillas del shell.",
damengBuiltinDriver: "DM8 incluido",
damengCustomDriver: "DM6 / JAR personalizado",
damengDriverModeHint: "Use el controlador incluido para DM8. Para DM6 u otra versión compatible, seleccione su JAR JDBC oficial.",
damengCustomDriverHint: "Seleccione un JAR JDBC de Dameng de una fuente con licencia. El controlador se carga de forma aislada en el agente de Dameng y no requiere el complemento JDBC genérico.",
damengCustomDriverRequired: "Seleccione un JAR del controlador JDBC de Dameng.",
zookeeperClusterInputHint: "El clúster ZooKeeper puede completarse con todos los nodos en la URL superior (por ejemplo, zookeeper://zk-1:2181,zk-2:2181/app), o en el Connect String a continuación.",
zookeeperAuthMethod: "Método de autenticación",
zookeeperAuthDigest: "Digest (predeterminado)",
+5
View File
@@ -1140,6 +1140,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "Una proprietà -Dkey o -Dkey=value per riga. I valori con spazi non richiedono virgolette della shell.",
damengJvmOptionsInvalid: "La riga {line} deve essere una proprietà -Dkey o -Dkey=value senza virgolette della shell.",
damengBuiltinDriver: "DM8 integrato",
damengCustomDriver: "DM6 / JAR personalizzato",
damengDriverModeHint: "Usa il driver integrato per DM8. Per DM6 o un'altra versione compatibile, seleziona il relativo JAR JDBC ufficiale.",
damengCustomDriverHint: "Seleziona un JAR JDBC Dameng da una fonte autorizzata. Il driver viene caricato in modo isolato nell'agente Dameng e non richiede il plugin JDBC generico.",
damengCustomDriverRequired: "Seleziona un JAR del driver JDBC Dameng.",
zookeeperClusterInputHint: "Il cluster ZooKeeper può essere specificato inserendo tutti i nodi nell'URL superiore (ad esempio zookeeper://zk-1:2181,zk-2:2181/app), oppure nella stringa di connessione sottostante.",
zookeeperAuthMethod: "Metodo di autenticazione",
zookeeperAuthDigest: "Digest (predefinito)",
+5
View File
@@ -1140,6 +1140,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "1 行に 1 つの -Dkey または -Dkey=value を入力します。空白を含む値にもシェル引用符は不要です。",
damengJvmOptionsInvalid: "{line} 行目は、シェル引用符を使わない -Dkey または -Dkey=value 形式で入力してください。",
damengBuiltinDriver: "内蔵 DM8",
damengCustomDriver: "DM6 / カスタム JAR",
damengDriverModeHint: "DM8 では内蔵ドライバーを使用します。DM6 またはその他の互換バージョンでは、対応する公式 JDBC JAR を選択してください。",
damengCustomDriverHint: "正規の提供元から入手した Dameng JDBC JAR を選択してください。カスタムドライバーは Dameng エージェント内で分離して読み込まれ、汎用 JDBC プラグインは不要です。",
damengCustomDriverRequired: "Dameng JDBC ドライバー JAR を選択してください。",
localInfilePathHint: "LOAD DATA LOCAL INFILE を使用する場合、localInfilePath=/絶対パス/file.csv で各ファイルを明示的に許可してください。複数のファイルがある場合はこのパラメータを繰り返します。",
hiveAuthMode: "認証",
hiveAuthNone: "なし",
+5
View File
@@ -554,6 +554,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "한 줄에 하나의 -Dkey 또는 -Dkey=value. 공백이 있는 값에는 셸 인용 부호가 필요하지 않습니다.",
damengJvmOptionsInvalid: "{line}번째 줄은 셸 인용 부호 없는 -Dkey 또는 -Dkey=value 시스템 속성이어야 합니다.",
damengBuiltinDriver: "내장 DM8",
damengCustomDriver: "DM6 / 사용자 지정 JAR",
damengDriverModeHint: "DM8은 내장 드라이버를 사용합니다. DM6 또는 다른 호환 버전은 해당 공식 JDBC JAR을 선택하세요.",
damengCustomDriverHint: "정식 출처의 Dameng JDBC JAR을 선택하세요. 사용자 지정 드라이버는 Dameng 에이전트에서 격리되어 로드되며 일반 JDBC 플러그인이 필요하지 않습니다.",
damengCustomDriverRequired: "Dameng JDBC 드라이버 JAR을 선택하세요.",
redisTlsInsecure: "인증서 검증 건너뛰기",
redisTlsInsecureHint: "redis-cli --tls --insecure과 동일하며, 자체 서명 인증서나 사설 CA용입니다.",
mysqlTlsMode: "TLS 모드",
+5
View File
@@ -1141,6 +1141,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "Uma propriedade -Dkey ou -Dkey=value por linha. Valores com espaços não precisam de aspas do shell.",
damengJvmOptionsInvalid: "A linha {line} deve ser uma propriedade -Dkey ou -Dkey=value sem aspas do shell.",
damengBuiltinDriver: "DM8 integrado",
damengCustomDriver: "DM6 / JAR personalizado",
damengDriverModeHint: "Use o driver integrado para DM8. Para DM6 ou outra versão compatível, selecione o JAR JDBC oficial correspondente.",
damengCustomDriverHint: "Selecione um JAR JDBC do Dameng de uma fonte licenciada. O driver é carregado de forma isolada no agente Dameng e não requer o plugin JDBC genérico.",
damengCustomDriverRequired: "Selecione um JAR do driver JDBC do Dameng.",
zookeeperClusterInputHint: "O cluster ZooKeeper pode ser preenchido com todos os nós na URL acima (por exemplo, zookeeper://zk-1:2181,zk-2:2181/app), ou também na Connect String abaixo.",
zookeeperAuthMethod: "Método de autenticação",
zookeeperAuthDigest: "Digest (padrão)",
+5
View File
@@ -485,6 +485,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "每行一个 -Dkey 或 -Dkey=value;值包含空格时也不要使用 shell 引号。",
damengJvmOptionsInvalid: "第 {line} 行必须是未使用 shell 引号的 -Dkey 或 -Dkey=value 系统属性。",
damengBuiltinDriver: "内置 DM8",
damengCustomDriver: "DM6 / 自定义 JAR",
damengDriverModeHint: "DM8 使用内置驱动;DM6 或其他兼容版本请选择对应的官方 JDBC JAR。",
damengCustomDriverHint: "请选择合法来源的达梦 JDBC JAR。自定义驱动在达梦 Agent 内隔离加载,无需安装通用 JDBC 插件。",
damengCustomDriverRequired: "请选择达梦 JDBC 驱动 JAR。",
redisTlsInsecure: "跳过证书验证",
redisTlsInsecureHint: "等价于 redis-cli --tls --insecure,适用于自签名证书或私有 CA。",
mysqlTlsMode: "TLS 模式",
+5
View File
@@ -1142,6 +1142,11 @@ export default withEnglishFallback({
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
damengJvmOptionsHint: "每行一個 -Dkey 或 -Dkey=value;值包含空格時也不要使用 shell 引號。",
damengJvmOptionsInvalid: "第 {line} 行必須是未使用 shell 引號的 -Dkey 或 -Dkey=value 系統屬性。",
damengBuiltinDriver: "內建 DM8",
damengCustomDriver: "DM6 / 自訂 JAR",
damengDriverModeHint: "DM8 使用內建驅動;DM6 或其他相容版本請選擇對應的官方 JDBC JAR。",
damengCustomDriverHint: "請選擇合法來源的達夢 JDBC JAR。自訂驅動會在達夢 Agent 內隔離載入,不需要安裝通用 JDBC 外掛。",
damengCustomDriverRequired: "請選擇達夢 JDBC 驅動 JAR。",
zookeeperClusterInputHint: "ZooKeeper 叢集可在上方 URL 中填寫全部節點(例如 zookeeper://zk-1:2181,zk-2:2181/app),也可在下方 Connect String 中填寫。",
zookeeperAuthMethod: "認證方式",
zookeeperAuthDigest: "Digest(預設)",
@@ -10,7 +10,7 @@ describe("BigQuery connection dialog", () => {
expect(nativeAgentDriverSupport).toContain('dbType === "bigquery"');
expect(jdbcBackedDatabaseTypes).toContain('"bigquery"');
expect(dialogSource).toContain("const supportsNativeAgentJdbcDriverConfig = computed(() => supportsNativeAgentJdbcDriverConfigType(form.value.db_type));");
expect(dialogSource).toContain("const supportsNativeAgentJdbcDriverConfig = computed(() => supportsNativeAgentJdbcDriverConfigType(form.value.db_type)");
expect(dialogSource).toContain('<template v-if="supportsNativeAgentJdbcDriverConfig">');
expect(dialogSource).toContain('if (profile.type === "bigquery") {');
expect(dialogSource).toContain("jdbcManualClasspathOpen.value = true;");
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { DAMENG_CUSTOM_DRIVER_PROFILE, DAMENG_DEFAULT_JDBC_DRIVER_CLASS, damengCustomJdbcUrl, damengDriverModeForConfig, defaultDamengJdbcUrl } from "@/lib/database/damengDriverOptions";
describe("dameng driver options", () => {
it("keeps existing connections on the built-in driver", () => {
expect(damengDriverModeForConfig({ driver_profile: "dm", jdbc_driver_paths: [] })).toBe("builtin");
});
it("recognizes custom DM6 profiles and legacy configs with an external JAR", () => {
expect(damengDriverModeForConfig({ driver_profile: DAMENG_CUSTOM_DRIVER_PROFILE })).toBe("custom");
expect(damengDriverModeForConfig({ driver_profile: "dm", jdbc_driver_paths: [" /drivers/DmJdbcDriver6.jar "] })).toBe("custom");
});
it("builds DM6 driver defaults from host, port, and optional database", () => {
expect(DAMENG_DEFAULT_JDBC_DRIVER_CLASS).toBe("dm6.jdbc.driver.DmDriver");
expect(defaultDamengJdbcUrl({ host: "dm6.internal", port: 5237, database: "MAIN" })).toBe("jdbc:dm6://dm6.internal:5237/MAIN");
expect(defaultDamengJdbcUrl({ host: "", port: 0, database: "" })).toBe("jdbc:dm6://127.0.0.1:5236");
});
it("preserves a complete custom JDBC URL", () => {
expect(damengCustomJdbcUrl({ host: "ignored", port: 5236, connection_string: " jdbc:dm6://dm6.internal:5236?foo=bar " })).toBe("jdbc:dm6://dm6.internal:5236?foo=bar");
});
});
@@ -0,0 +1,27 @@
import type { ConnectionConfig } from "@/types/database";
export const DAMENG_BUILTIN_DRIVER_PROFILE = "dm";
export const DAMENG_CUSTOM_DRIVER_PROFILE = "dm6-custom";
export const DAMENG_DEFAULT_JDBC_DRIVER_CLASS = "dm6.jdbc.driver.DmDriver";
export type DamengDriverMode = "builtin" | "custom";
type DamengDriverConfig = Partial<Pick<ConnectionConfig, "driver_profile" | "host" | "port" | "database" | "connection_string" | "jdbc_driver_paths">>;
export function damengDriverModeForConfig(config: DamengDriverConfig): DamengDriverMode {
if (config.driver_profile === DAMENG_CUSTOM_DRIVER_PROFILE || config.jdbc_driver_paths?.some((path) => path.trim())) {
return "custom";
}
return "builtin";
}
export function defaultDamengJdbcUrl(config: Pick<DamengDriverConfig, "host" | "port" | "database">): string {
const host = config.host?.trim() || "127.0.0.1";
const port = Number(config.port) > 0 ? Number(config.port) : 5236;
const database = config.database?.trim();
return `jdbc:dm6://${host}:${port}${database ? `/${database}` : ""}`;
}
export function damengCustomJdbcUrl(config: DamengDriverConfig): string {
return config.connection_string?.trim() || defaultDamengJdbcUrl(config);
}