feat(sqlite): support custom database file suffixes

This commit is contained in:
t8y2 committed 2026-06-23 20:15:06 +08:00
1 parent 25bac0c67a
commit 49705a1a8f
10 files changed
+138 -31

No files matched your search

+2 -10
View File
@@ -69,6 +69,7 @@ import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/safeStorage";
import { rankSavedSqlHistory } from "@/lib/savedSqlHistory";
import { isSchemaAware, isSingleDatabase, usesTreeSchemaMode } from "@/lib/databaseFeatureSupport";
import { connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect";
import { detectDatabaseFileType } from "@/lib/databaseFileDetection";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -624,21 +625,12 @@ async function openPendingSqlFiles() {
}
}
const DB_EXTENSIONS = [".db", ".db3", ".sqlite", ".sqlite3", ".duckdb"];
function getDbTypeFromPath(path: string): "sqlite" | "duckdb" | null {
const lower = path.toLowerCase();
if (lower.endsWith(".duckdb")) return "duckdb";
if (DB_EXTENSIONS.some((ext) => lower.endsWith(ext))) return "sqlite";
return null;
}
async function openDbFilePath(path: string) {
if (!isTauriRuntime()) return;
await connectionStore.initFromDisk();
try {
const name = path.split("/").pop()?.split("\\").pop() || path;
const dbType = getDbTypeFromPath(path);
const dbType = await detectDatabaseFileType(path);
if (!dbType) return;
// Check for existing connection with the same file path
@@ -31,6 +31,7 @@ import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/m
import { copyToClipboard } from "@/lib/clipboard";
import { showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint";
import { prestoSqlBuiltinDriverPaths } from "@/lib/prestoSqlBuiltinDriver";
import { SQLITE_DATABASE_FILE_EXTENSIONS } from "@/lib/databaseFileDetection";
import { ArrowLeft, ArrowDown, ArrowUp, CheckSquare, ChevronRight, CircleHelp, Copy, ExternalLink, FilePlus2, FolderOpen, GripVertical, Grid3X3, KeyRound, Link2, List, ListFilter, Loader2, Pipette, Plus, Search, ShieldCheck, Square, Trash2 } from "@lucide/vue";
import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleDatabaseSelectionIsStale } from "@/lib/connectionVisibleDatabases";
import { canSaveVisibleDatabaseSelection, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
@@ -2158,18 +2159,11 @@ async function browseEtcdTlsFile(target: "ca" | "cert" | "key") {
async function browseDbFilePath() {
if (isTauriRuntime()) {
const { open } = await import("@tauri-apps/plugin-dialog");
const filters =
form.value.db_type === "duckdb"
? [{ name: "DuckDB", extensions: ["duckdb", "db"] }]
: form.value.db_type === "access"
? [{ name: "Microsoft Access", extensions: ["accdb", "mdb"] }]
: form.value.db_type === "h2"
? [{ name: "H2", extensions: ["db"] }]
: [{ name: "SQLite", extensions: ["db", "db3", "sqlite", "sqlite3"] }];
const filters = form.value.db_type === "duckdb" ? [{ name: "DuckDB", extensions: ["duckdb", "db"] }] : form.value.db_type === "access" ? [{ name: "Microsoft Access", extensions: ["accdb", "mdb"] }] : form.value.db_type === "h2" ? [{ name: "H2", extensions: ["db"] }] : undefined;
const selected = await open({
title: "Select Database File",
multiple: false,
filters,
...(filters ? { filters } : {}),
});
if (selected && typeof selected === "string") {
form.value.host = selected;
@@ -2218,7 +2212,8 @@ async function createDuckDbFilePath() {
}
function ensureSqliteFileExtension(path: string): string {
return /\.(db|db3|sqlite|sqlite3)$/i.test(path) ? path : `${path}.db`;
const extensionPattern = new RegExp(`\\.(${SQLITE_DATABASE_FILE_EXTENSIONS.join("|")})$`, "i");
return extensionPattern.test(path) ? path : `${path}.db`;
}
async function createSqliteFilePath() {
@@ -2227,7 +2222,7 @@ async function createSqliteFilePath() {
const selected = await save({
title: t("connection.createSqliteFile"),
defaultPath: "database.db",
filters: [{ name: "SQLite", extensions: ["db", "db3", "sqlite", "sqlite3"] }],
filters: [{ name: "SQLite", extensions: SQLITE_DATABASE_FILE_EXTENSIONS }],
});
if (!selected) return;
+2 -10
View File
@@ -6,15 +6,7 @@ import { useQueryStore } from "@/stores/queryStore";
import { useToast } from "@/composables/useToast";
import * as api from "@/lib/api";
import type { ConnectionConfig } from "@/types/database";
const DB_EXTENSIONS = [".db", ".db3", ".sqlite", ".sqlite3", ".duckdb"];
function getDbType(path: string): "sqlite" | "duckdb" | null {
const lower = path.toLowerCase();
if (lower.endsWith(".duckdb")) return "duckdb";
if (DB_EXTENSIONS.some((ext) => lower.endsWith(ext))) return "sqlite";
return null;
}
import { detectDatabaseFileType } from "@/lib/databaseFileDetection";
function isSqlFilePath(path: string): boolean {
return /\.sql$/i.test(path);
@@ -81,7 +73,7 @@ export function useFileDrop() {
continue;
}
const dbType = getDbType(path);
const dbType = await detectDatabaseFileType(path);
if (!dbType) continue;
const config: ConnectionConfig = {
id: uuid(),
+1
View File
@@ -99,6 +99,7 @@ export const deleteSavedSqlFile = forward("deleteSavedSqlFile");
export const savedSqlStorageDir = forward("savedSqlStorageDir");
export const openSavedSqlStorageDir = forward("openSavedSqlStorageDir");
export const revealPathInFileManager = forward("revealPathInFileManager");
export const isSqliteDatabaseFile = forward("isSqliteDatabaseFile");
export const backupSqliteDatabase = forward("backupSqliteDatabase");
export const syncSavedSqlDirectory = forward("syncSavedSqlDirectory");
@@ -0,0 +1,18 @@
import * as api from "@/lib/api";
export const SQLITE_DATABASE_FILE_EXTENSIONS = ["db", "db3", "sqlite", "sqlite3", "sqlitedb"];
const SQLITE_DATABASE_EXTENSION_SET = new Set(SQLITE_DATABASE_FILE_EXTENSIONS.map((extension) => `.${extension}`));
export function databaseTypeFromKnownExtension(path: string): "sqlite" | "duckdb" | null {
const lower = path.toLowerCase();
if (lower.endsWith(".duckdb")) return "duckdb";
if ([...SQLITE_DATABASE_EXTENSION_SET].some((extension) => lower.endsWith(extension))) return "sqlite";
return null;
}
export async function detectDatabaseFileType(path: string): Promise<"sqlite" | "duckdb" | null> {
const knownType = databaseTypeFromKnownExtension(path);
if (knownType) return knownType;
return (await api.isSqliteDatabaseFile(path).catch(() => false)) ? "sqlite" : null;
}
+4
View File
@@ -403,6 +403,10 @@ export async function revealPathInFileManager(_path: string): Promise<void> {
throw new Error("Reveal in file manager is only available in the desktop app.");
}
export async function isSqliteDatabaseFile(_path: string): Promise<boolean> {
return false;
}
export async function backupSqliteDatabase(_connectionId: string, _destinationPath: string): Promise<void> {
throw new Error("SQLite backup is only available in the desktop app.");
}
+4
View File
@@ -1044,6 +1044,10 @@ export async function revealPathInFileManager(path: string): Promise<void> {
return invoke("reveal_path_in_file_manager", { path });
}
export async function isSqliteDatabaseFile(path: string): Promise<boolean> {
return invoke("is_sqlite_database_file", { path });
}
export async function backupSqliteDatabase(connectionId: string, destinationPath: string): Promise<void> {
return invoke("backup_sqlite_database", { connectionId, destinationPath });
}