From ed9dd1072e44c77bbbcbcf583bce0a81204367d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Wed, 29 Jul 2026 00:33:48 +0800 Subject: [PATCH] feat(desktop): show batch drop progress --- .../src/components/objects/ObjectBrowser.vue | 84 ++++++++++---- .../src/components/sidebar/ConnectionTree.vue | 13 ++- .../sidebar/SidebarTreeRuntimeHost.vue | 49 +++++++- .../__tests__/table/batchTableDrop.spec.ts | 75 +++++++++++++ apps/desktop/src/lib/backend/api.ts | 1 + apps/desktop/src/lib/backend/http.ts | 31 ++++++ apps/desktop/src/lib/backend/tauri.ts | 36 ++++++ .../src/lib/sidebar/sidebarDangerDialog.ts | 6 + apps/desktop/src/lib/table/batchTableDrop.ts | 65 +++++++++++ crates/dbx-core/src/query.rs | 105 ++++++++++++++++-- src-tauri/src/commands/query.rs | 25 ++++- 11 files changed, 453 insertions(+), 37 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/table/batchTableDrop.spec.ts create mode 100644 apps/desktop/src/lib/table/batchTableDrop.ts diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 289ed5cb5..8a27675fc 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -93,6 +93,7 @@ import { isCancelSearchShortcut } from "@/lib/editor/keyboardShortcuts"; import { executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard"; import { formatShortcut } from "@/lib/editor/shortcutRegistry"; import { batchTableEmptyFeedback, buildBatchTableEmptyPlan, runBatchTableEmpty, type BatchTableEmptyPlanItem } from "@/lib/sidebar/batchTableEmpty"; +import { runBatchTableDrop } from "@/lib/table/batchTableDrop"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { buildObjectBrowserRows, @@ -224,6 +225,8 @@ const procedureExecutionTarget = ref(null); const selectedTableIds = ref>(new Set()); const expandedPartitionParentIds = ref>(new Set()); const showBatchDropConfirm = ref(false); +const batchDropExecuting = ref(false); +const batchDropProgress = ref({ completed: 0, total: 0 }); const batchDropPreviewSql = ref(""); const showBatchTruncateConfirm = ref(false); const batchTruncatePreviewSql = ref(""); @@ -544,6 +547,7 @@ const selectedTableCount = computed(() => selectedTableRows.value.length); const canBatchDropCascade = computed(() => selectedTableCount.value > 0 && supportsDropTableCascade(effectiveDatabaseType.value)); const canBatchTruncateCascade = computed(() => selectedTableCount.value > 0 && supportsTruncateTableCascade(effectiveDatabaseType.value)); const allVisibleTablesSelected = computed(() => visibleSelectableRows.value.length > 0 && visibleSelectableRows.value.every((row) => selectedTableIds.value.has(row.id))); +const batchDropProgressPercent = computed(() => (batchDropProgress.value.total > 0 ? Math.round((batchDropProgress.value.completed / batchDropProgress.value.total) * 100) : 0)); function iconFor(row: ObjectBrowserRow) { if (row.type === "VIEW" || row.type === "MATERIALIZED_VIEW") return Eye; @@ -1514,37 +1518,55 @@ async function refreshBatchDropPreviewSql() { function requestBatchDropTables() { if (selectedTableCount.value === 0) return; batchDropCascade.value = false; + batchDropProgress.value = { completed: 0, total: 0 }; batchDropPreviewSql.value = ""; void refreshBatchDropPreviewSql(); showBatchDropConfirm.value = true; } async function confirmBatchDropTables() { - const targets = await fetchSortedTableRowsForDrop(); - if (targets.length === 0) return; + if (batchDropExecuting.value) return; + batchDropExecuting.value = true; try { + const targets = await fetchSortedTableRowsForDrop(); + if (targets.length === 0) return; + batchDropProgress.value = { completed: 0, total: targets.length }; const useCascade = canBatchDropCascade.value && batchDropCascade.value; - const statements = await Promise.all( - targets.map(async (row) => ({ - row, - sql: await buildDropTableSql(tableAdminSqlOptions(row, { cascade: useCascade })), + const plan = await Promise.all( + targets.map(async (target) => ({ + target, + sql: await buildDropTableSql(tableAdminSqlOptions(target, { cascade: useCascade })), })), ); - const executed = await executeObjectBrowserSqlWithProductionGuard(statements.map(({ sql }) => sql).join(";\n"), async () => { - for (const { row, sql } of statements) { - await api.executeQuery(props.connection.id, props.database, sql); - closeDroppedTableObjectTabsForRow(row); - } - return true; - }); - if (!executed) return; - toast(t("objects.batchDropSuccess", { count: targets.length })); - removePinnedObjectBrowserRows(targets); - clearTableSelection(); - await reload(); - await connectionStore.refreshObjectListTreeNode(props.connection.id, props.database, selectedSchema.value); + const batchSql = plan.map(({ sql }) => sql).join(";\n"); + const result = await executeObjectBrowserSqlWithProductionGuard(batchSql, () => + runBatchTableDrop({ + databaseType: effectiveDatabaseType.value, + plan, + executeStatement: (sql) => api.executeQuery(props.connection.id, props.database, sql), + executeBatch: (sql, onProgress) => api.executeMultiWithProgress(props.connection.id, props.database, sql, onProgress), + onProgress: (progress) => { + batchDropProgress.value = { completed: Math.min(progress.completed, targets.length), total: targets.length }; + }, + }), + ); + if (!result) return; + + for (const row of result.succeeded) closeDroppedTableObjectTabsForRow(row); + if (result.succeeded.length > 0) { + removePinnedObjectBrowserRows(result.succeeded); + await reload(); + await connectionStore.refreshObjectListTreeNode(props.connection.id, props.database, selectedSchema.value); + } + + if (result.failed) throw result.failed; + toast(t("objects.batchDropSuccess", { count: result.succeeded.length })); } catch (e: any) { toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } finally { + batchDropExecuting.value = false; + batchDropProgress.value = { completed: 0, total: 0 }; + showBatchDropConfirm.value = false; } } @@ -3072,9 +3094,27 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] { - -