You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
migrate-ts: (col)::text cast folded on bpchar columns hides real regex CHECK drift on Postgres #470
meta verify --db on Postgres treats a hand-authored (col)::text ~ '…' CHECK on a char(n) (bpchar) column as equal to the uncast col ~ '…'. The two expressions match different values, so this is a false-clean result: a real constraint difference is reported as no drift.
The cause is that #463 put bpchar in the text-family set whose (col)::text cast is folded away before ~, ~*, !~ and !~*:
server/typescript/packages/migrate-ts/src/diff/index.ts: const PG_TEXT_FAMILY_UDTS = new Set(["text", "varchar", "bpchar"]);
For text and varchar that fold is correct. For bpchar it is not.
Why bpchar is different
bpchar has its own regex operator.~(bpchar, text) resolves to bpcharregexeq, which sees the blank padding. Casting to text trims the trailing blanks first, so the cast changes the result.
Postgres never adds the cast on bpchar. For a varchar column it stores ((v)::text ~ '…'::text), which is the drift fix(migrate-ts): regex CHECK on a varchar/text column no longer reads as drift on Postgres #463 fixed. For a char(n) column it stores (key ~ '…'::text) with no column cast. Folding the cast on bpchar therefore fixes no server-generated drift. It only affects hand-authored casts, and there it hides a real difference.
Reproduction (Postgres 16)
select ('ab'::char(8)) ~ 'b$'; -- f (bpcharregexeq sees 'ab ')select (('ab'::char(8))::text) ~ 'b$'; -- t (cast trims the padding)select oprcode from pg_operator
where oprname ='~'and oprleft ='bpchar'::regtype; -- bpcharregexeqcreatetablet (key char(8) check (key ~ '^a'), v varchar(8) check (v ~ '^a'));
select conname, pg_get_constraintdef(oid) from pg_constraint where conrelid ='t'::regclass;
-- t_key_check | CHECK ((key ~ '^a'::text)) <- no column cast on bpchar-- t_v_check | CHECK (((v)::text ~ '^a'::text)) <- cast added on varchar
With the current code, an expected key ~ 'b$' and an actual (key)::text ~ 'b$' on a char(8) column compare equal, although the first rejects 'ab' and the second accepts it.
Proposed fix
Remove bpchar from PG_TEXT_FAMILY_UDTS so only text and varchar fold the cast.
In test/check-evolution/diff-offline.test.ts, drop bpchar from the test.each(["text", "varchar", "bpchar"]) no-drift case.
Add a test that an explicit (key)::text ~ '…' on a bpchar column still reads as drift against key ~ '…'.
Add a check that the uncast form Postgres stores on bpchar ((key ~ '…'::text)) is still clean.
Update the CHANGELOG line and the ColumnDescriptor.udtName doc comment, which name bpchar as part of the text family.
The char(n) scenario in the #463 PR description (uncast check, no drift) keeps passing, because Postgres stores no column cast there.
Activity