@@ -40,6 +40,25 @@ const meta = JSON.stringify({
4040 } ,
4141} ) ;
4242
43+ const lengthMeta = JSON . stringify ( {
44+ "metadata.root" : {
45+ package : "acme" ,
46+ children : [
47+ {
48+ "object.entity" : {
49+ name : "Space" ,
50+ children : [
51+ { "source.rdb" : { "@table" : "spaces" } } ,
52+ { "field.long" : { name : "id" } } ,
53+ { "field.string" : { name : "key" , "@column" : "key" , children : [ { "validator.length" : { name : "len" , "@min" : 2 } } ] } } ,
54+ { "identity.primary" : { name : "pk" , "@fields" : [ "id" ] , "@generation" : "increment" } } ,
55+ ] ,
56+ } ,
57+ } ,
58+ ] ,
59+ } ,
60+ } ) ;
61+
4362describe ( "regex CHECK vs Postgres's stored rewrite (live PG)" , ( ) => {
4463 if ( ! PG_URL ) {
4564 test . skip ( "skipped — MIGRATE_TS_PG_URL not set" , ( ) => { } ) ;
@@ -105,6 +124,38 @@ describe("regex CHECK vs Postgres's stored rewrite (live PG)", () => {
105124 expect ( r . kinds ) . toEqual ( [ "add-check" , "drop-check" ] ) ;
106125 } ) ;
107126
127+ // The same holds off the regex operator: `length(bpchar)` is its own function, so a stored
128+ // `length((key)::text)` is a different constraint from the uncast `length("key")`, while a
129+ // varchar column's cast is Postgres's own rewrite.
130+ const lengthChanges = async ( colType : string , authoredCheck : string ) => {
131+ await sql . raw ( "DROP TABLE IF EXISTS spaces CASCADE" ) . execute ( kysely ) ;
132+ await sql
133+ . raw ( `CREATE TABLE spaces (id bigserial PRIMARY KEY, "key" ${ colType } NOT NULL,
134+ CONSTRAINT spaces_key_length_chk CHECK (${ authoredCheck } ))` )
135+ . execute ( kysely ) ;
136+ const actual = await introspectPostgres ( kysely ) ;
137+ const stored = actual . tables . find ( ( t ) => t . name === "spaces" ) ?. checks . find ( ( c ) => c . name === "spaces_key_length_chk" ) ;
138+ const loaded = await new MetaDataLoader ( ) . load ( [ new InMemoryStringSource ( lengthMeta ) ] ) ;
139+ expect ( loaded . errors ) . toEqual ( [ ] ) ;
140+ const d = await diff ( { expected : buildExpectedSchema ( loaded . root , { dialect : "postgres" } ) , actual, dialect : "postgres" } ) ;
141+ return { stored : stored ?. expression , kinds : d . changes . filter ( ( c ) => c . kind . endsWith ( "-check" ) ) . map ( ( c ) => c . kind ) . sort ( ) } ;
142+ } ;
143+ test ( "char(n) column: Postgres stores length() uncast and it is clean" , async ( ) => {
144+ const r = await lengthChanges ( "char(8)" , `length("key") >= 2` ) ;
145+ expect ( r . stored ) . not . toContain ( "::text" ) ;
146+ expect ( r . kinds ) . toEqual ( [ ] ) ;
147+ } ) ;
148+ test ( "char(n) column: a stored length((col)::text) is drift against the uncast length()" , async ( ) => {
149+ const r = await lengthChanges ( "char(8)" , `length(("key")::text) >= 2` ) ;
150+ expect ( r . stored ) . toContain ( "(key)::text" ) ;
151+ expect ( r . kinds ) . toEqual ( [ "add-check" , "drop-check" ] ) ;
152+ } ) ;
153+ test ( "varchar column: Postgres's length((col)::text) rewrite is no drift" , async ( ) => {
154+ const r = await lengthChanges ( "varchar(8)" , `length("key") >= 2` ) ;
155+ expect ( r . stored ) . toContain ( "(key)::text" ) ;
156+ expect ( r . kinds ) . toEqual ( [ ] ) ;
157+ } ) ;
158+
108159 test ( "varchar column: a changed pattern still reads as drift" , async ( ) => {
109160 const r = await checkChanges ( "varchar(64)" , `"key" ~ '^[a-z]+$'` ) ;
110161 expect ( r . kinds ) . toEqual ( [ "add-check" , "drop-check" ] ) ;
0 commit comments