25
25
import static org .junit .jupiter .api .Assertions .assertTrue ;
26
26
27
27
import com .fasterxml .jackson .databind .JsonNode ;
28
+ import com .fasterxml .jackson .databind .ObjectMapper ;
28
29
import com .fasterxml .jackson .databind .node .ObjectNode ;
29
30
import com .google .common .collect .ImmutableList ;
30
31
import com .google .common .collect .ImmutableMap ;
50
51
import io .airbyte .protocol .models .v0 .AirbyteGlobalState ;
51
52
import io .airbyte .protocol .models .v0 .AirbyteMessage ;
52
53
import io .airbyte .protocol .models .v0 .AirbyteRecordMessage ;
54
+ import io .airbyte .protocol .models .v0 .AirbyteRecordMessageMeta ;
55
+ import io .airbyte .protocol .models .v0 .AirbyteRecordMessageMetaChange ;
56
+ import io .airbyte .protocol .models .v0 .AirbyteRecordMessageMetaChange .Change ;
57
+ import io .airbyte .protocol .models .v0 .AirbyteRecordMessageMetaChange .Reason ;
53
58
import io .airbyte .protocol .models .v0 .AirbyteStateMessage ;
54
59
import io .airbyte .protocol .models .v0 .AirbyteStateMessage .AirbyteStateType ;
55
60
import io .airbyte .protocol .models .v0 .AirbyteStream ;
59
64
import io .airbyte .protocol .models .v0 .ConfiguredAirbyteStream ;
60
65
import io .airbyte .protocol .models .v0 .StreamDescriptor ;
61
66
import io .airbyte .protocol .models .v0 .SyncMode ;
67
+ import java .util .ArrayList ;
62
68
import java .util .Collections ;
63
69
import java .util .HashSet ;
64
70
import java .util .List ;
@@ -80,6 +86,11 @@ public class CdcMysqlSourceTest extends CdcSourceTest<MySqlSource, MySQLTestData
80
86
81
87
private static final Random RANDOM = new Random ();
82
88
89
+ private static final String TEST_DATE_STREAM_NAME = "TEST_DATE_TABLE" ;
90
+ private static final String COL_DATE_TIME = "CAR_DATE" ;
91
+ private static final List <JsonNode > DATE_TIME_RECORDS = ImmutableList .of (
92
+ Jsons .jsonNode (ImmutableMap .of (COL_ID , 120 , COL_DATE_TIME , "'2023-00-00 20:37:47'" )));
93
+
83
94
@ Override
84
95
protected MySQLTestDatabase createTestDatabase () {
85
96
return MySQLTestDatabase .in (BaseImage .MYSQL_8 , ContainerModifier .INVALID_TIMEZONE_CEST ).withCdcPermissions ();
@@ -734,6 +745,70 @@ public void testCompressedSchemaHistory() throws Exception {
734
745
assertEquals (recordsToCreate , extractRecordMessages (dataFromSecondBatch ).size ());
735
746
}
736
747
748
+ private void writeDateRecords (
749
+ final JsonNode recordJson ,
750
+ final String dbName ,
751
+ final String streamName ,
752
+ final String idCol ,
753
+ final String dateCol ) {
754
+ testdb .with ("INSERT INTO `%s` .`%s` (%s, %s) VALUES (%s, %s);" , dbName , streamName ,
755
+ idCol , dateCol ,
756
+ recordJson .get (idCol ).asInt (), recordJson .get (dateCol ).asText ());
757
+ }
758
+
759
+ @ Test
760
+ public void testInvalidDatetime_metaChangesPopulated () throws Exception {
761
+ final ConfiguredAirbyteCatalog configuredCatalog = Jsons .clone (getConfiguredCatalog ());
762
+
763
+ // Add a datetime stream to the catalog
764
+ testdb
765
+ .withoutStrictMode ()
766
+ .with (createTableSqlFmt (), getDatabaseName (), TEST_DATE_STREAM_NAME ,
767
+ columnClause (ImmutableMap .of (COL_ID , "INTEGER" , COL_DATE_TIME , "DATETIME" ), Optional .of (COL_ID )));
768
+
769
+ for (final JsonNode recordJson : DATE_TIME_RECORDS ) {
770
+ writeDateRecords (recordJson , getDatabaseName (), TEST_DATE_STREAM_NAME , COL_ID , COL_DATE_TIME );
771
+ }
772
+
773
+ final ConfiguredAirbyteStream airbyteStream = new ConfiguredAirbyteStream ()
774
+ .withStream (CatalogHelpers .createAirbyteStream (
775
+ TEST_DATE_STREAM_NAME ,
776
+ getDatabaseName (),
777
+ Field .of (COL_ID , JsonSchemaType .INTEGER ),
778
+ Field .of (COL_DATE_TIME , JsonSchemaType .STRING_TIMESTAMP_WITHOUT_TIMEZONE ))
779
+ .withSupportedSyncModes (
780
+ Lists .newArrayList (SyncMode .FULL_REFRESH , SyncMode .INCREMENTAL ))
781
+ .withSourceDefinedPrimaryKey (List .of (List .of (COL_ID ))));
782
+ airbyteStream .setSyncMode (SyncMode .INCREMENTAL );
783
+
784
+ final List <ConfiguredAirbyteStream > streams = new ArrayList <>();
785
+ streams .add (airbyteStream );
786
+ configuredCatalog .withStreams (streams );
787
+
788
+ final AutoCloseableIterator <AirbyteMessage > read1 = source ()
789
+ .read (config (), configuredCatalog , null );
790
+ final List <AirbyteMessage > actualRecords = AutoCloseableIterators .toListAndClose (read1 );
791
+
792
+ // Sync is expected to succeed with one record. However, the meta changes column should be populated
793
+ // for this record
794
+ // as it is an invalid date. As a result, this field will be omitted as Airbyte is unable to
795
+ // serialize the source value.
796
+ final Set <AirbyteRecordMessage > recordMessages = extractRecordMessages (actualRecords );
797
+ assertEquals (recordMessages .size (), 1 );
798
+ final AirbyteRecordMessage invalidDateRecord = recordMessages .stream ().findFirst ().get ();
799
+
800
+ final AirbyteRecordMessageMetaChange expectedChange =
801
+ new AirbyteRecordMessageMetaChange ().withReason (Reason .SOURCE_SERIALIZATION_ERROR ).withChange (
802
+ Change .NULLED ).withField (COL_DATE_TIME );
803
+ final AirbyteRecordMessageMeta expectedMessageMeta = new AirbyteRecordMessageMeta ().withChanges (List .of (expectedChange ));
804
+ assertEquals (expectedMessageMeta , invalidDateRecord .getMeta ());
805
+
806
+ ObjectMapper mapper = new ObjectMapper ();
807
+ final JsonNode expectedDataWithoutCdcFields = mapper .readTree ("{\" id\" :120}" );
808
+ removeCDCColumns ((ObjectNode ) invalidDateRecord .getData ());
809
+ assertEquals (expectedDataWithoutCdcFields , invalidDateRecord .getData ());
810
+ }
811
+
737
812
private void createTablesToIncreaseSchemaHistorySize () {
738
813
for (int i = 0 ; i <= 200 ; i ++) {
739
814
final String tableName = generateRandomStringOf32Characters ();
0 commit comments