1818// - respells a cast TARGET canonically: `x::int` → `(x)::integer`
1919// All five are canonicalized below so an enum/range CHECK introspected from PG
2020// compares equal to the one we generate (idempotency on the --from-db / verify paths).
21- // Three more are folded back by foldPgAnalyzerRewrites and PAREN_COLUMN_TEXT_CAST : a
21+ // Three more are folded back by foldPgAnalyzerRewrites and COLUMN_TEXT_CAST : a
2222// one-element `x IN (v)` stored as `x = v`, `a IS NOT DISTINCT FROM b` stored as
2323// `NOT (a IS DISTINCT FROM b)`, and `(varchar_col)::text` stored for a text operator.
2424//
@@ -54,13 +54,13 @@ const NO_COLUMNS: ReadonlySet<string> = new Set();
5454/**
5555 * `(col)::text` — the form Postgres writes when it casts a `varchar` column to `text` to reach
5656 * a text operator or function (`length((code)::text)`, `(a)::text < (b)::text`,
57- * `((region)::text IS DISTINCT FROM 'eu'::text)`). Matched BEFORE the brackets are stripped,
58- * while `(col)` can still be told from a call `f (col)`: the opening bracket must not follow a
59- * word character. It is dropped only for a column the caller names as text-family
57+ * `((region)::text IS DISTINCT FROM 'eu'::text)`) — and the bare `col::text` an author writes
58+ * for the same cast. Matched BEFORE the brackets are stripped, while ` (col)` can still be told
59+ * from a call `f(col)`: the match must not follow a word character or a dot . It is dropped only for a column the caller names as text-family
6060 * (`text` / `varchar`), where the cast changes nothing; on any other column it changes the
6161 * operator (citext) or the value (`char(n)` trims its padding), so it stays and reads as drift.
6262 */
63- const PAREN_COLUMN_TEXT_CAST = / (?< ! \w ) \( \s * ( [ a - z _ ] \w * ) \s * \) \s * : : \s * t e x t \b / g;
63+ const COLUMN_TEXT_CAST = / (?< ! [ \w . ] ) (?: \( \s * ( [ a - z _ ] \w * ) \s * \) | ( [ a - z _ ] \w * ) ) \s * : : \s * t e x t \b / g;
6464
6565/** Split on single-quoted literals ('' escapes honored) and rewrite only the text between them. */
6666function mapOutsideLiterals ( s : string , fn : ( part : string , index : number ) => string ) : string {
@@ -184,7 +184,7 @@ function foldPgAnalyzerRewrites(s: string): string {
184184 * around commas, casts and SYMBOLIC operators (outside single-quoted literals).
185185 *
186186 * `textColumns` (lower-cased names of `text` / `varchar` columns) lets a `(col)::text` cast
187- * fold away; see PAREN_COLUMN_TEXT_CAST and isElidableCast.
187+ * fold away; see COLUMN_TEXT_CAST and isElidableCast.
188188 */
189189export function normalizeCheckExpr ( expr : string , textColumns : ReadonlySet < string > = NO_COLUMNS ) : string {
190190 // Every textual rewrite below runs OUTSIDE single-quoted literals. A literal is DATA: its
@@ -205,7 +205,10 @@ export function normalizeCheckExpr(expr: string, textColumns: ReadonlySet<string
205205 // makes the generated and introspected forms compare equal, and keeps a
206206 // legacy unquoted check matching its quoted re-emit.
207207 . replace ( / " ( \w + ) " / g, "$1" )
208- . replace ( PAREN_COLUMN_TEXT_CAST , ( whole , column : string ) => ( textColumns . has ( column ) ? column : whole ) )
208+ . replace ( COLUMN_TEXT_CAST , ( whole , parenthesised : string | undefined , bare : string | undefined ) => {
209+ const column = parenthesised ?? bare ! ;
210+ return textColumns . has ( column ) ? column : whole ;
211+ } )
209212 . replace ( / [ ( ) [ \] ] / g, " " ) // drop parens AND square brackets (ARRAY[…])
210213 . replace ( / \s + / g, " " )
211214 // PG stores `col IN (…)` as `col = ANY (ARRAY[…])`; after the bracket strip above
0 commit comments