Skip to content

Commit aaebe8c

Browse files
committed
816 read invisible columns during snapshot
1 parent 13ec4d2 commit aaebe8c

3 files changed

Lines changed: 167 additions & 11 deletions

File tree

flow/connectors/mysql/qrep.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,27 +179,19 @@ func (c *MySqlConnector) GetDefaultPartitionKeyForTables(
179179

180180
func buildSelectedColumns(cols []*protos.FieldDescription, exclude []string) string {
181181
columns := []string{}
182-
selectAsterisk := true
183182
for _, col := range cols {
184183
if slices.Contains(exclude, col.Name) {
185-
selectAsterisk = false
186184
continue
187185
}
188186

189187
converted := common.QuoteMySQLIdentifier(col.Name)
190188
if col.Type == string(types.QValueKindUint16Enum) {
191189
converted = fmt.Sprintf("CAST(%s AS UNSIGNED) AS %s", converted, converted)
192-
selectAsterisk = false
193190
}
194191
columns = append(columns, converted)
195192
}
196193

197-
selectedColumns := "*"
198-
if !selectAsterisk {
199-
selectedColumns = strings.Join(columns, ", ")
200-
}
201-
202-
return selectedColumns
194+
return strings.Join(columns, ", ")
203195
}
204196

205197
func (c *MySqlConnector) PullQRepRecords(

flow/connectors/mysql/qrep_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestBuildSelectedColumns(t *testing.T) {
2222
{Name: "status", Type: string(types.QValueKindEnum)},
2323
},
2424
exclude: []string{},
25-
expectedSelectedColumns: "*",
25+
expectedSelectedColumns: "`id`, `name`, `status`",
2626
},
2727
{
2828
name: "one excluded column",
@@ -49,7 +49,7 @@ func TestBuildSelectedColumns(t *testing.T) {
4949
{Name: "status", Type: string(types.QValueKindEnum)},
5050
},
5151
exclude: []string{},
52-
expectedSelectedColumns: "*",
52+
expectedSelectedColumns: "`id`, `status`",
5353
},
5454
{
5555
name: "uint16enum with exclude",

flow/e2e/clickhouse_mysql_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2944
func (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+
595759
func (s ClickHouseSuite) Test_MySQL_Vector() {
596760
mysource, ok := s.source.(*MySqlSource)
597761
if !ok || mysource.Config.Flavor != protos.MySqlFlavor_MYSQL_MYSQL {

0 commit comments

Comments
 (0)