fix(export): release db backup snapshot only on terminal state
This commit is contained in:
parent
f9e93b356f
commit
7d2a5d3355
|
|
@ -12,7 +12,7 @@ import * as api from "@/lib/backend/api";
|
|||
import type { ExportProgress } from "@/lib/backend/api";
|
||||
import { isSchemaAware } from "@/lib/database/databaseFeatureSupport";
|
||||
import { databaseOptionsForConnection } from "@/composables/useDatabaseOptions";
|
||||
import { buildAllDatabaseExportPlan, generateDatabaseExportId, runDatabaseExportUntilTerminal, runWithDatabaseBackupSnapshot, type AllDatabaseExportPlanItem } from "@/lib/export/databaseExport";
|
||||
import { buildAllDatabaseExportPlan, generateDatabaseExportId, runDatabaseExportUntilTerminal, runWithDatabaseBackupSnapshot, shouldUseDatabaseBackupSnapshot, type AllDatabaseExportPlanItem } from "@/lib/export/databaseExport";
|
||||
import { buildSelectedTablesPayload } from "@/lib/export/databaseExportSelection";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
|
|
@ -304,7 +304,7 @@ async function startExport() {
|
|||
{
|
||||
connectionId: connectionId.value,
|
||||
database: database.value,
|
||||
enabled: includeData.value && (connectionType === "mysql" || connectionType === "postgres"),
|
||||
enabled: shouldUseDatabaseBackupSnapshot(connectionType, includeData.value, isTauriRuntime()),
|
||||
},
|
||||
async (snapshotSessionId) => {
|
||||
const request: api.DatabaseExportRequest = {
|
||||
|
|
@ -323,7 +323,7 @@ async function startExport() {
|
|||
snapshotSessionId,
|
||||
batchSize: 1000,
|
||||
};
|
||||
await api.exportDatabaseSql(request, (progress) => {
|
||||
return runDatabaseExportUntilTerminal(request, (progress) => {
|
||||
exportProgress.value = { ...progress };
|
||||
updateDatabaseExportTask(progress.exportId, progress);
|
||||
if (progress.status === "Done") {
|
||||
|
|
@ -342,7 +342,7 @@ async function startExport() {
|
|||
}
|
||||
});
|
||||
},
|
||||
() => !exportError.value && !exportCancelled.value,
|
||||
(terminal) => terminal.status === "Done",
|
||||
);
|
||||
} catch (e: any) {
|
||||
exportError.value = e?.message || String(e);
|
||||
|
|
@ -438,7 +438,7 @@ async function startAllDatabasesExport() {
|
|||
{
|
||||
connectionId: connectionId.value,
|
||||
database: item.database,
|
||||
enabled: includeData.value && (connectionType === "mysql" || connectionType === "postgres"),
|
||||
enabled: shouldUseDatabaseBackupSnapshot(connectionType, includeData.value, isTauriRuntime()),
|
||||
},
|
||||
(snapshotSessionId) =>
|
||||
runDatabaseExportUntilTerminal(
|
||||
|
|
|
|||
|
|
@ -2423,7 +2423,7 @@ export default withEnglishFallback({
|
|||
changeOpenMode: "Modificar modo de apertura",
|
||||
closeRightTabs: "Cerrar pestañas a la derecha",
|
||||
compileObject: "Compilar",
|
||||
compileObjectSuccess: "\"{name}\" compilado con éxito",
|
||||
compileObjectSuccess: '"{name}" compilado con éxito',
|
||||
},
|
||||
visibleDatabases: {
|
||||
title: "Bases de datos visibles",
|
||||
|
|
|
|||
|
|
@ -2421,7 +2421,7 @@ export default withEnglishFallback({
|
|||
changeOpenMode: "Modifica modalità apertura",
|
||||
closeRightTabs: "Chiudi le schede a destra",
|
||||
compileObject: "Compila",
|
||||
compileObjectSuccess: "\"{name}\" compilato con successo",
|
||||
compileObjectSuccess: '"{name}" compilato con successo',
|
||||
},
|
||||
visibleDatabases: {
|
||||
title: "Database Visibili",
|
||||
|
|
|
|||
|
|
@ -2448,7 +2448,7 @@ export default withEnglishFallback({
|
|||
changeOpenMode: "開き方を変更",
|
||||
closeRightTabs: "右側のタブを閉じる",
|
||||
compileObject: "コンパイル",
|
||||
compileObjectSuccess: "\"{name}\" のコンパイルに成功しました",
|
||||
compileObjectSuccess: '"{name}" のコンパイルに成功しました',
|
||||
},
|
||||
visibleDatabases: {
|
||||
title: "表示するデータベース",
|
||||
|
|
|
|||
|
|
@ -2423,7 +2423,7 @@ export default withEnglishFallback({
|
|||
changeOpenMode: "Alterar modo de abertura",
|
||||
closeRightTabs: "Fechar abas à direita",
|
||||
compileObject: "Compilar",
|
||||
compileObjectSuccess: "\"{name}\" compilado com sucesso",
|
||||
compileObjectSuccess: '"{name}" compilado com sucesso',
|
||||
},
|
||||
visibleDatabases: {
|
||||
title: "Bancos de dados visíveis",
|
||||
|
|
|
|||
|
|
@ -2422,7 +2422,7 @@ export default withEnglishFallback({
|
|||
changeOpenMode: "修改開啟方式",
|
||||
closeRightTabs: "關閉右側標籤頁",
|
||||
compileObject: "編譯",
|
||||
compileObjectSuccess: "\"{name}\" 編譯成功",
|
||||
compileObjectSuccess: '"{name}" 編譯成功',
|
||||
},
|
||||
visibleDatabases: {
|
||||
title: "顯示資料庫",
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ export function generateDatabaseExportId(): string {
|
|||
return uuid();
|
||||
}
|
||||
|
||||
export function shouldUseDatabaseBackupSnapshot(databaseType: DatabaseType | undefined, includeData: boolean, desktopRuntime: boolean): boolean {
|
||||
return desktopRuntime && includeData && (databaseType === "mysql" || databaseType === "postgres");
|
||||
}
|
||||
|
||||
export async function runDatabaseExportUntilTerminal(request: api.DatabaseExportRequest, onProgress: (progress: api.ExportProgress) => void): Promise<api.ExportProgress> {
|
||||
return new Promise<api.ExportProgress>((resolve, reject) => {
|
||||
api
|
||||
|
|
|
|||
|
|
@ -1,25 +1,68 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { beforeEach, test, vi } from "vitest";
|
||||
|
||||
const { beginDatabaseBackupSnapshot, rollbackManualTransaction } = vi.hoisted(() => ({
|
||||
const { beginDatabaseBackupSnapshot, exportDatabaseSql, rollbackManualTransaction } = vi.hoisted(() => ({
|
||||
beginDatabaseBackupSnapshot: vi.fn(),
|
||||
exportDatabaseSql: vi.fn(),
|
||||
rollbackManualTransaction: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
beginDatabaseBackupSnapshot,
|
||||
exportDatabaseSql,
|
||||
rollbackManualTransaction,
|
||||
}));
|
||||
|
||||
import { runWithDatabaseBackupSnapshot } from "../../apps/desktop/src/lib/export/databaseExport.ts";
|
||||
import { runDatabaseExportUntilTerminal, runWithDatabaseBackupSnapshot, shouldUseDatabaseBackupSnapshot } from "../../apps/desktop/src/lib/export/databaseExport.ts";
|
||||
|
||||
beforeEach(() => {
|
||||
beginDatabaseBackupSnapshot.mockReset();
|
||||
exportDatabaseSql.mockReset();
|
||||
rollbackManualTransaction.mockReset();
|
||||
beginDatabaseBackupSnapshot.mockResolvedValue({ sessionId: "snapshot-1" });
|
||||
rollbackManualTransaction.mockResolvedValue({});
|
||||
});
|
||||
|
||||
test("database export snapshots are limited to supported desktop data exports", () => {
|
||||
assert.equal(shouldUseDatabaseBackupSnapshot("mysql", true, true), true);
|
||||
assert.equal(shouldUseDatabaseBackupSnapshot("postgres", true, true), true);
|
||||
assert.equal(shouldUseDatabaseBackupSnapshot("oracle", true, true), false);
|
||||
assert.equal(shouldUseDatabaseBackupSnapshot("sqlserver", true, true), false);
|
||||
assert.equal(shouldUseDatabaseBackupSnapshot("mysql", false, true), false);
|
||||
assert.equal(shouldUseDatabaseBackupSnapshot("postgres", true, false), false);
|
||||
});
|
||||
|
||||
test("database export waits for terminal progress before releasing its snapshot", async () => {
|
||||
let emitProgress: ((progress: any) => void) | undefined;
|
||||
exportDatabaseSql.mockImplementation(async (_request, onProgress) => {
|
||||
emitProgress = onProgress;
|
||||
});
|
||||
|
||||
const operation = runDatabaseExportUntilTerminal({ exportId: "export-1" } as any, () => {});
|
||||
let settled = false;
|
||||
void operation.then(() => {
|
||||
settled = true;
|
||||
});
|
||||
await Promise.resolve();
|
||||
|
||||
assert.equal(settled, false);
|
||||
emitProgress?.({ exportId: "export-1", status: "Running" });
|
||||
await Promise.resolve();
|
||||
assert.equal(settled, false);
|
||||
|
||||
emitProgress?.({ exportId: "export-1", status: "Done" });
|
||||
assert.equal((await operation).status, "Done");
|
||||
});
|
||||
|
||||
test("single database export holds its snapshot until terminal progress", () => {
|
||||
const source = readFileSync("apps/desktop/src/components/export/DatabaseExportDialog.vue", "utf8");
|
||||
const singleExport = source.slice(source.indexOf("async function startExport()"), source.indexOf("async function startAllDatabasesExport()"));
|
||||
|
||||
assert.match(singleExport, /return runDatabaseExportUntilTerminal\(request,/);
|
||||
assert.doesNotMatch(singleExport, /await api\.exportDatabaseSql\(request,/);
|
||||
});
|
||||
|
||||
test("database export snapshot is released after success", async () => {
|
||||
const operation = vi.fn(async (sessionId: string | undefined) => `done:${sessionId}`);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue