@@ -26,6 +26,21 @@ func mysqlEnumUsesOrdinals() bool {
2626 return internal .MySQLTestVersionIsMySQLPos ()
2727}
2828
29+ func (s ClickHouseSuite ) requireMySQLVersionAtLeast (version string ) {
30+ s .t .Helper ()
31+
32+ mySource , ok := s .source .(* MySqlSource )
33+ if ! ok || mySource .Config .Flavor != protos .MySqlFlavor_MYSQL_MYSQL {
34+ s .t .Skip ("only applies to mysql" )
35+ }
36+
37+ cmp , err := mySource .CompareServerVersion (s .t .Context (), version )
38+ require .NoError (s .t , err )
39+ if cmp < 0 {
40+ s .t .Skipf ("requires MySQL %s+" , version )
41+ }
42+ }
43+
2944func (s ClickHouseSuite ) Test_UnsignedMySQL () {
3045 if _ , ok := s .source .(* MySqlSource ); ! ok {
3146 s .t .Skip ("only applies to mysql" )
@@ -592,6 +607,155 @@ func (s ClickHouseSuite) Test_MySQL_Enum_Consistency_Version0() {
592607 RequireEnvCanceled (s .t , env )
593608}
594609
610+ func (s ClickHouseSuite ) Test_MySQL_InvisibleColumn_Consistency () {
611+ s .requireMySQLVersionAtLeast ("8.0.23" )
612+
613+ srcTableName := "test_invisible_column"
614+ srcFullName := s .attachSchemaSuffix (srcTableName )
615+ dstTableName := "test_invisible_column_dst"
616+
617+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (`
618+ CREATE TABLE IF NOT EXISTS %s (
619+ id INT PRIMARY KEY,
620+ vis INT NOT NULL,
621+ invis INT INVISIBLE NOT NULL
622+ )
623+ ` , srcFullName )))
624+
625+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (
626+ `INSERT INTO %s (id, vis, invis) VALUES (1, 10, 100), (2, 20, 200)` , srcFullName )))
627+
628+ connectionGen := FlowConnectionGenerationConfig {
629+ FlowJobName : s .attachSuffix (srcTableName ),
630+ TableNameMapping : map [string ]string {srcFullName : dstTableName },
631+ Destination : s .Peer ().Name ,
632+ }
633+ flowConnConfig := connectionGen .GenerateFlowConnectionConfigs (s )
634+ flowConnConfig .DoInitialSnapshot = true
635+
636+ tc := NewTemporalClient (s .t )
637+ env := ExecutePeerflow (s .t , tc , flowConnConfig )
638+ SetupCDCFlowStatusQuery (s .t , env , flowConnConfig )
639+
640+ EnvWaitForEqualTablesWithNames (env , s , "waiting on invisible snapshot" ,
641+ srcTableName , dstTableName , "id,vis,invis" )
642+
643+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (
644+ `INSERT INTO %s (id, vis, invis) VALUES (3, 30, 300)` , srcFullName )))
645+
646+ EnvWaitForEqualTablesWithNames (env , s , "waiting on invisible cdc" ,
647+ srcTableName , dstTableName , "id,vis,invis" )
648+
649+ env .Cancel (s .t .Context ())
650+ RequireEnvCanceled (s .t , env )
651+ }
652+
653+ func (s ClickHouseSuite ) Test_MySQL_GeneratedInvisiblePrimaryKey_Consistency () {
654+ s .requireMySQLVersionAtLeast ("8.0.30" )
655+
656+ srcTableName := "test_gipk"
657+ srcFullName := s .attachSchemaSuffix (srcTableName )
658+ dstTableName := "test_gipk_dst"
659+
660+ require .NoError (s .t , s .source .Exec (s .t .Context (), `SET SESSION sql_generate_invisible_primary_key=ON` ))
661+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (`
662+ CREATE TABLE IF NOT EXISTS %s (
663+ val INT NOT NULL
664+ )
665+ ` , srcFullName )))
666+ require .NoError (s .t , s .source .Exec (s .t .Context (), `SET SESSION sql_generate_invisible_primary_key=OFF` ))
667+
668+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (
669+ `INSERT INTO %s (val) VALUES (10), (20)` , srcFullName )))
670+
671+ connectionGen := FlowConnectionGenerationConfig {
672+ FlowJobName : s .attachSuffix (srcTableName ),
673+ TableNameMapping : map [string ]string {srcFullName : dstTableName },
674+ Destination : s .Peer ().Name ,
675+ }
676+ flowConnConfig := connectionGen .GenerateFlowConnectionConfigs (s )
677+ flowConnConfig .DoInitialSnapshot = true
678+
679+ tc := NewTemporalClient (s .t )
680+ env := ExecutePeerflow (s .t , tc , flowConnConfig )
681+ SetupCDCFlowStatusQuery (s .t , env , flowConnConfig )
682+
683+ EnvWaitForEqualTablesWithNames (env , s , "waiting on gipk snapshot" ,
684+ srcTableName , dstTableName , "my_row_id AS id,val" )
685+
686+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (
687+ `INSERT INTO %s (val) VALUES (30)` , srcFullName )))
688+
689+ EnvWaitForEqualTablesWithNames (env , s , "waiting on gipk cdc" ,
690+ srcTableName , dstTableName , "my_row_id AS id,val" )
691+
692+ ch , err := connclickhouse .Connect (s .t .Context (), nil , s .Peer ().GetClickhouseConfig ())
693+ require .NoError (s .t , err )
694+ defer ch .Close ()
695+
696+ var rowCount , missingRowIDCount uint64
697+ require .NoError (s .t , ch .QueryRow (s .t .Context (), fmt .Sprintf (
698+ `SELECT count(), countIf(my_row_id IS NULL OR my_row_id = 0)
699+ FROM %s FINAL WHERE _peerdb_is_deleted = 0` ,
700+ clickhouse .QuoteIdentifier (dstTableName ),
701+ )).Scan (& rowCount , & missingRowIDCount ))
702+ require .Equal (s .t , uint64 (3 ), rowCount )
703+ require .Zero (s .t , missingRowIDCount )
704+
705+ env .Cancel (s .t .Context ())
706+ RequireEnvCanceled (s .t , env )
707+ }
708+
709+ func (s ClickHouseSuite ) Test_MySQL_InvisibleColumn_WithExcludedColumn () {
710+ s .requireMySQLVersionAtLeast ("8.0.23" )
711+
712+ srcTableName := "test_invisible_with_exclude"
713+ srcFullName := s .attachSchemaSuffix (srcTableName )
714+ dstTableName := "test_invisible_with_exclude_dst"
715+
716+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (`
717+ CREATE TABLE IF NOT EXISTS %s (
718+ id INT PRIMARY KEY,
719+ visible INT NOT NULL,
720+ skipped INT NOT NULL,
721+ invis INT INVISIBLE NOT NULL
722+ )
723+ ` , srcFullName )))
724+
725+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (
726+ `INSERT INTO %s (id, visible, skipped, invis) VALUES (1, 10, 1000, 100), (2, 20, 2000, 200)` ,
727+ srcFullName )))
728+
729+ connectionGen := FlowConnectionGenerationConfig {
730+ FlowJobName : s .attachSuffix (srcTableName ),
731+ TableMappings : []* protos.TableMapping {{
732+ SourceTableIdentifier : srcFullName ,
733+ DestinationTableIdentifier : dstTableName ,
734+ ShardingKey : "id" ,
735+ Exclude : []string {"skipped" },
736+ }},
737+ Destination : s .Peer ().Name ,
738+ }
739+ flowConnConfig := connectionGen .GenerateFlowConnectionConfigs (s )
740+ flowConnConfig .DoInitialSnapshot = true
741+
742+ tc := NewTemporalClient (s .t )
743+ env := ExecutePeerflow (s .t , tc , flowConnConfig )
744+ SetupCDCFlowStatusQuery (s .t , env , flowConnConfig )
745+
746+ EnvWaitForEqualTablesWithNames (env , s , "waiting on excluded invisible snapshot" ,
747+ srcTableName , dstTableName , "id,visible,invis" )
748+
749+ require .NoError (s .t , s .source .Exec (s .t .Context (), fmt .Sprintf (
750+ `INSERT INTO %s (id, visible, skipped, invis) VALUES (3, 30, 3000, 300)` , srcFullName )))
751+
752+ EnvWaitForEqualTablesWithNames (env , s , "waiting on excluded invisible cdc" ,
753+ srcTableName , dstTableName , "id,visible,invis" )
754+
755+ env .Cancel (s .t .Context ())
756+ RequireEnvCanceled (s .t , env )
757+ }
758+
595759func (s ClickHouseSuite ) Test_MySQL_Vector () {
596760 mysource , ok := s .source .(* MySqlSource )
597761 if ! ok || mysource .Config .Flavor != protos .MySqlFlavor_MYSQL_MYSQL {
0 commit comments