From 2929b1c035612904adaa994dcfead8971a50ada1 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 8 Jul 2026 13:22:06 +0800 Subject: [PATCH] feat(hive): add Kerberos connection options --- .../java/com/dbx/agent/hive/HiveAgent.java | 26 ++- .../com/dbx/agent/hive/HiveAgentTest.java | 31 ++++ .../connection/ConnectionDialog.vue | 130 +++++++++++++++ apps/desktop/src/i18n/locales/en.ts | 10 ++ apps/desktop/src/i18n/locales/zh-CN.ts | 10 ++ .../src/lib/database/hiveKerberosOptions.ts | 149 ++++++++++++++++++ apps/desktop/src/stores/connectionStore.ts | 1 + apps/desktop/src/types/database.ts | 1 + crates/dbx-core/src/agent_connection.rs | 1 + crates/dbx-core/src/agent_manager.rs | 23 ++- crates/dbx-core/src/agent_runtime.rs | 24 +-- crates/dbx-core/src/cloud_sync.rs | 3 + crates/dbx-core/src/connection.rs | 12 +- crates/dbx-core/src/connection_secrets.rs | 1 + crates/dbx-core/src/db/agent_driver.rs | 141 ++++++++++++++++- crates/dbx-core/src/db/redis_driver.rs | 1 + crates/dbx-core/src/models/connection.rs | 23 +++ crates/dbx-core/src/mq/config.rs | 1 + crates/dbx-core/src/mq/service.rs | 1 + crates/dbx-core/src/nacos/config.rs | 1 + crates/dbx-core/src/nacos/service.rs | 2 + crates/dbx-core/src/query.rs | 2 + crates/dbx-core/src/schema.rs | 1 + crates/dbx-core/src/schema/normalization.rs | 1 + crates/dbx-core/src/storage.rs | 2 + crates/dbx-core/src/transfer.rs | 1 + .../live_postgres_query_result_export.rs | 1 + .../dbx-core/tests/live_postgres_transfer.rs | 1 + .../tests/live_sqlserver_completion.rs | 1 + .../app-tests/hiveKerberosOptions.test.ts | 60 +++++++ 30 files changed, 639 insertions(+), 23 deletions(-) create mode 100644 apps/desktop/src/lib/database/hiveKerberosOptions.ts create mode 100644 packages/app-tests/hiveKerberosOptions.test.ts diff --git a/agents/drivers/hive/src/main/java/com/dbx/agent/hive/HiveAgent.java b/agents/drivers/hive/src/main/java/com/dbx/agent/hive/HiveAgent.java index 9a3b407621..98a285c901 100644 --- a/agents/drivers/hive/src/main/java/com/dbx/agent/hive/HiveAgent.java +++ b/agents/drivers/hive/src/main/java/com/dbx/agent/hive/HiveAgent.java @@ -169,8 +169,30 @@ public final class HiveAgent extends AbstractJdbcAgent { return result; } - private static String buildUrl(ConnectParams params) { - return "jdbc:hive2://" + params.getHost() + ":" + params.getPort() + "/" + params.getDatabase(); + static String buildUrl(ConnectParams params) { + String connectionString = trimToEmpty(params.getConnection_string()); + if (!connectionString.isEmpty()) { + return connectionString; + } + String url = "jdbc:hive2://" + params.getHost() + ":" + params.getPort() + "/" + params.getDatabase(); + return appendHiveUrlParams(url, params.getUrl_params()); + } + + private static String appendHiveUrlParams(String url, String urlParams) { + String params = trimHiveUrlParams(urlParams); + if (params.isEmpty()) { + return url; + } + // Hive JDBC authentication options use semicolon-separated URL properties. + return url + (url.endsWith(";") ? "" : ";") + params; + } + + private static String trimHiveUrlParams(String urlParams) { + String value = trimToEmpty(urlParams); + while (value.startsWith("?") || value.startsWith("&") || value.startsWith(";")) { + value = value.substring(1); + } + return value; } private static String trimToNull(String value) { diff --git a/agents/drivers/hive/src/test/java/com/dbx/agent/hive/HiveAgentTest.java b/agents/drivers/hive/src/test/java/com/dbx/agent/hive/HiveAgentTest.java index 36006e7271..f3496cdae3 100644 --- a/agents/drivers/hive/src/test/java/com/dbx/agent/hive/HiveAgentTest.java +++ b/agents/drivers/hive/src/test/java/com/dbx/agent/hive/HiveAgentTest.java @@ -1,11 +1,42 @@ package com.dbx.agent.hive; +import com.dbx.agent.ConnectParams; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + class HiveAgentTest { @Test void hiveJdbcStandaloneRuntimeClassesAreAvailable() throws ClassNotFoundException { Class.forName("org.apache.hive.jdbc.HiveDriver"); Class.forName("org.apache.hive.org.apache.thrift.protocol.TProtocol"); } + + @Test + void buildUrlAppendsKerberosUrlParams() { + ConnectParams params = new ConnectParams(); + params.setHost("hive.example.com"); + params.setPort(10000); + params.setDatabase("default"); + params.setUrl_params(";principal=hive/hive.example.com@EXAMPLE.COM;auth=kerberos"); + + assertEquals( + "jdbc:hive2://hive.example.com:10000/default;principal=hive/hive.example.com@EXAMPLE.COM;auth=kerberos", + HiveAgent.buildUrl(params) + ); + } + + @Test + void buildUrlUsesCustomJdbcUrlAsIs() { + ConnectParams params = new ConnectParams(); + params.setConnection_string( + "jdbc:hive2://zk1.example.com,zk2.example.com/default;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2;principal=hive/_HOST@EXAMPLE.COM" + ); + params.setUrl_params("principal=hive/ignored@EXAMPLE.COM"); + + assertEquals( + "jdbc:hive2://zk1.example.com,zk2.example.com/default;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2;principal=hive/_HOST@EXAMPLE.COM", + HiveAgent.buildUrl(params) + ); + } } diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 026d148e7d..d5698d4073 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -48,6 +48,7 @@ import { isSchemaAware } from "@/lib/database/databaseFeatureSupport"; import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue"; import { oceanbaseModeConnectionPatch, oceanbaseSubModeFromConfig } from "@/lib/database/oceanbaseConnectionMode"; import { translateBackendError } from "@/i18n/backend-errors"; +import { applyHiveKerberosSubmitConfig, hiveKerberosFormConfig, type HiveKerberosAuthMode } from "@/lib/database/hiveKerberosOptions"; type DbOption = { value: string; label: string }; type DbCategory = { key: string; title: string; options: DbOption[] }; @@ -151,6 +152,7 @@ const defaultForm = (): ConnectionForm => ({ driver_profile: "mysql", driver_label: "MySQL", url_params: "", + agent_java_options: [], host: "127.0.0.1", port: 3306, username: "root", @@ -378,6 +380,12 @@ const dremioConnectionUrls = ref>({ "arrow-flight-sql": DREMIO_ARROW_FLIGHT_SQL_JDBC_URL, legacy: DREMIO_LEGACY_JDBC_URL, }); +const hiveAuthMode = ref("none"); +const hivePrincipal = ref(""); +const hiveKrb5ConfPath = ref(""); +const hiveJaasConfigPath = ref(""); +const hiveUseSubjectCredsOnlyFalse = ref(false); +const hiveExtraJavaOptions = ref(""); const dialogStep = ref("select"); const dbPickerView = ref("icon"); const dbSearchQuery = ref(""); @@ -775,6 +783,16 @@ function hydrateInfluxDbFields(value: unknown) { resetInfluxDbFields(value as Partial); } +function resetHiveKerberosFields(config?: Pick) { + const kerberos = hiveKerberosFormConfig(config?.url_params, config?.agent_java_options); + hiveAuthMode.value = kerberos.authMode; + hivePrincipal.value = kerberos.principal; + hiveKrb5ConfPath.value = kerberos.krb5ConfPath; + hiveJaasConfigPath.value = kerberos.jaasConfigPath; + hiveUseSubjectCredsOnlyFalse.value = kerberos.useSubjectCredsOnlyFalse; + hiveExtraJavaOptions.value = kerberos.extraJavaOptions; +} + function buildInfluxDbExternalConfig(): InfluxDbExternalConfig { if (influxDbVersion.value !== "2") return { version: "1" }; const org = influxDbOrg.value.trim(); @@ -1202,6 +1220,7 @@ function applyProfile(val: string, preserveConnectionFields = false) { form.value.port = profile.port; form.value.username = profile.user; form.value.url_params = profile.urlParams || ""; + form.value.agent_java_options = []; if (profile.host) { form.value.host = profile.host; } @@ -1258,6 +1277,7 @@ function applyProfile(val: string, preserveConnectionFields = false) { form.value.password = ""; form.value.connection_string = undefined; } + resetHiveKerberosFields(profile.type === "hive" ? form.value : undefined); } } @@ -1294,6 +1314,7 @@ watch( driver_profile: oceanbasePatch?.driver_profile || profile, driver_label: config.driver_label || oceanbasePatch?.driver_label || driverProfiles[profile]?.label || config.db_type, url_params: config.url_params || "", + agent_java_options: config.agent_java_options || [], host: config.db_type === "h2" && h2FilePathFromJdbcUrl(config.connection_string) ? h2FilePathFromJdbcUrl(config.connection_string) : config.host, port: profile === "tdengine" && (config.port === 0 || config.port === 6030) ? 6041 : config.port, username: config.username, @@ -1346,6 +1367,7 @@ watch( } else { resetInfluxDbFields(); } + resetHiveKerberosFields(config.db_type === "hive" ? config : undefined); h2ConnectionMode.value = h2ConnectionModeForConfig(config); customColorInput.value = config.color || ""; selectedTransportLayerId.value = form.value.transport_layers?.[0]?.id || null; @@ -1373,6 +1395,7 @@ watch( resetMqFields(); resetNacosFields(); resetInfluxDbFields(); + resetHiveKerberosFields(); oceanbaseSubMode.value = "mysql"; h2ConnectionMode.value = "file"; dremioConnectionMode.value = "legacy"; @@ -2128,6 +2151,24 @@ function connectionConfigForSubmit(id: string): ConnectionConfig { if (config.db_type === "manticoresearch") { config.url_params = ""; } + if (config.db_type === "hive") { + if (hiveAuthMode.value === "kerberos" && !hivePrincipal.value.trim()) { + throw new Error(t("connection.hiveKerberosPrincipalRequired")); + } + const hiveKerberos = applyHiveKerberosSubmitConfig({ + authMode: hiveAuthMode.value, + principal: hivePrincipal.value, + krb5ConfPath: hiveKrb5ConfPath.value, + jaasConfigPath: hiveJaasConfigPath.value, + useSubjectCredsOnlyFalse: hiveUseSubjectCredsOnlyFalse.value, + extraJavaOptions: hiveExtraJavaOptions.value, + urlParams: config.url_params, + }); + config.url_params = hiveKerberos.urlParams; + config.agent_java_options = hiveKerberos.agentJavaOptions; + } else { + config.agent_java_options = undefined; + } if (config.db_type === "informix" && config.informix_server) { // Strip INFORMIXSERVER from url_params to avoid duplicate when dedicated field is used config.url_params = (config.url_params || "") @@ -2339,6 +2380,7 @@ function connectionConfigForSubmit(id: string): ConnectionConfig { config.visible_databases = Array.isArray(config.visible_databases) && config.visible_databases.length > 0 ? config.visible_databases : undefined; } if (config.visible_schemas && Object.keys(config.visible_schemas).length === 0) config.visible_schemas = undefined; + if (config.agent_java_options && config.agent_java_options.length === 0) config.agent_java_options = undefined; return config as ConnectionConfig; } @@ -3239,6 +3281,24 @@ async function browseEtcdTlsFile(target: "ca" | "cert" | "key") { } } +async function browseHiveKerberosFile(target: "krb5" | "jaas") { + if (isTauriRuntime()) { + const { open } = await import("@tauri-apps/plugin-dialog"); + const selected = await open({ + title: target === "krb5" ? t("connection.hiveKrb5ConfBrowse") : t("connection.hiveJaasConfigBrowse"), + multiple: false, + filters: [{ name: "Config", extensions: ["conf", "ini", "properties", "*"] }], + }); + if (selected && typeof selected === "string") { + if (target === "krb5") { + hiveKrb5ConfPath.value = selected; + } else { + hiveJaasConfigPath.value = selected; + } + } + } +} + async function browseDbFilePath() { if (isTauriRuntime()) { const { open } = await import("@tauri-apps/plugin-dialog"); @@ -4387,6 +4447,76 @@ function openExternalUrl(url: string) { +