feat(kyuubi): add native connection support
This commit is contained in:
38 files changed
+402
-54
No files matched your search
@@ -1162,6 +1162,7 @@ const driverProfiles: Record<
|
||||
trino: { type: "trino", port: 8080, user: "", label: "Trino", icon: "trino" },
|
||||
prestosql: { type: "prestosql", port: 8080, user: "", label: "PrestoSQL", icon: "presto" },
|
||||
hive: { type: "hive", port: 10000, user: "", label: "Apache Hive", icon: "hive" },
|
||||
kyuubi: { type: "kyuubi", port: 10009, user: "", label: "Apache Kyuubi", icon: "kyuubi", urlParams: "auth=NONE" },
|
||||
impala: { type: "impala", port: 21050, user: "", label: "Apache Impala", icon: "impala", urlParams: "auth=noSasl" },
|
||||
spark: { type: "spark", port: 10015, user: "", label: "Apache Spark", icon: "spark" },
|
||||
db2: { type: "db2", port: 50000, user: "db2inst1", label: "IBM DB2", icon: "db2" },
|
||||
@@ -2416,7 +2417,7 @@ function applyProfile(val: string, preserveConnectionFields = false) {
|
||||
form.value.connection_string = undefined;
|
||||
form.value.url_params = "";
|
||||
}
|
||||
resetHiveKerberosFields(profile.type === "hive" || profile.type === "impala" ? form.value : undefined);
|
||||
resetHiveKerberosFields(profile.type === "hive" || profile.type === "kyuubi" || profile.type === "impala" ? form.value : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2542,7 +2543,7 @@ watch(
|
||||
resetVictoriaMetricsFields();
|
||||
}
|
||||
resetElasticsearchProxyFields(config.db_type === "elasticsearch" ? config.external_config : undefined);
|
||||
resetHiveKerberosFields(config.db_type === "hive" || config.db_type === "impala" ? config : undefined);
|
||||
resetHiveKerberosFields(config.db_type === "hive" || config.db_type === "kyuubi" || config.db_type === "impala" ? config : undefined);
|
||||
resetDamengJvmOptions(config.db_type === "dameng" ? config : undefined);
|
||||
h2ConnectionMode.value = h2ConnectionModeForConfig(config);
|
||||
customColorInput.value = config.color || "";
|
||||
@@ -2797,6 +2798,7 @@ const iconTypeMap: Record<string, string> = {
|
||||
trino: "trino",
|
||||
prestosql: "prestosql",
|
||||
hive: "hive",
|
||||
kyuubi: "kyuubi",
|
||||
impala: "impala",
|
||||
spark: "spark",
|
||||
db2: "db2",
|
||||
@@ -2879,6 +2881,7 @@ const dbOptions: DbOption[] = [
|
||||
{ value: "trino", label: "Trino" },
|
||||
{ value: "prestosql", label: "PrestoSQL" },
|
||||
{ value: "hive", label: "Hive" },
|
||||
{ value: "kyuubi", label: "Apache Kyuubi" },
|
||||
{ value: "impala", label: "Apache Impala" },
|
||||
{ value: "spark", label: "Apache Spark" },
|
||||
{ value: "db2", label: "DB2" },
|
||||
@@ -2925,7 +2928,7 @@ const dbCategoryDefinitions: Array<{
|
||||
{
|
||||
key: "analytics",
|
||||
titleKey: "connection.databaseCategoryAnalytics",
|
||||
optionValues: ["cloudberry", "clickhouse", "doris", "starrocks", "databend", "selectdb", "databricks", "saphana", "teradata", "vertica", "exasol", "redshift", "snowflake", "trino", "prestosql", "hive", "impala", "spark", "bigquery", "kylin", "dremio"],
|
||||
optionValues: ["cloudberry", "clickhouse", "doris", "starrocks", "databend", "selectdb", "databricks", "saphana", "teradata", "vertica", "exasol", "redshift", "snowflake", "trino", "prestosql", "hive", "kyuubi", "impala", "spark", "bigquery", "kylin", "dremio"],
|
||||
},
|
||||
{
|
||||
key: "domestic",
|
||||
@@ -3767,7 +3770,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
|
||||
config.ssl = !!config.ssl || damengSsl.enabled;
|
||||
config.url_params = applyDamengSslUrlParams(config.url_params, config.ssl, damengSsl.sslFilesPath, damengSsl.sslKeystorePassword, damengSsl.sslProtocol);
|
||||
}
|
||||
if (config.db_type === "hive" || config.db_type === "impala") {
|
||||
if (config.db_type === "hive" || config.db_type === "kyuubi" || config.db_type === "impala") {
|
||||
if (hiveAuthMode.value === "kerberos" && !hivePrincipal.value.trim()) {
|
||||
throw new Error(t("connection.hiveKerberosPrincipalRequired"));
|
||||
}
|
||||
@@ -7039,7 +7042,7 @@ function openExternalUrl(url: string) {
|
||||
<p class="col-span-3 text-xs text-muted-foreground">{{ t("connection.oracleTnsPathHint") }}</p>
|
||||
</div>
|
||||
|
||||
<template v-if="form.db_type === 'hive' || form.db_type === 'impala'">
|
||||
<template v-if="form.db_type === 'hive' || form.db_type === 'kyuubi' || form.db_type === 'impala'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.hiveAuthMode") }}</Label>
|
||||
<div class="col-span-3 grid h-8 grid-cols-2 overflow-hidden rounded-md border border-input bg-muted/30 p-0.5">
|
||||
|
||||
@@ -44,6 +44,17 @@ describe("DatabaseIcon", () => {
|
||||
app.unmount();
|
||||
});
|
||||
|
||||
it("uses the Apache Kyuubi asset instead of the Hive asset", async () => {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const app = createApp(DatabaseIcon, { dbType: "kyuubi" });
|
||||
app.mount(container);
|
||||
await nextTick();
|
||||
|
||||
expect(container.querySelector("img")?.getAttribute("src")).toBe("/icons/database/kyuubi.png");
|
||||
app.unmount();
|
||||
});
|
||||
|
||||
it("uses the Meilisearch asset", async () => {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
|
||||
@@ -56,6 +56,7 @@ const assetIcons: Record<string, string> = {
|
||||
presto: "presto",
|
||||
prestosql: "presto",
|
||||
hive: "hive",
|
||||
kyuubi: "kyuubi.png",
|
||||
impala: "impala",
|
||||
hbase: "hbase",
|
||||
phoenix: "phoenix",
|
||||
|
||||
@@ -22,6 +22,10 @@ describe("connectionAttemptTimeout", () => {
|
||||
expect(connectionAttemptTimeoutMs({ db_type: "impala", connect_timeout_secs: 5, transport_layers: [] })).toBe(AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS * 1000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS);
|
||||
});
|
||||
|
||||
it("uses the startup floor for Kyuubi agent connections", () => {
|
||||
expect(connectionAttemptTimeoutMs({ db_type: "kyuubi", connect_timeout_secs: 5, transport_layers: [] })).toBe(AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS * 1000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS);
|
||||
});
|
||||
|
||||
it("uses the startup floor for generic JDBC plugin connections", () => {
|
||||
expect(connectionAttemptTimeoutMs({ db_type: "jdbc", connect_timeout_secs: 5, transport_layers: [] })).toBe(AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS * 1000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,17 @@ describe("connection password prompting", () => {
|
||||
expect(connectionNeedsPasswordPrompt(connection({ url_params: "auth=noSasl;principal=impala/_HOST@EXAMPLE.COM" }))).toBe(true);
|
||||
});
|
||||
|
||||
it("allows Kyuubi NONE and NOSASL connections to persist an empty password", () => {
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "kyuubi", port: 10009, url_params: "auth=NONE" }))).toBe(false);
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "kyuubi", port: 10009, url_params: "auth=NOSASL" }))).toBe(false);
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "kyuubi", port: 10009, url_params: "" }))).toBe(false);
|
||||
});
|
||||
|
||||
it("prompts for Kyuubi authentication modes that require credentials", () => {
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "kyuubi", port: 10009, url_params: "auth=LDAP" }))).toBe(true);
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "kyuubi", port: 10009, url_params: "principal=hive/_HOST@EXAMPLE.COM" }))).toBe(true);
|
||||
});
|
||||
|
||||
it("does not change password prompting for other database types", () => {
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "mysql", port: 3306, url_params: "auth=noSasl" }))).toBe(true);
|
||||
expect(connectionNeedsPasswordPrompt(connection({ db_type: "postgres", port: 5432, save_password: true }))).toBe(false);
|
||||
|
||||
@@ -50,6 +50,10 @@ describe("quoteTableIdentifier", () => {
|
||||
expect(quoteTableIdentifier("mysql", "orders")).toBe("`orders`");
|
||||
});
|
||||
|
||||
it("backtick-quotes Kyuubi identifiers", () => {
|
||||
expect(quoteTableIdentifier("kyuubi", "order`items")).toBe("`order``items`");
|
||||
});
|
||||
|
||||
it("uses BigQuery quoted identifiers and escape sequences", () => {
|
||||
expect(quoteTableIdentifier("bigquery", "order")).toBe("`order`");
|
||||
expect(quoteTableIdentifier("bigquery", "a`b")).toBe("`a\\`b`");
|
||||
|
||||
@@ -9,10 +9,12 @@ describe("DuckDB driver installation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Impala driver installation", () => {
|
||||
describe("HiveServer2-compatible driver installation", () => {
|
||||
it("reuses the downloadable Hive driver", () => {
|
||||
expect(agentDriverInstallKey("impala")).toBe("hive");
|
||||
expect(showAgentDriverInstallHint("impala", [{ db_type: "hive", installed: true }])).toBe(false);
|
||||
expect(agentDriverInstallKey("kyuubi")).toBe("hive");
|
||||
expect(showAgentDriverInstallHint("kyuubi", [{ db_type: "hive", installed: true }])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export function hasInstalledAgentVersion(drivers: readonly AgentDriverInstallSta
|
||||
}
|
||||
|
||||
export function agentDriverInstallKey(dbType: DatabaseType | undefined, driverProfile?: string): string | undefined {
|
||||
if (dbType === "impala") return "hive";
|
||||
if (dbType === "kyuubi" || dbType === "impala") return "hive";
|
||||
if (dbType === "oracle") return "oracle";
|
||||
if (dbType === "mongodb") return "mongodb";
|
||||
if (dbType === "dameng") return "dameng";
|
||||
|
||||
@@ -30,6 +30,7 @@ const DRIVER_STARTUP_FLOOR_TYPES = new Set<DatabaseType>([
|
||||
"prestosql",
|
||||
"jdbc",
|
||||
"hive",
|
||||
"kyuubi",
|
||||
"impala",
|
||||
"spark",
|
||||
"db2",
|
||||
|
||||
@@ -17,12 +17,14 @@ function hiveServerUrlParams(urlParams?: string): Map<string, string> {
|
||||
|
||||
export function connectionUsesPasswordlessAuthentication(config: PasswordAuthenticationConfig): boolean {
|
||||
const profile = config.driver_profile || config.db_type;
|
||||
if (config.db_type !== "impala" && profile !== "impala") return false;
|
||||
const isImpala = config.db_type === "impala" || profile === "impala";
|
||||
const isKyuubi = config.db_type === "kyuubi" || profile === "kyuubi";
|
||||
if (!isImpala && !isKyuubi) return false;
|
||||
|
||||
const params = hiveServerUrlParams(config.url_params);
|
||||
if (params.get("principal")) return false;
|
||||
const auth = (params.get("auth") || "nosasl").toLowerCase();
|
||||
return auth === "nosasl";
|
||||
const auth = (params.get("auth") || (isKyuubi ? "none" : "nosasl")).toLowerCase();
|
||||
return isKyuubi ? auth === "none" || auth === "nosasl" : auth === "nosasl";
|
||||
}
|
||||
|
||||
export function connectionNeedsPasswordPrompt(config: ConnectionConfig): boolean {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { manifestDatabaseTypes } from "@/lib/database/databaseDriverManifest";
|
||||
import { supportsTransfer } from "@/lib/database/databaseFeatureSupport";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
const TABLE_ONLY_TRANSFER_DATABASES: DatabaseType[] = ["sqlite", "rqlite", "cloudflare-d1", "duckdb", "clickhouse", "mongodb", "questdb", "hive", "impala", "spark"];
|
||||
const TABLE_ONLY_TRANSFER_DATABASES: DatabaseType[] = ["sqlite", "rqlite", "cloudflare-d1", "duckdb", "clickhouse", "mongodb", "questdb", "hive", "kyuubi", "impala", "spark"];
|
||||
|
||||
describe("transferObjectKinds", () => {
|
||||
it("groups databases into transfer families", () => {
|
||||
|
||||
@@ -27,6 +27,7 @@ export const SCHEMA_AWARE_TYPES = new Set<DatabaseType>([
|
||||
"trino",
|
||||
"prestosql",
|
||||
"hive",
|
||||
"kyuubi",
|
||||
"impala",
|
||||
"spark",
|
||||
"databend",
|
||||
|
||||
@@ -66,6 +66,7 @@ export const DATABASE_NAMESPACE_CREATION_MATRIX = {
|
||||
trino: { database: "schema" },
|
||||
prestosql: { database: "schema" },
|
||||
hive: { deferred: "Hive database creation needs agent metadata validation first" },
|
||||
kyuubi: { deferred: "Kyuubi database creation needs dedicated metadata validation first" },
|
||||
impala: { deferred: "Impala database creation needs dedicated metadata validation first" },
|
||||
spark: { deferred: "Spark database creation needs agent metadata validation first" },
|
||||
db2: { database: "schema" },
|
||||
|
||||
@@ -69,6 +69,7 @@ const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
||||
["doris", TABLE_VIEW_OBJECTS],
|
||||
["starrocks", TABLE_VIEW_MV_OBJECTS],
|
||||
["hive", TABLE_VIEW_OBJECTS],
|
||||
["kyuubi", TABLE_VIEW_OBJECTS],
|
||||
["impala", TABLE_VIEW_OBJECTS],
|
||||
["spark", TABLE_VIEW_OBJECTS],
|
||||
["trino", TABLE_VIEW_OBJECTS],
|
||||
|
||||
@@ -66,6 +66,7 @@ export const DATABASE_PROPERTY_EDITING_MATRIX = {
|
||||
trino: { deferred: "catalog properties are connector-managed" },
|
||||
prestosql: { deferred: "catalog properties are connector-managed" },
|
||||
hive: { deferred: "database properties need agent metadata validation first" },
|
||||
kyuubi: { deferred: "database properties need Kyuubi-specific validation first" },
|
||||
impala: { deferred: "database properties need Impala-specific validation first" },
|
||||
spark: { deferred: "database properties need agent metadata validation first" },
|
||||
db2: { deferred: "schema properties need product-specific handling" },
|
||||
|
||||
@@ -90,6 +90,17 @@ const NAVICAT_STYLE_TABLE_DATA_TYPES = new Set<DatabaseType>([
|
||||
]);
|
||||
|
||||
const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<DatabaseCapability>>> = {
|
||||
kyuubi: {
|
||||
tableData: {
|
||||
insert: false,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
existingRowsReadonly: true,
|
||||
transaction: false,
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
impala: {
|
||||
tableData: {
|
||||
insert: false,
|
||||
|
||||
@@ -13,7 +13,7 @@ const GAUSSDB_TARGET_SERVER_TYPE_KEY = "gaussdbTargetServerType";
|
||||
export const GAUSSDB_M_JDBC_DRIVER_PROFILE = "gaussdb-m";
|
||||
export const GAUSSDB_M_JDBC_DRIVER_CLASS = "com.huawei.gaussdb.jdbc.Driver";
|
||||
|
||||
const DATABASE_AS_EXECUTION_SCHEMA_TYPES = new Set<DatabaseType>(["hive", "impala", "spark"]);
|
||||
const DATABASE_AS_EXECUTION_SCHEMA_TYPES = new Set<DatabaseType>(["hive", "kyuubi", "impala", "spark"]);
|
||||
const CONNECTION_ROOT_SCHEMA_TYPES = new Set<DatabaseType>(["oracle", "dameng", "oceanbase-oracle"]);
|
||||
|
||||
const JDBC_DIALECT_MATCHERS: Array<{ type: DatabaseType; patterns: RegExp[] }> = [
|
||||
|
||||
@@ -33,7 +33,19 @@ export function quoteTableIdentifier(databaseType: DatabaseType | undefined, nam
|
||||
// (DatabaseMetaData.getIdentifierQuoteString()) — pass through unquoted.
|
||||
if (databaseType === "jdbc") return name;
|
||||
if (databaseType === "bigquery") return `\`${name.replace(/`/g, "\\`")}\``;
|
||||
if (databaseType === "mysql" || databaseType === "clickhouse" || databaseType === "hive" || databaseType === "impala" || databaseType === "spark" || databaseType === "databend" || databaseType === "tdengine" || databaseType === "access" || databaseType === "doris" || databaseType === "starrocks")
|
||||
if (
|
||||
databaseType === "mysql" ||
|
||||
databaseType === "clickhouse" ||
|
||||
databaseType === "hive" ||
|
||||
databaseType === "kyuubi" ||
|
||||
databaseType === "impala" ||
|
||||
databaseType === "spark" ||
|
||||
databaseType === "databend" ||
|
||||
databaseType === "tdengine" ||
|
||||
databaseType === "access" ||
|
||||
databaseType === "doris" ||
|
||||
databaseType === "starrocks"
|
||||
)
|
||||
return `\`${name.replace(/`/g, "``")}\``;
|
||||
if (databaseType === "informix" && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(name)) return name;
|
||||
if (databaseType === "neo4j") return quoteCypherIdentifier(name);
|
||||
|
||||
@@ -1095,6 +1095,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
trino: "Trino",
|
||||
prestosql: "PrestoSQL",
|
||||
hive: "Hive",
|
||||
kyuubi: "Apache Kyuubi",
|
||||
impala: "Apache Impala",
|
||||
spark: "Apache Spark",
|
||||
db2: "DB2",
|
||||
|
||||
@@ -53,6 +53,7 @@ export type DatabaseType =
|
||||
| "trino"
|
||||
| "prestosql"
|
||||
| "hive"
|
||||
| "kyuubi"
|
||||
| "impala"
|
||||
| "spark"
|
||||
| "db2"
|
||||
|
||||
Reference in new issue
Block a user