github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/dmlsink/mq/dispatcher/event_router_test.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package dispatcher 15 16 import ( 17 "testing" 18 19 timodel "github.com/pingcap/tidb/pkg/parser/model" 20 "github.com/pingcap/tiflow/cdc/model" 21 "github.com/pingcap/tiflow/cdc/sink/dmlsink/mq/dispatcher/partition" 22 "github.com/pingcap/tiflow/cdc/sink/dmlsink/mq/dispatcher/topic" 23 "github.com/pingcap/tiflow/pkg/config" 24 "github.com/pingcap/tiflow/pkg/sink" 25 "github.com/stretchr/testify/require" 26 ) 27 28 func newReplicaConfig4DispatcherTest() *config.ReplicaConfig { 29 return &config.ReplicaConfig{ 30 Sink: &config.SinkConfig{ 31 DispatchRules: []*config.DispatchRule{ 32 // rule-0 33 { 34 Matcher: []string{"test_default1.*"}, 35 PartitionRule: "default", 36 }, 37 // rule-1 38 { 39 Matcher: []string{"test_default2.*"}, 40 PartitionRule: "unknown-dispatcher", 41 }, 42 // rule-2 43 { 44 Matcher: []string{"test_table.*"}, 45 PartitionRule: "table", 46 TopicRule: "hello_{schema}_world", 47 }, 48 // rule-3 49 { 50 Matcher: []string{"test_index_value.*"}, 51 PartitionRule: "index-value", 52 TopicRule: "{schema}_world", 53 }, 54 // rule-4 55 { 56 Matcher: []string{"test.*"}, 57 PartitionRule: "rowid", 58 TopicRule: "hello_{schema}", 59 }, 60 // rule-5 61 { 62 Matcher: []string{"*.*", "!*.test"}, 63 PartitionRule: "ts", 64 TopicRule: "{schema}_{table}", 65 }, 66 // rule-6: hard code the topic 67 { 68 Matcher: []string{"hard_code_schema.*"}, 69 PartitionRule: "default", 70 TopicRule: "hard_code_topic", 71 }, 72 }, 73 }, 74 } 75 } 76 77 func TestEventRouter(t *testing.T) { 78 t.Parallel() 79 80 replicaConfig := config.GetDefaultReplicaConfig() 81 d, err := NewEventRouter(replicaConfig, config.ProtocolCanalJSON, "test", sink.KafkaScheme) 82 require.NoError(t, err) 83 require.Equal(t, "test", d.GetDefaultTopic()) 84 85 topicDispatcher, partitionDispatcher := d.matchDispatcher("test", "test") 86 require.IsType(t, &topic.StaticTopicDispatcher{}, topicDispatcher) 87 require.IsType(t, &partition.DefaultDispatcher{}, partitionDispatcher) 88 89 actual := topicDispatcher.Substitute("test", "test") 90 require.Equal(t, d.defaultTopic, actual) 91 92 replicaConfig = newReplicaConfig4DispatcherTest() 93 d, err = NewEventRouter(replicaConfig, config.ProtocolCanalJSON, "", sink.KafkaScheme) 94 require.NoError(t, err) 95 96 // no matched, use the default 97 topicDispatcher, partitionDispatcher = d.matchDispatcher("sbs", "test") 98 require.IsType(t, &topic.StaticTopicDispatcher{}, topicDispatcher) 99 require.IsType(t, &partition.DefaultDispatcher{}, partitionDispatcher) 100 101 // match rule-0 102 topicDispatcher, partitionDispatcher = d.matchDispatcher("test_default1", "test") 103 require.IsType(t, &topic.StaticTopicDispatcher{}, topicDispatcher) 104 require.IsType(t, &partition.DefaultDispatcher{}, partitionDispatcher) 105 106 // match rule-1 107 topicDispatcher, partitionDispatcher = d.matchDispatcher("test_default2", "test") 108 require.IsType(t, &topic.StaticTopicDispatcher{}, topicDispatcher) 109 require.IsType(t, &partition.DefaultDispatcher{}, partitionDispatcher) 110 111 // match rule-2 112 topicDispatcher, partitionDispatcher = d.matchDispatcher("test_table", "test") 113 require.IsType(t, &topic.DynamicTopicDispatcher{}, topicDispatcher) 114 require.IsType(t, &partition.TableDispatcher{}, partitionDispatcher) 115 116 // match rule-4 117 topicDispatcher, partitionDispatcher = d.matchDispatcher("test_index_value", "test") 118 require.IsType(t, &topic.DynamicTopicDispatcher{}, topicDispatcher) 119 require.IsType(t, &partition.IndexValueDispatcher{}, partitionDispatcher) 120 121 // match rule-4 122 topicDispatcher, partitionDispatcher = d.matchDispatcher("test", "table1") 123 require.IsType(t, &topic.DynamicTopicDispatcher{}, topicDispatcher) 124 require.IsType(t, &partition.IndexValueDispatcher{}, partitionDispatcher) 125 126 // match rule-5 127 topicDispatcher, partitionDispatcher = d.matchDispatcher("sbs", "table2") 128 require.IsType(t, &topic.DynamicTopicDispatcher{}, topicDispatcher) 129 require.IsType(t, &partition.TsDispatcher{}, partitionDispatcher) 130 131 // match rule-6 132 topicDispatcher, partitionDispatcher = d.matchDispatcher("hard_code_schema", "test") 133 require.IsType(t, &topic.StaticTopicDispatcher{}, topicDispatcher) 134 require.IsType(t, &partition.DefaultDispatcher{}, partitionDispatcher) 135 } 136 137 func TestGetActiveTopics(t *testing.T) { 138 t.Parallel() 139 140 replicaConfig := newReplicaConfig4DispatcherTest() 141 d, err := NewEventRouter(replicaConfig, config.ProtocolCanalJSON, "test", sink.KafkaScheme) 142 require.NoError(t, err) 143 names := []model.TableName{ 144 {Schema: "test_default1", Table: "table"}, 145 {Schema: "test_default2", Table: "table"}, 146 {Schema: "test_table", Table: "table"}, 147 {Schema: "test_index_value", Table: "table"}, 148 {Schema: "test", Table: "table"}, 149 {Schema: "sbs", Table: "table"}, 150 } 151 topics := d.GetActiveTopics(names) 152 require.Equal(t, []string{"test", "hello_test_table_world", "test_index_value_world", "hello_test", "sbs_table"}, topics) 153 } 154 155 func TestGetTopicForRowChange(t *testing.T) { 156 t.Parallel() 157 158 replicaConfig := newReplicaConfig4DispatcherTest() 159 d, err := NewEventRouter(replicaConfig, config.ProtocolCanalJSON, "test", "kafka") 160 require.NoError(t, err) 161 162 topicName := d.GetTopicForRowChange(&model.RowChangedEvent{ 163 TableInfo: &model.TableInfo{ 164 TableName: model.TableName{Schema: "test_default1", Table: "table"}, 165 }, 166 }) 167 require.Equal(t, "test", topicName) 168 169 topicName = d.GetTopicForRowChange(&model.RowChangedEvent{ 170 TableInfo: &model.TableInfo{ 171 TableName: model.TableName{Schema: "test_default2", Table: "table"}, 172 }, 173 }) 174 require.Equal(t, "test", topicName) 175 176 topicName = d.GetTopicForRowChange(&model.RowChangedEvent{ 177 TableInfo: &model.TableInfo{ 178 TableName: model.TableName{Schema: "test_table", Table: "table"}, 179 }, 180 }) 181 require.Equal(t, "hello_test_table_world", topicName) 182 183 topicName = d.GetTopicForRowChange(&model.RowChangedEvent{ 184 TableInfo: &model.TableInfo{ 185 TableName: model.TableName{Schema: "test_index_value", Table: "table"}, 186 }, 187 }) 188 require.Equal(t, "test_index_value_world", topicName) 189 190 topicName = d.GetTopicForRowChange(&model.RowChangedEvent{ 191 TableInfo: &model.TableInfo{ 192 TableName: model.TableName{Schema: "a", Table: "table"}, 193 }, 194 }) 195 require.Equal(t, "a_table", topicName) 196 } 197 198 func TestGetPartitionForRowChange(t *testing.T) { 199 t.Parallel() 200 201 replicaConfig := newReplicaConfig4DispatcherTest() 202 d, err := NewEventRouter(replicaConfig, config.ProtocolCanalJSON, "test", sink.KafkaScheme) 203 require.NoError(t, err) 204 205 cols := []*model.Column{ 206 { 207 Name: "id", 208 Value: 1, 209 Flag: model.HandleKeyFlag | model.PrimaryKeyFlag, 210 }, 211 } 212 tableInfo := model.BuildTableInfo("test_default1", "table", cols, [][]int{{0}}) 213 p, _, err := d.GetPartitionForRowChange(&model.RowChangedEvent{ 214 TableInfo: tableInfo, 215 Columns: model.Columns2ColumnDatas(cols, tableInfo), 216 }, 16) 217 require.NoError(t, err) 218 require.Equal(t, int32(14), p) 219 220 cols = []*model.Column{ 221 { 222 Name: "id", 223 Value: 1, 224 Flag: model.HandleKeyFlag | model.PrimaryKeyFlag, 225 }, 226 } 227 tableInfo = model.BuildTableInfo("test_default2", "table", cols, [][]int{{0}}) 228 p, _, err = d.GetPartitionForRowChange(&model.RowChangedEvent{ 229 TableInfo: tableInfo, 230 Columns: model.Columns2ColumnDatas(cols, tableInfo), 231 }, 16) 232 require.NoError(t, err) 233 require.Equal(t, int32(0), p) 234 235 p, _, err = d.GetPartitionForRowChange(&model.RowChangedEvent{ 236 TableInfo: &model.TableInfo{ 237 TableName: model.TableName{Schema: "test_table", Table: "table"}, 238 }, 239 CommitTs: 1, 240 }, 16) 241 require.NoError(t, err) 242 require.Equal(t, int32(15), p) 243 244 cols = []*model.Column{ 245 { 246 Name: "a", 247 Value: 11, 248 Flag: model.HandleKeyFlag | model.PrimaryKeyFlag, 249 }, { 250 Name: "b", 251 Value: 22, 252 Flag: 0, 253 }, 254 } 255 tableInfo = model.BuildTableInfo("test_index_value", "table", cols, [][]int{{0}}) 256 p, _, err = d.GetPartitionForRowChange(&model.RowChangedEvent{ 257 TableInfo: tableInfo, 258 Columns: model.Columns2ColumnDatas(cols, tableInfo), 259 }, 10) 260 require.NoError(t, err) 261 require.Equal(t, int32(1), p) 262 263 p, _, err = d.GetPartitionForRowChange(&model.RowChangedEvent{ 264 TableInfo: &model.TableInfo{ 265 TableName: model.TableName{Schema: "a", Table: "table"}, 266 }, 267 CommitTs: 1, 268 }, 2) 269 require.NoError(t, err) 270 require.Equal(t, int32(1), p) 271 } 272 273 func TestGetTopicForDDL(t *testing.T) { 274 t.Parallel() 275 276 replicaConfig := &config.ReplicaConfig{ 277 Sink: &config.SinkConfig{ 278 DispatchRules: []*config.DispatchRule{ 279 { 280 Matcher: []string{"test.*"}, 281 PartitionRule: "rowid", 282 TopicRule: "hello_{schema}", 283 }, 284 { 285 Matcher: []string{"*.*", "!*.test"}, 286 PartitionRule: "ts", 287 TopicRule: "{schema}_{table}", 288 }, 289 }, 290 }, 291 } 292 293 d, err := NewEventRouter(replicaConfig, config.ProtocolDefault, "test", "kafka") 294 require.NoError(t, err) 295 296 tests := []struct { 297 ddl *model.DDLEvent 298 expectedTopic string 299 }{ 300 { 301 ddl: &model.DDLEvent{ 302 TableInfo: &model.TableInfo{ 303 TableName: model.TableName{ 304 Schema: "test", 305 }, 306 }, 307 Type: timodel.ActionCreateSchema, 308 }, 309 expectedTopic: "test", 310 }, 311 { 312 ddl: &model.DDLEvent{ 313 TableInfo: &model.TableInfo{ 314 TableName: model.TableName{ 315 Schema: "test", 316 }, 317 }, 318 Type: timodel.ActionDropSchema, 319 }, 320 expectedTopic: "test", 321 }, 322 { 323 ddl: &model.DDLEvent{ 324 TableInfo: &model.TableInfo{ 325 TableName: model.TableName{ 326 Schema: "test", 327 Table: "tb1", 328 }, 329 }, 330 Type: timodel.ActionCreateTable, 331 }, 332 expectedTopic: "hello_test", 333 }, 334 { 335 ddl: &model.DDLEvent{ 336 TableInfo: &model.TableInfo{ 337 TableName: model.TableName{ 338 Schema: "test", 339 Table: "tb1", 340 }, 341 }, 342 Type: timodel.ActionDropTable, 343 }, 344 expectedTopic: "hello_test", 345 }, 346 { 347 ddl: &model.DDLEvent{ 348 TableInfo: &model.TableInfo{ 349 TableName: model.TableName{ 350 Schema: "test1", 351 Table: "view1", 352 }, 353 }, 354 Type: timodel.ActionDropView, 355 }, 356 expectedTopic: "test1_view1", 357 }, 358 { 359 ddl: &model.DDLEvent{ 360 TableInfo: &model.TableInfo{ 361 TableName: model.TableName{ 362 Schema: "test1", 363 Table: "tb1", 364 }, 365 }, 366 Type: timodel.ActionAddColumn, 367 }, 368 expectedTopic: "test1_tb1", 369 }, 370 { 371 ddl: &model.DDLEvent{ 372 TableInfo: &model.TableInfo{ 373 TableName: model.TableName{ 374 Schema: "test1", 375 Table: "tb1", 376 }, 377 }, 378 Type: timodel.ActionDropColumn, 379 }, 380 expectedTopic: "test1_tb1", 381 }, 382 { 383 ddl: &model.DDLEvent{ 384 PreTableInfo: &model.TableInfo{ 385 TableName: model.TableName{ 386 Schema: "test1", 387 Table: "tb1", 388 }, 389 }, 390 TableInfo: &model.TableInfo{ 391 TableName: model.TableName{ 392 Schema: "test1", 393 Table: "tb2", 394 }, 395 }, 396 Type: timodel.ActionRenameTable, 397 }, 398 expectedTopic: "test1_tb1", 399 }, 400 } 401 402 for _, test := range tests { 403 require.Equal(t, test.expectedTopic, d.GetTopicForDDL(test.ddl)) 404 } 405 } 406 407 func TestVerifyTables(t *testing.T) { 408 t.Parallel() 409 410 replicaConfig := newReplicaConfig4DispatcherTest() 411 d, err := NewEventRouter(replicaConfig, config.ProtocolCanalJSON, "test", sink.KafkaScheme) 412 require.NoError(t, err) 413 414 cols := []*model.Column{ 415 { 416 Name: "id", 417 Value: 1, 418 Flag: model.HandleKeyFlag | model.PrimaryKeyFlag, 419 }, 420 } 421 tableInfo := model.BuildTableInfo("test_index_value", "table", cols, [][]int{{0}}) 422 err = d.VerifyTables([]*model.TableInfo{tableInfo}) 423 require.NoError(t, err) 424 }