From 1016ccc352d272e4579cf8d8f89dc257c04d5c5a Mon Sep 17 00:00:00 2001 From: Melody Ruch <396921921@qq.com> Date: Thu, 27 Aug 2026 22:24:09 +0800 Subject: [PATCH] fix(backup): match db.schema.table excludes when database equals schema --- .../backup/scheduledDatabaseBackup.spec.ts | 22 ++++++++++++++++++- .../src/lib/backup/scheduledDatabaseBackup.ts | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/lib/__tests__/backup/scheduledDatabaseBackup.spec.ts b/apps/desktop/src/lib/__tests__/backup/scheduledDatabaseBackup.spec.ts index 1737a83d20..096e881992 100644 --- a/apps/desktop/src/lib/__tests__/backup/scheduledDatabaseBackup.spec.ts +++ b/apps/desktop/src/lib/__tests__/backup/scheduledDatabaseBackup.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { databaseBackupRunsToPrune, normalizeDatabaseBackupRun, type DatabaseBackupRun } from "../../backup/scheduledDatabaseBackup"; +import { databaseBackupRunsToPrune, databaseBackupTableMatchesPattern, normalizeDatabaseBackupRun, resolveScheduledDatabaseBackupTableScope, type DatabaseBackupRun } from "../../backup/scheduledDatabaseBackup"; const startedAt = "2026-08-12T00:00:00.000Z"; @@ -46,3 +46,23 @@ describe("database backup run persistence", () => { expect(databaseBackupRunsToPrune(normalizedRuns, "schedule-1", 1).map((value) => value.id)).toEqual(["scheduled-old"]); }); }); + +describe("database backup table pattern matching", () => { + it("matches a fully-qualified db.schema.table pattern when the database and schema share the same name", () => { + // Regression for #7314: PostgreSQL commonly has a schema with the same + // name as its database, and the fully-qualified candidate was previously + // suppressed whenever database === schema, silently breaking exclude + // rules written as "db.schema.table". + expect(databaseBackupTableMatchesPattern("vector_data", ["issue7314.issue7314.vector_data"], "issue7314", "issue7314")).toBe(true); + }); + + it("still matches a fully-qualified db.schema.table pattern when the database and schema differ", () => { + expect(databaseBackupTableMatchesPattern("vector_data", ["mydb.myschema.vector_data"], "mydb", "myschema")).toBe(true); + }); + + it("excludes the matched table from the backup scope when database and schema share the same name", () => { + const scope = resolveScheduledDatabaseBackupTableScope("exclude", ["issue7314.issue7314.vector_data"], ["vector_data", "keep_rows"], "issue7314", "issue7314"); + expect(scope.includedTables).toEqual(["keep_rows"]); + expect(scope.excludedTables).toEqual(["vector_data"]); + }); +}); diff --git a/apps/desktop/src/lib/backup/scheduledDatabaseBackup.ts b/apps/desktop/src/lib/backup/scheduledDatabaseBackup.ts index 2bc2aecd54..0a505c3a4d 100644 --- a/apps/desktop/src/lib/backup/scheduledDatabaseBackup.ts +++ b/apps/desktop/src/lib/backup/scheduledDatabaseBackup.ts @@ -79,7 +79,7 @@ function tablePatternRegex(pattern: string, caseSensitive: boolean): RegExp { export function databaseBackupTableMatchesPattern(table: string, patterns: readonly string[], database = "", schema = "", caseSensitive = true): boolean { const candidates = [table]; if (schema) candidates.push(`${schema}.${table}`); - if (database && schema && database !== schema) candidates.push(`${database}.${schema}.${table}`); + if (database && schema) candidates.push(`${database}.${schema}.${table}`); return patterns.some((pattern) => { const matcher = tablePatternRegex(pattern, caseSensitive); return candidates.some((candidate) => matcher.test(candidate));