Skip to content

Commit 3b36103

Browse files
dmealingclaude
andcommitted
fix(migrate-ts): make CHECK-expr cast-strip quote-aware (preserve :: in regex patterns)
The cast-strip introduced for the IN/ANY fold was quote-unaware, so a regex pattern containing '::' (e.g. 'a::foo') normalized to 'a' — making two distinct regex CHECKs compare equal and a pattern change silently missed. Restrict the strip to a cast that follows a CLOSING quote (PG's 'open'::text form) via a lookbehind; a '::' inside a quoted pattern is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1314f0a commit 3b36103

2 files changed

Lines changed: 14 additions & 1 deletion

File tree

server/typescript/packages/migrate-ts/src/check-expr-compare.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
export function normalizeCheckExpr(expr: string): string {
1717
const stripped = expr
1818
.toLowerCase()
19-
.replace(/::\s*"?\w+"?/g, "") // drop `::text` / `::"MyType"` type casts PG adds to literals
19+
// Drop `::text` / `::"MyType"` type casts PG adds to literals. The lookbehind
20+
// restricts the strip to a cast that immediately follows a CLOSING single
21+
// quote (`'open'::text`), so a `::` appearing INSIDE a regex pattern literal
22+
// (`slug ~ 'a::foo'`) is preserved — otherwise two distinct regex CHECKs would
23+
// normalize equal and a pattern change would be silently missed.
24+
.replace(/(?<=')::\s*"?\w+"?/g, "")
2025
.replace(/[()[\]]/g, " ") // drop parens AND square brackets (ARRAY[…])
2126
.replace(/\s+/g, " ")
2227
.trim();

server/typescript/packages/migrate-ts/test/check-evolution/normalize.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ describe("normalizeCheckExpr", () => {
2727
"status = ANY (ARRAY['OPEN'::text, 'CLOSED'::text, 'CANCELLED'::text])",
2828
)).toBe(false);
2929
});
30+
test("a `::` inside a regex pattern is preserved (only post-quote casts are stripped)", () => {
31+
// the cast-strip must not corrupt a regex literal containing `::`, else two
32+
// distinct regex CHECKs would compare equal and a pattern change be missed.
33+
expect(checkExprEquals("slug ~ 'a::foo'", "slug ~ 'a::bar'")).toBe(false);
34+
expect(normalizeCheckExpr("slug ~ 'a::foo'")).toBe("slug ~ 'a::foo'");
35+
// but PG's post-literal `::text` cast (enum form) is still stripped
36+
expect(normalizeCheckExpr("'open'::text")).toBe("'open'");
37+
});
3038
test("genuinely different expressions are not equal", () => {
3139
expect(checkExprEquals("col >= 0", "col >= 5")).toBe(false);
3240
});

0 commit comments

Comments
 (0)