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