@@ -616,4 +616,146 @@ public void testProcessElementWithInvalidChangeEventException() throws Exception
616616 assertEquals (
617617 "Invalid byte array value for column: invalidKey" , argument .getValue ().getErrorMessage ());
618618 }
619+
620+ @ Test
621+ public void testProcessElementWithSpannerMutationAndColumnRename () throws Exception {
622+ ObjectMapper mapper = new ObjectMapper ();
623+ mapper .enable (DeserializationFeature .USE_BIG_DECIMAL_FOR_FLOATS );
624+ Schema schema = mock (Schema .class );
625+ CustomTransformation customTransformation = mock (CustomTransformation .class );
626+ DoFn .ProcessContext processContextMock = mock (DoFn .ProcessContext .class );
627+ ISpannerMigrationTransformer spannerMigrationTransformer =
628+ mock (ISpannerMigrationTransformer .class );
629+ PCollectionView <Ddl > ddl = mock (PCollectionView .class );
630+ SpannerConfig spannerConfig = mock (SpannerConfig .class );
631+ SpannerAccessor spannerAccessor = mock (SpannerAccessor .class );
632+ DatabaseClient databaseClientMock = mock (DatabaseClient .class );
633+ ChangeEventSessionConvertor changeEventSessionConvertor =
634+ mock (ChangeEventSessionConvertor .class );
635+
636+ // Create failsafe element input for the DoFn
637+ // Simulating a failed Spanner mutation where column "first_name" (Source) was renamed to
638+ // "full_name" (Spanner)
639+ ObjectNode changeEvent = mapper .createObjectNode ();
640+ changeEvent .put (DatastreamConstants .EVENT_SOURCE_TYPE_KEY , Constants .MYSQL_SOURCE_TYPE );
641+ changeEvent .put (DatastreamConstants .EVENT_TABLE_NAME_KEY , "Users" );
642+ changeEvent .put ("full_name" , "Johnny Rose" ); // Spanner column name
643+ changeEvent .put (EVENT_CHANGE_TYPE_KEY , "INSERT" );
644+ changeEvent .put ("_metadata_spanner_mutation" , true );
645+ FailsafeElement <String , String > failsafeElement =
646+ FailsafeElement .of (changeEvent .toString (), changeEvent .toString ());
647+
648+ Map <String , Object > sourceRecord =
649+ ChangeEventToMapConvertor .convertChangeEventToMap (changeEvent );
650+ MigrationTransformationRequest expectedRequest =
651+ new MigrationTransformationRequest ("Users" , sourceRecord , "" , "INSERT" );
652+ Map <String , Object > spannerRecord = new HashMap <>(sourceRecord );
653+ // The transformer returns the record as is (i.e. custom transformation is a no-op)
654+ MigrationTransformationResponse migrationTransformationResponse =
655+ new MigrationTransformationResponse (spannerRecord , false );
656+
657+ when (processContextMock .element ()).thenReturn (failsafeElement );
658+ // Explicitly return false for schema.isEmpty() to prove that we would have entered the schema
659+ // check block if not bypassed
660+ when (schema .isEmpty ()).thenReturn (false );
661+ // Throw if verifyTableInSession IS called - proving bypass
662+ doThrow (new RuntimeException ("Schema check should be bypassed for spanner mutations" ))
663+ .when (schema )
664+ .verifyTableInSession (any ());
665+ when (spannerMigrationTransformer .transformFailedSpannerMutation (refEq (expectedRequest )))
666+ .thenReturn (migrationTransformationResponse );
667+ when (spannerAccessor .getDatabaseClient ()).thenReturn (databaseClientMock );
668+ when (changeEventSessionConvertor .getShardId (changeEvent )).thenReturn ("" );
669+
670+ ChangeEventTransformerDoFn changeEventTransformerDoFn =
671+ ChangeEventTransformerDoFn .create (
672+ schema , null , null , null , "mysql" , customTransformation , false , ddl , spannerConfig );
673+ changeEventTransformerDoFn .setMapper (mapper );
674+ changeEventTransformerDoFn .setDatastreamToSpannerTransformer (spannerMigrationTransformer );
675+ changeEventTransformerDoFn .setSpannerAccessor (spannerAccessor );
676+ changeEventTransformerDoFn .setChangeEventSessionConvertor (changeEventSessionConvertor );
677+ changeEventTransformerDoFn .processElement (processContextMock );
678+
679+ ArgumentCaptor <FailsafeElement > argument = ArgumentCaptor .forClass (FailsafeElement .class );
680+ verify (processContextMock , times (1 ))
681+ .output (eq (DatastreamToSpannerConstants .TRANSFORMED_EVENT_TAG ), argument .capture ());
682+
683+ // Verify standard transformations bypassed (which would have failed or looked for "first_name"
684+ // if session was checking)
685+ verify (schema , times (0 )).verifyTableInSession (any ());
686+
687+ assertEquals (
688+ "{\" _metadata_source_type\" :\" mysql\" ,\" _metadata_table\" :\" Users\" ,\" full_name\" :\" Johnny Rose\" ,\" _metadata_change_type\" :\" INSERT\" ,\" _metadata_spanner_mutation\" :true}" ,
689+ argument .getValue ().getPayload ());
690+ }
691+
692+ @ Test
693+ public void testProcessElementWithSpannerMutationAndCustomTransformation () throws Exception {
694+ ObjectMapper mapper = new ObjectMapper ();
695+ mapper .enable (DeserializationFeature .USE_BIG_DECIMAL_FOR_FLOATS );
696+ Schema schema = mock (Schema .class );
697+ CustomTransformation customTransformation = mock (CustomTransformation .class );
698+ DoFn .ProcessContext processContextMock = mock (DoFn .ProcessContext .class );
699+ ISpannerMigrationTransformer spannerMigrationTransformer =
700+ mock (ISpannerMigrationTransformer .class );
701+ PCollectionView <Ddl > ddl = mock (PCollectionView .class );
702+ SpannerConfig spannerConfig = mock (SpannerConfig .class );
703+ SpannerAccessor spannerAccessor = mock (SpannerAccessor .class );
704+ DatabaseClient databaseClientMock = mock (DatabaseClient .class );
705+ ChangeEventSessionConvertor changeEventSessionConvertor =
706+ mock (ChangeEventSessionConvertor .class );
707+
708+ // Create failsafe element input for the DoFn
709+ ObjectNode changeEvent = mapper .createObjectNode ();
710+ changeEvent .put (DatastreamConstants .EVENT_SOURCE_TYPE_KEY , Constants .MYSQL_SOURCE_TYPE );
711+ changeEvent .put (DatastreamConstants .EVENT_TABLE_NAME_KEY , "Users" );
712+ changeEvent .put ("first_name" , "Johnny" );
713+ changeEvent .put (EVENT_CHANGE_TYPE_KEY , "INSERT" );
714+ changeEvent .put ("_metadata_spanner_mutation" , true );
715+ FailsafeElement <String , String > failsafeElement =
716+ FailsafeElement .of (changeEvent .toString (), changeEvent .toString ());
717+
718+ Map <String , Object > sourceRecord =
719+ ChangeEventToMapConvertor .convertChangeEventToMap (changeEvent );
720+ MigrationTransformationRequest expectedRequest =
721+ new MigrationTransformationRequest ("Users" , sourceRecord , "" , "INSERT" );
722+ Map <String , Object > spannerRecord = new HashMap <>(sourceRecord );
723+ spannerRecord .put ("first_name" , "Johnny Rose" );
724+ MigrationTransformationResponse migrationTransformationResponse =
725+ new MigrationTransformationResponse (spannerRecord , false );
726+
727+ // Explicitly return false for schema.isEmpty() to prove that we would have entered the schema
728+ // check block if not bypassed
729+ when (schema .isEmpty ()).thenReturn (false );
730+ // Throw if verifyTableInSession IS called - proving bypass
731+ doThrow (new RuntimeException ("Schema check should be bypassed for spanner mutations" ))
732+ .when (schema )
733+ .verifyTableInSession (any ());
734+ when (processContextMock .element ()).thenReturn (failsafeElement );
735+ when (spannerMigrationTransformer .transformFailedSpannerMutation (refEq (expectedRequest )))
736+ .thenReturn (migrationTransformationResponse );
737+ when (spannerAccessor .getDatabaseClient ()).thenReturn (databaseClientMock );
738+ when (changeEventSessionConvertor .getShardId (changeEvent )).thenReturn ("" );
739+
740+ ChangeEventTransformerDoFn changeEventTransformerDoFn =
741+ ChangeEventTransformerDoFn .create (
742+ schema , null , null , null , "mysql" , customTransformation , false , ddl , spannerConfig );
743+ changeEventTransformerDoFn .setMapper (mapper );
744+ changeEventTransformerDoFn .setDatastreamToSpannerTransformer (spannerMigrationTransformer );
745+ changeEventTransformerDoFn .setSpannerAccessor (spannerAccessor );
746+ changeEventTransformerDoFn .setChangeEventSessionConvertor (changeEventSessionConvertor );
747+ changeEventTransformerDoFn .processElement (processContextMock );
748+
749+ ArgumentCaptor <FailsafeElement > argument = ArgumentCaptor .forClass (FailsafeElement .class );
750+ verify (processContextMock , times (1 ))
751+ .output (eq (DatastreamToSpannerConstants .TRANSFORMED_EVENT_TAG ), argument .capture ());
752+
753+ // Verify standard transformations bypassed
754+ verify (schema , times (0 )).verifyTableInSession (any ());
755+ verify (changeEventSessionConvertor , times (0 )).transformChangeEventViaSessionFile (any ());
756+
757+ assertEquals (
758+ "{\" _metadata_source_type\" :\" mysql\" ,\" _metadata_table\" :\" Users\" ,\" first_name\" :\" Johnny Rose\" ,\" _metadata_change_type\" :\" INSERT\" ,\" _metadata_spanner_mutation\" :true}" ,
759+ argument .getValue ().getPayload ());
760+ }
619761}
0 commit comments