feat: export entire database as SQL file

Add a new "Export Database" feature accessible from the sidebar
right-click menu on database/schema nodes. Generates a .sql file
containing CREATE TABLE DDL, batched INSERT statements, and
view/procedure/function source code.

Backend (database_export.rs):
- Stream-writes SQL to file with configurable batch size
- Supports all SQL database types via existing DDL/INSERT generators
- Progress events with cancellation support
- Graceful error handling (continues on per-table failures)

Frontend (DatabaseExportDialog.vue):
- Connection/database/schema selector
- Checkboxes: structure, data, objects
- Real-time progress with current table and row count
- Cancel button

Wired up through Tauri commands, Web SSE routes, and API layer.
This commit is contained in:
t8y2
2026-05-13 01:08:16 +08:00
parent 83216b2765
commit 9ba2976d11
14 changed files with 1134 additions and 139 deletions
+6
View File
@@ -98,6 +98,10 @@ export const previewTableImportFile = forward("previewTableImportFile");
export const importTableFile = forward("importTableFile");
export const cancelTableImport = forward("cancelTableImport");
// Database Export
export const exportDatabaseSql = forward("exportDatabaseSql");
export const cancelDatabaseExport = forward("cancelDatabaseExport");
// Redis
export const redisListDatabases = forward("redisListDatabases");
export const redisScanKeys = forward("redisScanKeys");
@@ -172,4 +176,6 @@ export type {
TableImportRequest,
TableImportSummary,
TableImportProgress,
DatabaseExportRequest,
ExportProgress,
} from "./tauri";
+40
View File
@@ -38,6 +38,8 @@ import type {
TableImportRequest,
TableImportSummary,
TableImportProgress,
DatabaseExportRequest,
ExportProgress,
} from "./tauri";
// ---------------------------------------------------------------------------
@@ -497,6 +499,44 @@ export async function cancelTableImport(importId: string): Promise<boolean> {
return post("/api/import/cancel", { importId });
}
// ---------------------------------------------------------------------------
// Database Export
// ---------------------------------------------------------------------------
export async function exportDatabaseSql(
request: DatabaseExportRequest,
onProgress: (progress: ExportProgress) => void,
): Promise<void> {
// 1. POST to start the export
const res = await fetch("/api/export/database", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request }),
});
if (!res.ok) throw new Error(await res.text());
// 2. SSE to listen for progress
return new Promise((resolve, reject) => {
const es = new EventSource(`/api/export/database/progress/${request.exportId}`);
es.onmessage = (e) => {
const progress: ExportProgress = JSON.parse(e.data);
onProgress(progress);
if (progress.status === "Done" || progress.status === "Error" || progress.status === "Cancelled") {
es.close();
resolve();
}
};
es.onerror = () => {
es.close();
reject(new Error("Export SSE connection failed"));
};
});
}
export async function cancelDatabaseExport(exportId: string): Promise<void> {
await post("/api/export/database/cancel", { exportId });
}
// ---------------------------------------------------------------------------
// Redis
// ---------------------------------------------------------------------------
+48
View File
@@ -744,3 +744,51 @@ export async function importTableFile(
export async function cancelTableImport(importId: string): Promise<boolean> {
return invoke("cancel_table_import", { importId });
}
// --- Database Export ---
export interface DatabaseExportRequest {
exportId: string;
connectionId: string;
database: string;
schema: string;
filePath: string;
includeStructure: boolean;
includeData: boolean;
includeObjects: boolean;
batchSize: number;
}
export interface ExportProgress {
exportId: string;
currentObject: string;
objectIndex: number;
totalObjects: number;
rowsExported: number;
totalRows: number | null;
status: "Running" | "Done" | "Error" | "Cancelled";
error: string | null;
}
export async function exportDatabaseSql(
request: DatabaseExportRequest,
onProgress: (progress: ExportProgress) => void,
): Promise<void> {
const unlisten: UnlistenFn = await listen<ExportProgress>("database-export-progress", (event) => {
if (event.payload.exportId === request.exportId) {
onProgress(event.payload);
if (event.payload.status === "Done" || event.payload.status === "Error" || event.payload.status === "Cancelled") {
unlisten();
}
}
});
try {
await invoke("export_database_sql", { request });
} catch (e) {
unlisten();
throw e;
}
}
export async function cancelDatabaseExport(exportId: string): Promise<void> {
await invoke("cancel_database_export", { exportId });
}