From b76efa56d6977aaf354f02aead8726a6ae3eabcf Mon Sep 17 00:00:00 2001
From: t8y2 <1156263951@qq.com>
Date: Tue, 19 May 2026 16:34:08 +0800
Subject: [PATCH] feat(duckdb): create database file from connection form
---
.../connection/ConnectionDialog.vue | 44 ++++++++++++++++++-
apps/desktop/src/i18n/locales/en.ts | 1 +
apps/desktop/src/i18n/locales/es.ts | 1 +
apps/desktop/src/i18n/locales/zh-CN.ts | 1 +
.../connectionDialogEditOpen.test.ts | 8 ++++
5 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue
index 51138634a..0c4f26ea7 100644
--- a/apps/desktop/src/components/connection/ConnectionDialog.vue
+++ b/apps/desktop/src/components/connection/ConnectionDialog.vue
@@ -19,7 +19,18 @@ import { applyParsedConnectionUrl, parseConnectionUrl } from "@/lib/connectionUr
import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connectionPresentation";
import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions";
import { showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint";
-import { ArrowLeft, ChevronRight, Copy, ExternalLink, FolderOpen, Grid3X3, Link2, List, Search } from "lucide-vue-next";
+import {
+ ArrowLeft,
+ ChevronRight,
+ Copy,
+ ExternalLink,
+ FilePlus2,
+ FolderOpen,
+ Grid3X3,
+ Link2,
+ List,
+ Search,
+} from "lucide-vue-next";
type DbOption = { value: string; label: string };
type DbCategory = { key: string; title: string; options: DbOption[] };
@@ -770,6 +781,24 @@ async function browseDbFilePath() {
}
}
+function ensureDuckDbFileExtension(path: string): string {
+ return /\.(duckdb|db)$/i.test(path) ? path : `${path}.duckdb`;
+}
+
+async function createDuckDbFilePath() {
+ if (!isTauriRuntime()) return;
+ const { save } = await import("@tauri-apps/plugin-dialog");
+ const selected = await save({
+ title: t("connection.createDuckDbFile"),
+ defaultPath: "database.duckdb",
+ filters: [{ name: "DuckDB", extensions: ["duckdb", "db"] }],
+ });
+ if (!selected) return;
+
+ const path = ensureDuckDbFileExtension(selected);
+ form.value.host = path;
+}
+
async function browseJdbcDriverPaths() {
if (!isTauriRuntime()) return;
const { open } = await import("@tauri-apps/plugin-dialog");
@@ -1138,6 +1167,19 @@ function openExternalUrl(url: string) {
{{ t("connection.memoryDatabasePathHint") }} diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 0684185d6..beb14adb5 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -97,6 +97,7 @@ export default { type: "Type", host: "Host", filePath: "File Path", + createDuckDbFile: "Create DuckDB file", memoryDatabasePathHint: "Use :memory: to create an in-memory SQLite or DuckDB database.", user: "User", password: "Password", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 3f2f9f3ce..bc21f90fc 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -97,6 +97,7 @@ export default { type: "Tipo", host: "Host", filePath: "Ruta del archivo", + createDuckDbFile: "Crear archivo DuckDB", user: "Usuario", password: "Contraseña", database: "Base de datos", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 0b09a0586..2cb5f979d 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -96,6 +96,7 @@ export default { type: "类型", host: "主机", filePath: "文件路径", + createDuckDbFile: "新建 DuckDB 文件", memoryDatabasePathHint: "输入 :memory: 可创建 SQLite 或 DuckDB 内存数据库。", user: "用户名", password: "密码", diff --git a/packages/app-tests/connectionDialogEditOpen.test.ts b/packages/app-tests/connectionDialogEditOpen.test.ts index 3da2909bf..b5611543b 100644 --- a/packages/app-tests/connectionDialogEditOpen.test.ts +++ b/packages/app-tests/connectionDialogEditOpen.test.ts @@ -28,3 +28,11 @@ test("connection dialog initializes edit form on first mount", () => { test("connection dialog maps legacy Dameng configs to the DM profile", () => { assert.match(connectionDialogSource, /if \(config\.db_type === "dameng"\) return "dm";/); }); + +test("connection dialog offers DuckDB file creation from the new connection form", () => { + assert.match(connectionDialogSource, /async function createDuckDbFilePath\(\)/); + assert.match(connectionDialogSource, /const \{ save \} = await import\("@tauri-apps\/plugin-dialog"\);/); + assert.match(connectionDialogSource, /form\.value\.host = path;/); + assert.match(connectionDialogSource, /form\.db_type === 'duckdb'/); + assert.match(connectionDialogSource, /@click="createDuckDbFilePath"/); +});