@@ -416,6 +416,93 @@ func Test_PostgresToPostgres_SchemaObjects(t *testing.T) {
416416 }, 20 * time .Second , 200 * time .Millisecond )
417417}
418418
419+ // Test_PostgresToPostgres_AlwaysIdentityUpdate verifies that UPDATE events on a
420+ // table with a GENERATED ALWAYS AS IDENTITY column are replicated successfully.
421+ // Postgres rejects UPDATE ... SET <always-identity-col> = <value> with
422+ // "column can only be updated to DEFAULT", so the column must be filtered out
423+ // of the SET clause on the target.
424+ func Test_PostgresToPostgres_AlwaysIdentityUpdate (t * testing.T ) {
425+ if os .Getenv ("PGSTREAM_INTEGRATION_TESTS" ) == "" {
426+ t .Skip ("skipping integration test..." )
427+ }
428+
429+ cfg := & stream.Config {
430+ Listener : testPostgresListenerCfg (),
431+ Processor : testPostgresProcessorCfg (pgurl , withoutBulkIngestion ),
432+ }
433+
434+ ctx , cancel := context .WithCancel (context .Background ())
435+ defer cancel ()
436+
437+ targetConn , err := pglib .NewConn (ctx , targetPGURL )
438+ require .NoError (t , err )
439+ defer targetConn .Close (ctx )
440+
441+ runStream (t , ctx , cfg )
442+
443+ testTable := "pg2pg_always_identity_update_test"
444+ defer execQuery (t , ctx , fmt .Sprintf ("DROP TABLE IF EXISTS %s" , testTable ))
445+
446+ // Table has a separate primary key plus a GENERATED ALWAYS AS IDENTITY
447+ // column. UPDATEs on `name` must not include `request_id` in the SET
448+ // clause, or Postgres rejects them.
449+ execQuery (t , ctx , fmt .Sprintf (`
450+ CREATE TABLE %s (
451+ id bigint PRIMARY KEY,
452+ request_id bigint GENERATED ALWAYS AS IDENTITY,
453+ name text
454+ )
455+ ` , testTable ))
456+
457+ // Wait for the table schema to land on the target before querying it.
458+ require .Eventually (t , func () bool {
459+ columns := getInformationSchemaColumns (t , ctx , targetConn , testTable )
460+ return len (columns ) == 3
461+ }, 20 * time .Second , 200 * time .Millisecond , "table schema not replicated" )
462+
463+ execQuery (t , ctx , fmt .Sprintf ("INSERT INTO %s(id, name) VALUES(1, 'Alice')" , testTable ))
464+ execQuery (t , ctx , fmt .Sprintf ("INSERT INTO %s(id, name) VALUES(2, 'Bob')" , testTable ))
465+
466+ require .Eventually (t , func () bool {
467+ rows := getIDNameRows (t , ctx , targetConn ,
468+ fmt .Sprintf ("SELECT id, name FROM %s ORDER BY id" , testTable ))
469+ return len (rows ) == 2 && rows [0 ].name == "Alice" && rows [1 ].name == "Bob"
470+ }, 20 * time .Second , 200 * time .Millisecond , "initial inserts not replicated" )
471+
472+ // Capture the original request_id values on the target so we can verify
473+ // they are preserved across the UPDATE (since the SET clause must not
474+ // touch the always-identity column).
475+ var aliceReqIDBefore , bobReqIDBefore int64
476+ err = targetConn .QueryRow (ctx , []any {& aliceReqIDBefore },
477+ fmt .Sprintf ("SELECT request_id FROM %s WHERE id = 1" , testTable ))
478+ require .NoError (t , err )
479+ err = targetConn .QueryRow (ctx , []any {& bobReqIDBefore },
480+ fmt .Sprintf ("SELECT request_id FROM %s WHERE id = 2" , testTable ))
481+ require .NoError (t , err )
482+
483+ // Perform UPDATEs that, pre-fix, produced
484+ // "column \"request_id\" can only be updated to DEFAULT" on the target.
485+ execQuery (t , ctx , fmt .Sprintf ("UPDATE %s SET name = 'Alice2' WHERE id = 1" , testTable ))
486+ execQuery (t , ctx , fmt .Sprintf ("UPDATE %s SET name = 'Bob2' WHERE id = 2" , testTable ))
487+
488+ require .Eventually (t , func () bool {
489+ rows := getIDNameRows (t , ctx , targetConn ,
490+ fmt .Sprintf ("SELECT id, name FROM %s ORDER BY id" , testTable ))
491+ return len (rows ) == 2 && rows [0 ].name == "Alice2" && rows [1 ].name == "Bob2"
492+ }, 20 * time .Second , 200 * time .Millisecond , "UPDATE not replicated — likely rejected by always-identity rule" )
493+
494+ var aliceReqIDAfter , bobReqIDAfter int64
495+ err = targetConn .QueryRow (ctx , []any {& aliceReqIDAfter },
496+ fmt .Sprintf ("SELECT request_id FROM %s WHERE id = 1" , testTable ))
497+ require .NoError (t , err )
498+ err = targetConn .QueryRow (ctx , []any {& bobReqIDAfter },
499+ fmt .Sprintf ("SELECT request_id FROM %s WHERE id = 2" , testTable ))
500+ require .NoError (t , err )
501+
502+ require .Equal (t , aliceReqIDBefore , aliceReqIDAfter , "request_id should be unchanged by UPDATE" )
503+ require .Equal (t , bobReqIDBefore , bobReqIDAfter , "request_id should be unchanged by UPDATE" )
504+ }
505+
419506func Test_PostgresToPostgres_Sequences (t * testing.T ) {
420507 if os .Getenv ("PGSTREAM_INTEGRATION_TESTS" ) == "" {
421508 t .Skip ("skipping integration test..." )
0 commit comments