From 2f1a6875b70beac4a73751c70a78060ff6d5a0e3 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 1 Jun 2026 22:40:29 +0800 Subject: [PATCH] feat(sqlite): support loading extension libraries --- .../connection/ConnectionDialog.vue | 84 ++++++++++++++ apps/desktop/src/i18n/locales/en.ts | 5 + apps/desktop/src/i18n/locales/zh-CN.ts | 5 + apps/desktop/src/i18n/locales/zh-TW.ts | 5 + crates/dbx-core/Cargo.toml | 2 +- crates/dbx-core/src/connection.rs | 13 ++- crates/dbx-core/src/db/sqlite.rs | 108 +++++++++++++++++- src-tauri/src/commands/connection.rs | 30 ++++- 8 files changed, 239 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index f880346612..8133c69ad5 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -684,6 +684,12 @@ const filePathPlaceholder = computed(() => { return "/path/to/database.db or :memory:"; }); const supportsMemoryDatabasePath = computed(() => form.value.db_type === "sqlite" || form.value.db_type === "duckdb"); +const sqliteExtensionPaths = computed({ + get: () => sqliteExtensionPathsFromParams(form.value.url_params), + set: (value: string) => { + form.value.url_params = setSqliteExtensionPaths(form.value.url_params, value); + }, +}); const tlsCapableDatabaseTypes = new Set([ "mysql", "postgres", @@ -956,6 +962,29 @@ function getUrlParam(params: string | undefined, key: string): string { return parsed.get(key) || ""; } +function sqliteExtensionPathsFromParams(params: string | undefined): string { + const parsed = new URLSearchParams((params || "").trim().replace(/^\?/, "")); + return [ + ...parsed.getAll("sqlite_extension"), + ...parsed.getAll("sqlite_extensions").flatMap((value) => value.split(/\r?\n/)), + ] + .map((value) => value.trim()) + .filter(Boolean) + .join("\n"); +} + +function setSqliteExtensionPaths(params: string | undefined, paths: string): string { + const parsed = new URLSearchParams((params || "").trim().replace(/^\?/, "")); + parsed.delete("sqlite_extension"); + parsed.delete("sqlite_extensions"); + paths + .split(/\r?\n/) + .map((value) => value.trim()) + .filter(Boolean) + .forEach((value) => parsed.append("sqlite_extension", value)); + return parsed.toString(); +} + function setUrlParam(params: string | undefined, key: string, value: string): string { const parsed = new URLSearchParams((params || "").trim().replace(/^\?/, "")); const normalized = value.trim(); @@ -1462,6 +1491,32 @@ async function browseDbFilePath() { } } +async function browseSqliteExtensionPath() { + if (isTauriRuntime()) { + const { open } = await import("@tauri-apps/plugin-dialog"); + const selected = await open({ + title: t("connection.sqliteExtensionBrowse"), + multiple: true, + filters: [ + { name: "SQLite Extension", extensions: ["dylib", "so", "dll"] }, + { name: "All Files", extensions: ["*"] }, + ], + }); + const selectedPaths = Array.isArray(selected) + ? selected + : selected && typeof selected === "string" + ? [selected] + : []; + if (selectedPaths.length) { + const existing = sqliteExtensionPaths.value + .split(/\r?\n/) + .map((path) => path.trim()) + .filter(Boolean); + sqliteExtensionPaths.value = [...existing, ...selectedPaths].join("\n"); + } + } +} + function ensureDuckDbFileExtension(path: string): string { return /\.(duckdb|db)$/i.test(path) ? path : `${path}.duckdb`; } @@ -1890,6 +1945,35 @@ function openExternalUrl(url: string) {

+
+ +
+
+