Skip to content

Commit 0b4cfc8

Browse files
dmealingclaude
andcommitted
feat(migrate-ts): normalizeCheckExpr — compare CHECK exprs across PG rewrite
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e163379 commit 0b4cfc8

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// src/check-expr-compare.ts
2+
//
3+
// CHECK-expression comparison. Postgres rewrites a stored CHECK body — adding
4+
// parens around terms, normalizing whitespace/case — so the raw text we generate
5+
// (`col >= 0 AND col <= 100`) and the introspected text (`(col >= 0) AND (col <= 100)`)
6+
// differ textually but mean the same thing. This reduces both to ONE canonical
7+
// form for comparison. Reliable here because every check expression we emit is
8+
// machine-derived with a simple, known shape (comparison / IN / length / regex) —
9+
// there is no arbitrary author SQL to mis-normalize.
10+
11+
/** Canonical form: drop all parens, collapse whitespace, trim, lower-case. */
12+
export function normalizeCheckExpr(expr: string): string {
13+
return expr
14+
.replace(/[()]/g, " ")
15+
.replace(/\s+/g, " ")
16+
.trim()
17+
.toLowerCase();
18+
}
19+
20+
/** True when two CHECK expressions are equivalent after normalization. */
21+
export function checkExprEquals(a: string | undefined, b: string | undefined): boolean {
22+
if (a === undefined || b === undefined) return false;
23+
return normalizeCheckExpr(a) === normalizeCheckExpr(b);
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { normalizeCheckExpr, checkExprEquals } from "../../src/check-expr-compare.js";
3+
4+
describe("normalizeCheckExpr", () => {
5+
test("strips parens, collapses whitespace, lowercases", () => {
6+
expect(normalizeCheckExpr("(price >= 0) AND (price <= 100)")).toBe("price >= 0 and price <= 100");
7+
expect(normalizeCheckExpr("price >= 0 AND price <= 100")).toBe("price >= 0 and price <= 100");
8+
});
9+
test("PG-rewritten form equals the generated form", () => {
10+
expect(checkExprEquals("(col >= 0) AND (col <= 100)", "col >= 0 AND col <= 100")).toBe(true);
11+
expect(checkExprEquals("length(code) >= 3", "(length(code) >= 3)")).toBe(true);
12+
expect(checkExprEquals("status IN ('A', 'B')", "status in ('A', 'B')")).toBe(true);
13+
});
14+
test("genuinely different expressions are not equal", () => {
15+
expect(checkExprEquals("col >= 0", "col >= 5")).toBe(false);
16+
});
17+
test("undefined is never equal", () => {
18+
expect(checkExprEquals(undefined, "x")).toBe(false);
19+
});
20+
});

0 commit comments

Comments
 (0)