From d902a82e604d658cbdcfcbac4378482a753f1455 Mon Sep 17 00:00:00 2001 From: iamjr15 Date: Sun, 2 Aug 2026 13:58:30 +0530 Subject: [PATCH] fix(db): paren-insensitive constraint-drift comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pg_get_constraintdef's AND-chain grouping changed between Postgres minors: 17.4 prints left-associated parentheses, 17.6 flattens the chain. The pinned definitions came from the 17.4 production dump, so the drift check false-fired on a fresh 17.6 project (and would fire on production after its next minor upgrade). Comparing both sides with parentheses stripped keeps the check's purpose — operator, literal, and regex changes still surface. --- scripts/supabase-target/invariants.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/supabase-target/invariants.ts b/scripts/supabase-target/invariants.ts index 1342edb1..837dba7f 100644 --- a/scripts/supabase-target/invariants.ts +++ b/scripts/supabase-target/invariants.ts @@ -181,7 +181,15 @@ export async function validateIntegrityConstraints(client: PgClient): Promise candidate["conname"] === contract.name); - if (row && normalizedSqlDefinition(row["definition"]) !== contract.definition) { + // pg_get_constraintdef's AND-chain grouping changed between Postgres + // minors (17.4 prints left-associated parentheses, 17.6 flattens), so the + // comparison ignores parentheses. Operator, literal, and regex changes + // still surface, which is what this drift check exists to catch. + if ( + row && + parenInsensitiveSqlDefinition(row["definition"]) !== + parenInsensitiveSqlDefinition(contract.definition) + ) { issues.push(`Security check public.${contract.tableName}.${contract.name} has drifted.`); } } @@ -261,3 +269,7 @@ function stringField(row: Record, key: string): string | undefi function normalizedSqlDefinition(value: unknown): string { return typeof value === "string" ? value.replaceAll(/\s+/g, " ").trim() : ""; } + +function parenInsensitiveSqlDefinition(value: unknown): string { + return normalizedSqlDefinition(value).replaceAll(/[()]/g, "").replaceAll(/\s+/g, " ").trim(); +}