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  }