feat(h2): bundle multi-version JDBC drivers
This commit is contained in:
18 files changed
+1358
-113
No files matched your search
@@ -1024,13 +1024,14 @@ const jdbcDriverSelectItems = computed<JdbcDriverSelectItem[]>(() => {
|
||||
});
|
||||
|
||||
const jdbcDriverSelectItemById = computed(() => new Map(jdbcDriverSelectItems.value.map((item) => [item.id, item])));
|
||||
const jdbcManualClasspathCount = computed(
|
||||
() =>
|
||||
jdbcDriverPathsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean).length,
|
||||
);
|
||||
const jdbcManualClasspathCount = computed(() => parsedJdbcDriverPaths().length);
|
||||
|
||||
function parsedJdbcDriverPaths(): string[] {
|
||||
return jdbcDriverPathsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function applyCustomColor(value: string) {
|
||||
form.value.color = value;
|
||||
@@ -2897,6 +2898,21 @@ function switchH2ConnectionMode(mode: H2ConnectionMode) {
|
||||
resetTestState();
|
||||
}
|
||||
|
||||
function switchH2DriverProfile(profile: "h2" | "h2-v1" | "h2-v2" | "h2-v3" | "h2-custom") {
|
||||
form.value.driver_profile = profile;
|
||||
if (profile === "h2-custom") {
|
||||
form.value.jdbc_driver_class = form.value.jdbc_driver_class?.trim() || "org.h2.Driver";
|
||||
jdbcManualClasspathOpen.value = true;
|
||||
} else {
|
||||
form.value.jdbc_driver_class = undefined;
|
||||
form.value.jdbc_driver_paths = [];
|
||||
jdbcDriverPathsInput.value = "";
|
||||
selectedJdbcDriverPath.value = "";
|
||||
jdbcManualClasspathOpen.value = false;
|
||||
}
|
||||
resetTestState();
|
||||
}
|
||||
|
||||
function isH2FileJdbcUrlLikePath(value: string): boolean {
|
||||
return /\.(mv|h2)\.db$/i.test(value.trim()) || value.includes("/") || value.includes("\\");
|
||||
}
|
||||
@@ -3234,6 +3250,7 @@ const jdbcxHighPrivilegeExtensionsAllowed = computed({
|
||||
});
|
||||
const supportsNativeAgentJdbcDriverConfig = computed(() => supportsNativeAgentJdbcDriverConfigType(form.value.db_type));
|
||||
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));
|
||||
|
||||
const connectionUrlPlaceholder = computed(() => getUrlPlaceholder(form.value.db_type));
|
||||
@@ -4296,16 +4313,20 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
|
||||
config.jdbc_driver_class = GAUSSDB_M_JDBC_DRIVER_CLASS;
|
||||
}
|
||||
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || undefined;
|
||||
config.jdbc_driver_paths = jdbcDriverPathsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((path) => path.trim())
|
||||
.filter(Boolean);
|
||||
config.jdbc_driver_paths = parsedJdbcDriverPaths();
|
||||
} else if (config.db_type === "gaussdb") {
|
||||
config.connection_string = undefined;
|
||||
config.jdbc_driver_class = undefined;
|
||||
config.jdbc_driver_paths = [];
|
||||
}
|
||||
if (config.db_type === "h2") {
|
||||
if (config.driver_profile === "h2-custom") {
|
||||
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || "org.h2.Driver";
|
||||
config.jdbc_driver_paths = parsedJdbcDriverPaths();
|
||||
} else {
|
||||
config.jdbc_driver_class = undefined;
|
||||
config.jdbc_driver_paths = [];
|
||||
}
|
||||
const h2Mode = connectionUrlInput.value.trim() ? h2ConnectionModeForConfig(config) : h2ConnectionMode.value;
|
||||
if (h2Mode === "file") {
|
||||
const jdbcFilePath = h2FilePathFromJdbcUrl(config.connection_string);
|
||||
@@ -6032,28 +6053,52 @@ function openExternalUrl(url: string) {
|
||||
|
||||
<div v-if="form.db_type === 'h2'" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelSmallClass">Driver</Label>
|
||||
<div class="col-span-3 flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
:variant="form.driver_profile !== 'h2-legacy' ? 'default' : 'outline'"
|
||||
@click="
|
||||
form.driver_profile = 'h2';
|
||||
resetTestState();
|
||||
"
|
||||
>H2 2.3</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
:variant="form.driver_profile === 'h2-legacy' ? 'default' : 'outline'"
|
||||
@click="
|
||||
form.driver_profile = 'h2-legacy';
|
||||
resetTestState();
|
||||
"
|
||||
>H2 2.1 Legacy</Button
|
||||
>
|
||||
<div class="col-span-3 flex flex-wrap gap-2">
|
||||
<Button size="sm" :variant="!form.driver_profile || form.driver_profile === 'h2' || form.driver_profile === 'h2-auto' ? 'default' : 'outline'" @click="switchH2DriverProfile('h2')">Auto</Button>
|
||||
<Button size="sm" :variant="form.driver_profile === 'h2-v1' ? 'default' : 'outline'" @click="switchH2DriverProfile('h2-v1')">H2 1.x</Button>
|
||||
<Button size="sm" :variant="form.driver_profile === 'h2-v2' || form.driver_profile === 'h2-legacy' ? 'default' : 'outline'" @click="switchH2DriverProfile('h2-v2')">H2 2.0–2.1</Button>
|
||||
<Button size="sm" :variant="form.driver_profile === 'h2-v3' ? 'default' : 'outline'" @click="switchH2DriverProfile('h2-v3')">H2 2.2+</Button>
|
||||
<Button size="sm" :variant="form.driver_profile === 'h2-custom' ? 'default' : 'outline'" @click="switchH2DriverProfile('h2-custom')">Custom JAR</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="isH2CustomDriver">
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelTopClass">{{ t("connection.jdbcDriverPaths") }}</Label>
|
||||
<div class="col-span-3 space-y-2">
|
||||
<Select v-if="jdbcDriverSelectItems.length > 0" :model-value="selectedJdbcDriverPath" @update:model-value="onJdbcDriverSelect">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('connection.jdbcDriverSelectPlaceholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="driver in jdbcDriverSelectItems" :key="driver.id" :value="driver.id">
|
||||
{{ driver.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="flex items-start gap-1">
|
||||
<textarea
|
||||
v-model="jdbcDriverPathsInput"
|
||||
class="flex min-h-12 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
:placeholder="t('connection.jdbcDriverPathsPlaceholder')"
|
||||
/>
|
||||
<Tooltip v-if="isDesktop">
|
||||
<TooltipTrigger as-child>
|
||||
<Button type="button" variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="browseJdbcDriverPaths">
|
||||
<FolderOpen class="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t("connection.jdbcDriverBrowse") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.jdbcDriverClass") }}</Label>
|
||||
<Input v-model="form.jdbc_driver_class" class="col-span-3" placeholder="org.h2.Driver" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="h2DriverMissing" class="grid grid-cols-4 items-center gap-4">
|
||||
<span />
|
||||
<p class="col-span-3 text-xs text-muted-foreground">
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const categoriesSource = readFileSync(new URL("../../connection/driver-category-definitions.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("H2 connection dialog driver profiles", () => {
|
||||
it("offers auto, bundled compatibility ranges, and a custom JAR fallback", () => {
|
||||
expect(dialogSource).toContain("@click=\"switchH2DriverProfile('h2')\"");
|
||||
expect(dialogSource).toContain("@click=\"switchH2DriverProfile('h2-v1')\"");
|
||||
expect(dialogSource).toContain("@click=\"switchH2DriverProfile('h2-v2')\"");
|
||||
expect(dialogSource).toContain("@click=\"switchH2DriverProfile('h2-v3')\"");
|
||||
expect(dialogSource).toContain("@click=\"switchH2DriverProfile('h2-custom')\"");
|
||||
expect(dialogSource).toContain(">Custom JAR</Button>");
|
||||
});
|
||||
|
||||
it("reuses the JDBC classpath picker only for the custom profile", () => {
|
||||
expect(dialogSource).toContain('const isH2CustomDriver = computed(() => form.value.db_type === "h2" && form.value.driver_profile === "h2-custom");');
|
||||
expect(dialogSource).toContain('<template v-if="isH2CustomDriver">');
|
||||
expect(dialogSource).toContain('v-model="jdbcDriverPathsInput"');
|
||||
expect(dialogSource).toContain('@click="browseJdbcDriverPaths"');
|
||||
expect(dialogSource).toContain('placeholder="org.h2.Driver"');
|
||||
});
|
||||
|
||||
it("defaults the custom class and clears stale classpath data for bundled profiles", () => {
|
||||
const switchBlock = dialogSource.match(/function switchH2DriverProfile[\s\S]*?\n\}/)?.[0] ?? "";
|
||||
expect(switchBlock).toContain('form.value.jdbc_driver_class = form.value.jdbc_driver_class?.trim() || "org.h2.Driver";');
|
||||
expect(switchBlock).toContain("form.value.jdbc_driver_paths = [];");
|
||||
expect(switchBlock).toContain('jdbcDriverPathsInput.value = "";');
|
||||
|
||||
const submitBlock = dialogSource.match(/if \(config\.db_type === "h2"\) \{[\s\S]*?const h2Mode/)?.[0] ?? "";
|
||||
expect(submitBlock).toContain('config.driver_profile === "h2-custom"');
|
||||
expect(submitBlock).toContain("config.jdbc_driver_paths = parsedJdbcDriverPaths();");
|
||||
expect(submitBlock).toContain("config.jdbc_driver_paths = [];");
|
||||
});
|
||||
|
||||
it("keeps legacy profile hydration without exposing a second H2 catalog entry", () => {
|
||||
expect(dialogSource).toContain('"h2-legacy": { type: "h2"');
|
||||
expect(categoriesSource).toContain('h2: "lightweight"');
|
||||
expect(categoriesSource).not.toContain('"h2-legacy": "lightweight"');
|
||||
});
|
||||
});
|
||||
@@ -118,7 +118,6 @@ describe("AGENT_DRIVER_CATEGORY_MAP integrity", () => {
|
||||
"access",
|
||||
"duckdb",
|
||||
"h2",
|
||||
"h2-legacy",
|
||||
// document
|
||||
"cassandra",
|
||||
"mongodb",
|
||||
|
||||
@@ -26,6 +26,7 @@ export function hasInstalledAgentVersion(drivers: readonly AgentDriverInstallSta
|
||||
export function agentDriverInstallKey(dbType: DatabaseType | undefined, driverProfile?: string): string | undefined {
|
||||
if (dbType === "kyuubi" || dbType === "impala") return "hive";
|
||||
if (dbType === "oracle") return "oracle";
|
||||
if (dbType === "h2") return "h2";
|
||||
if (dbType === "mongodb") return "mongodb";
|
||||
if (dbType === "dameng") return "dameng";
|
||||
if (dbType === "gbase") return driverProfile === "gbase8s" ? "gbase8s" : "gbase8a";
|
||||
|
||||
@@ -32,7 +32,6 @@ export const AGENT_DRIVER_CATEGORY_MAP: Readonly<Record<string, DriverCategoryKe
|
||||
gbase8s: "domestic",
|
||||
goldendb: "domestic",
|
||||
h2: "lightweight",
|
||||
"h2-legacy": "lightweight",
|
||||
highgo: "domestic",
|
||||
hive: "analytics",
|
||||
influxdb: "timeseries",
|
||||
|
||||
Reference in new issue
Block a user