feat(backup): support table filters and MySQL compatibility
This commit is contained in:
1 parent
09c3f7f44c
commit
7abac0959f
15 files changed
+386
-32
No files matched your search
@@ -11,10 +11,12 @@ import {
|
||||
databaseBackupFilePath,
|
||||
databaseBackupRunsToPrune,
|
||||
databaseBackupScheduleIsDue,
|
||||
databaseBackupTableNamesAreCaseSensitive,
|
||||
nextDatabaseBackupRunAt,
|
||||
normalizeDatabaseBackupSchedule,
|
||||
readDatabaseBackupRuns,
|
||||
readDatabaseBackupSchedules,
|
||||
resolveScheduledDatabaseBackupTableScope,
|
||||
resolveScheduledDatabaseBackupTargets,
|
||||
supportsScheduledDatabaseBackup,
|
||||
writeDatabaseBackupRuns,
|
||||
@@ -178,11 +180,22 @@ export function useScheduledDatabaseBackups(options: { scheduler?: boolean } = {
|
||||
if (!connection || !supportsScheduledDatabaseBackup(connection.db_type)) throw new Error("The backup connection is unavailable or unsupported.");
|
||||
await connectionStore.ensureConnected(schedule.connectionId);
|
||||
const availableDatabases = (await api.listDatabases(schedule.connectionId)).map((database) => database.name);
|
||||
const selectedDatabases = resolveScheduledDatabaseBackupTargets(schedule.databases, availableDatabases);
|
||||
const selectedDatabases = resolveScheduledDatabaseBackupTargets(schedule.databases, availableDatabases, connection.db_type);
|
||||
if (selectedDatabases.length === 0) throw new Error("No databases are available for this backup schedule.");
|
||||
|
||||
let tableNamesCaseSensitive = true;
|
||||
if (connection.db_type === "mysql" && schedule.tableFilterMode !== "all") {
|
||||
try {
|
||||
const result = await api.executeQuery(schedule.connectionId, "", "SHOW VARIABLES LIKE 'lower_case_table_names'", undefined, undefined, { maxRows: 1 });
|
||||
tableNamesCaseSensitive = databaseBackupTableNamesAreCaseSensitive(connection.db_type, result.rows[0]?.[1] ?? result.rows[0]?.[0]);
|
||||
} catch (error) {
|
||||
appendDebugLog("warn", "[DBX][database-backup:table-name-case-detection-error]", error);
|
||||
}
|
||||
}
|
||||
|
||||
let exportIndex = 0;
|
||||
let totalExports = selectedDatabases.length;
|
||||
let totalExports = 0;
|
||||
let includedTableCount = 0;
|
||||
for (const database of selectedDatabases) {
|
||||
if (cancellationRequested.has(runId)) {
|
||||
finalStatus = "cancelled";
|
||||
@@ -197,9 +210,21 @@ export function useScheduledDatabaseBackups(options: { scheduler?: boolean } = {
|
||||
schemasByDatabase: { [database]: snapshot.schemas },
|
||||
});
|
||||
if (databasePlan.length === 0) throw new Error(`Database ${database} did not resolve to any schemas.`);
|
||||
totalExports += databasePlan.length - 1;
|
||||
|
||||
const scopedDatabasePlan: Array<(typeof databasePlan)[number] & { selectedTables?: string[]; excludedTables?: string[] }> = [];
|
||||
for (const item of databasePlan) {
|
||||
if (schedule.tableFilterMode === "all") {
|
||||
scopedDatabasePlan.push(item);
|
||||
continue;
|
||||
}
|
||||
const availableTables = (await api.listTables(schedule.connectionId, item.database, item.schema)).map((table) => table.name);
|
||||
const scope = resolveScheduledDatabaseBackupTableScope(schedule.tableFilterMode, schedule.tablePatterns, availableTables, item.database, item.schema, tableNamesCaseSensitive);
|
||||
includedTableCount += scope.includedTables.length;
|
||||
if (scope.includedTables.length === 0) continue;
|
||||
scopedDatabasePlan.push({ ...item, selectedTables: scope.selectedTables, excludedTables: scope.excludedTables });
|
||||
}
|
||||
totalExports += scopedDatabasePlan.length;
|
||||
|
||||
for (const item of scopedDatabasePlan) {
|
||||
if (cancellationRequested.has(runId)) {
|
||||
finalStatus = "cancelled";
|
||||
break;
|
||||
@@ -216,6 +241,8 @@ export function useScheduledDatabaseBackups(options: { scheduler?: boolean } = {
|
||||
database: item.database,
|
||||
schema: item.schema,
|
||||
filePath,
|
||||
selectedTables: item.selectedTables,
|
||||
excludedTables: item.excludedTables,
|
||||
includeStructure: schedule.includeStructure,
|
||||
includeData: schedule.includeData,
|
||||
includeObjects: schedule.includeObjects,
|
||||
@@ -257,6 +284,9 @@ export function useScheduledDatabaseBackups(options: { scheduler?: boolean } = {
|
||||
}
|
||||
if (finalStatus !== "success") break;
|
||||
}
|
||||
if (schedule.tableFilterMode !== "all" && includedTableCount === 0) {
|
||||
throw new Error(`No tables matched the configured ${schedule.tableFilterMode} backup rules.`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
finalStatus = cancellationRequested.has(runId) ? "cancelled" : "failed";
|
||||
finalError = error?.message || String(error);
|
||||
|
||||
Reference in new issue
Block a user