feat(backup): add gzip compression and history filtering
This commit is contained in:
36 files changed
+1144
-137
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { databaseBackupRunsToPrune, databaseBackupTableMatchesPattern, normalizeDatabaseBackupRun, resolveScheduledDatabaseBackupTableScope, type DatabaseBackupRun } from "../../backup/scheduledDatabaseBackup";
|
||||
import { databaseBackupFilePath, databaseBackupRunsToPrune, databaseBackupTableMatchesPattern, normalizeDatabaseBackupRun, resolveScheduledDatabaseBackupTableScope, type DatabaseBackupRun } from "../../backup/scheduledDatabaseBackup";
|
||||
|
||||
const startedAt = "2026-08-12T00:00:00.000Z";
|
||||
|
||||
@@ -19,6 +19,10 @@ function run(overrides: Record<string, unknown> = {}) {
|
||||
}
|
||||
|
||||
describe("database backup run persistence", () => {
|
||||
it("uses a gzip extension when the backup output is compressed", () => {
|
||||
expect(databaseBackupFilePath("/backups", "Nightly", "app", startedAt, "run-12345678", "gzip")).toMatch(/\.sql\.gz$/);
|
||||
});
|
||||
|
||||
it("accepts a one-shot run without a schedule id", () => {
|
||||
const normalized = normalizeDatabaseBackupRun(run());
|
||||
|
||||
|
||||
@@ -448,6 +448,7 @@ export const releaseTableImportSource = forward("releaseTableImportSource");
|
||||
export const beginDatabaseBackupSnapshot = forward("beginDatabaseBackupSnapshot");
|
||||
export const exportDatabaseSql = forward("exportDatabaseSql");
|
||||
export const cancelDatabaseExport = forward("cancelDatabaseExport");
|
||||
export const clearDatabaseExportCancellation = forward("clearDatabaseExportCancellation");
|
||||
export const recordDatabaseExportDestination = forward("recordDatabaseExportDestination");
|
||||
export const exportQueryResultCsv = forward("exportQueryResultCsv");
|
||||
export const exportTableDataCsv = forward("exportTableDataCsv");
|
||||
|
||||
@@ -2376,7 +2376,7 @@ export async function releaseTableImportSource(sourceRef: string): Promise<boole
|
||||
// Database Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function beginDatabaseBackupSnapshot(_connectionId: string, _database: string): Promise<DatabaseBackupSnapshot> {
|
||||
export async function beginDatabaseBackupSnapshot(_connectionId: string, _database: string, _exportId?: string): Promise<DatabaseBackupSnapshot> {
|
||||
throw new Error("Consistent database backup snapshots are only available in the desktop app.");
|
||||
}
|
||||
|
||||
@@ -2422,6 +2422,10 @@ export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
await post("/api/export/database/cancel", { exportId });
|
||||
}
|
||||
|
||||
export async function clearDatabaseExportCancellation(_exportId: string): Promise<void> {
|
||||
// The web exporter owns and clears its cancellation marker on completion.
|
||||
}
|
||||
|
||||
export async function recordDatabaseExportDestination(_directory: string): Promise<void> {
|
||||
throw new Error("Scheduled database backups are only available in the desktop app.");
|
||||
}
|
||||
|
||||
@@ -4675,6 +4675,7 @@ export interface DatabaseExportRequest {
|
||||
dropTableIfExists?: boolean;
|
||||
omitAutoIncrement?: boolean;
|
||||
failOnError?: boolean;
|
||||
outputCompression?: "none" | "gzip";
|
||||
snapshotSessionId?: string;
|
||||
batchSize: number;
|
||||
}
|
||||
@@ -4866,8 +4867,8 @@ export async function cancelQueryResultExport(exportId: string, executionId?: st
|
||||
});
|
||||
}
|
||||
|
||||
export async function beginDatabaseBackupSnapshot(connectionId: string, database: string): Promise<DatabaseBackupSnapshot> {
|
||||
return invoke("begin_database_backup_snapshot", { connectionId, database });
|
||||
export async function beginDatabaseBackupSnapshot(connectionId: string, database: string, exportId?: string): Promise<DatabaseBackupSnapshot> {
|
||||
return invoke("begin_database_backup_snapshot", { connectionId, database, exportId: exportId || null });
|
||||
}
|
||||
|
||||
export async function exportDatabaseSql(request: DatabaseExportRequest, onProgress: (progress: ExportProgress) => void): Promise<void> {
|
||||
@@ -4891,6 +4892,10 @@ export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
await invoke("cancel_database_export", { exportId });
|
||||
}
|
||||
|
||||
export async function clearDatabaseExportCancellation(exportId: string): Promise<void> {
|
||||
await invoke("clear_database_export_cancellation", { exportId });
|
||||
}
|
||||
|
||||
export async function recordDatabaseExportDestination(directory: string): Promise<void> {
|
||||
await invoke("record_database_export_destination", { directory });
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export type DatabaseBackupRunStatus = "running" | "success" | "failed" | "cancel
|
||||
export type DatabaseBackupRunTrigger = "manual" | "scheduled";
|
||||
export type DatabaseBackupRunSource = "scheduled" | "one-shot";
|
||||
export type DatabaseBackupTableFilterMode = "all" | "include" | "exclude";
|
||||
export type DatabaseBackupOutputCompression = "none" | "gzip";
|
||||
|
||||
const CONSISTENT_BACKUP_DATABASE_TYPES = new Set(["mysql", "postgres"]);
|
||||
|
||||
@@ -117,6 +118,7 @@ export interface DatabaseBackupExecutionConfig {
|
||||
includeData: boolean;
|
||||
includeObjects: boolean;
|
||||
dropTableIfExists: boolean;
|
||||
outputCompression: DatabaseBackupOutputCompression;
|
||||
}
|
||||
|
||||
export interface DatabaseBackupSchedule extends DatabaseBackupExecutionConfig {
|
||||
@@ -146,6 +148,7 @@ export function toDatabaseBackupExecutionConfig(schedule: DatabaseBackupSchedule
|
||||
includeData: schedule.includeData,
|
||||
includeObjects: schedule.includeObjects,
|
||||
dropTableIfExists: schedule.dropTableIfExists,
|
||||
outputCompression: schedule.outputCompression,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,6 +247,7 @@ export function normalizeDatabaseBackupSchedule(value: unknown, now = new Date()
|
||||
includeData: booleanValue(input.includeData, true),
|
||||
includeObjects: booleanValue(input.includeObjects, true),
|
||||
dropTableIfExists: booleanValue(input.dropTableIfExists, false),
|
||||
outputCompression: input.outputCompression === "gzip" ? "gzip" : "none",
|
||||
retentionCount: normalizeDatabaseBackupRetention(input.retentionCount),
|
||||
createdAt: validIsoDate(input.createdAt, nowIso),
|
||||
updatedAt: validIsoDate(input.updatedAt, nowIso),
|
||||
@@ -394,7 +398,8 @@ export function joinDatabaseBackupPath(directory: string, fileName: string): str
|
||||
return `${directory.replace(/[\\/]+$/, "")}${separator}${fileName}`;
|
||||
}
|
||||
|
||||
export function databaseBackupFilePath(directory: string, scheduleName: string, fileStem: string, startedAt: Date | string, runId: string): string {
|
||||
const fileName = `dbx-backup__${sanitizeDatabaseBackupFileSegment(scheduleName)}__${databaseBackupTimestamp(startedAt)}__${sanitizeDatabaseBackupFileSegment(fileStem)}__${sanitizeDatabaseBackupFileSegment(runId).slice(0, 8)}.sql`;
|
||||
export function databaseBackupFilePath(directory: string, scheduleName: string, fileStem: string, startedAt: Date | string, runId: string, outputCompression: DatabaseBackupOutputCompression = "none"): string {
|
||||
const suffix = outputCompression === "gzip" ? ".sql.gz" : ".sql";
|
||||
const fileName = `dbx-backup__${sanitizeDatabaseBackupFileSegment(scheduleName)}__${databaseBackupTimestamp(startedAt)}__${sanitizeDatabaseBackupFileSegment(fileStem)}__${sanitizeDatabaseBackupFileSegment(runId).slice(0, 8)}${suffix}`;
|
||||
return joinDatabaseBackupPath(directory, fileName);
|
||||
}
|
||||
Reference in new issue
Block a user