From b4b04d30b8db2e94bdd5eec15fe95ca7c8989a44 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sun, 23 Aug 2026 22:00:42 +0800 Subject: [PATCH] fix(export): allow routine-only database exports Closes #6888 --- .../export/DatabaseExportDialog.vue | 17 ++++++++-- .../src/lib/export/databaseExportSelection.ts | 10 ++++++ packages/app-tests/databaseExportPlan.test.ts | 34 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/components/export/DatabaseExportDialog.vue b/apps/desktop/src/components/export/DatabaseExportDialog.vue index 7dfa01f860..8298bde53c 100644 --- a/apps/desktop/src/components/export/DatabaseExportDialog.vue +++ b/apps/desktop/src/components/export/DatabaseExportDialog.vue @@ -13,7 +13,7 @@ import type { ExportProgress } from "@/lib/backend/api"; import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport"; import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection } from "@/composables/useDatabaseOptions"; import { buildAllDatabaseExportPlan, generateDatabaseExportId, runDatabaseExportUntilTerminal, runWithDatabaseBackupSnapshot, shouldUseDatabaseBackupSnapshot, type AllDatabaseExportPlanItem } from "@/lib/export/databaseExport"; -import { buildSelectedTablesPayload } from "@/lib/export/databaseExportSelection"; +import { buildSelectedTablesPayload, isDatabaseExportTableSelectionValid } from "@/lib/export/databaseExportSelection"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; import { useToast } from "@/composables/useToast"; import { Input } from "@/components/ui/input"; @@ -109,7 +109,18 @@ const canExport = computed(() => { const hasContent = includeStructure.value || includeData.value || includeObjects.value; if (!connectionId.value || !hasContent || isExporting.value) return false; if (exportAllDatabases.value) return selectedDatabases.value.length > 0 && !loadingMeta.value; - return database.value && schema.value && !loadingTables.value && !tableError.value && (tables.value.length === 0 || selectedTables.value.length > 0); + return ( + database.value && + schema.value && + !loadingTables.value && + !tableError.value && + isDatabaseExportTableSelectionValid({ + allTableCount: tables.value.length, + selectedTableCount: selectedTables.value.length, + includeStructure: includeStructure.value, + includeData: includeData.value, + }) + ); }); const selectedTableSet = computed(() => new Set(selectedTables.value)); @@ -340,7 +351,7 @@ async function startExport() { database: database.value, schema: schema.value, filePath, - selectedTables: buildSelectedTablesPayload(tables.value, selectedTables.value), + selectedTables: includeStructure.value || includeData.value ? buildSelectedTablesPayload(tables.value, selectedTables.value) : undefined, includeStructure: includeStructure.value, includeData: includeData.value, includeObjects: includeObjects.value, diff --git a/apps/desktop/src/lib/export/databaseExportSelection.ts b/apps/desktop/src/lib/export/databaseExportSelection.ts index 5e7b9e19d5..15e18ec1ec 100644 --- a/apps/desktop/src/lib/export/databaseExportSelection.ts +++ b/apps/desktop/src/lib/export/databaseExportSelection.ts @@ -6,3 +6,13 @@ export function buildSelectedTablesPayload(allTables: string[], selectedTables: const selected = new Set(selectedTables); return allTables.filter((table) => selected.has(table)); } + +export function isDatabaseExportTableSelectionValid(options: { + allTableCount: number; + selectedTableCount: number; + includeStructure: boolean; + includeData: boolean; +}): boolean { + if (!options.includeStructure && !options.includeData) return true; + return options.allTableCount === 0 || options.selectedTableCount > 0; +} diff --git a/packages/app-tests/databaseExportPlan.test.ts b/packages/app-tests/databaseExportPlan.test.ts index d20c0ea5da..288a9108a6 100644 --- a/packages/app-tests/databaseExportPlan.test.ts +++ b/packages/app-tests/databaseExportPlan.test.ts @@ -1,6 +1,40 @@ import assert from "node:assert/strict"; import { test } from "vitest"; import { buildAllDatabaseExportPlan } from "../../apps/desktop/src/lib/export/databaseExport.ts"; +import { isDatabaseExportTableSelectionValid } from "../../apps/desktop/src/lib/export/databaseExportSelection.ts"; + +test("database export allows object-only exports without selecting tables", () => { + assert.equal( + isDatabaseExportTableSelectionValid({ + allTableCount: 982, + selectedTableCount: 0, + includeStructure: false, + includeData: false, + }), + true, + ); +}); + +test("database export still requires a table when exporting structure or data", () => { + assert.equal( + isDatabaseExportTableSelectionValid({ + allTableCount: 982, + selectedTableCount: 0, + includeStructure: true, + includeData: false, + }), + false, + ); + assert.equal( + isDatabaseExportTableSelectionValid({ + allTableCount: 982, + selectedTableCount: 1, + includeStructure: false, + includeData: true, + }), + true, + ); +}); test("all-database export includes every schema for schema-aware databases", () => { const plan = buildAllDatabaseExportPlan({