github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/filter/filter_test.go (about)

     1  // Copyright 2020 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 filter
    15  
    16  import (
    17  	"testing"
    18  
    19  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    20  	"github.com/pingcap/tiflow/cdc/model"
    21  	bf "github.com/pingcap/tiflow/pkg/binlog-filter"
    22  	"github.com/pingcap/tiflow/pkg/config"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func TestShouldUseDefaultRules(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	filter, err := NewFilter(config.GetDefaultReplicaConfig(), "")
    30  	require.Nil(t, err)
    31  	require.True(t, filter.ShouldIgnoreTable("information_schema", ""))
    32  	require.True(t, filter.ShouldIgnoreTable("information_schema", "statistics"))
    33  	require.True(t, filter.ShouldIgnoreTable("performance_schema", ""))
    34  	require.False(t, filter.ShouldIgnoreTable("metric_schema", "query_duration"))
    35  	require.False(t, filter.ShouldIgnoreTable("sns", "user"))
    36  	require.True(t, filter.ShouldIgnoreTable("tidb_cdc", "repl_mark_a_a"))
    37  }
    38  
    39  func TestShouldUseCustomRules(t *testing.T) {
    40  	t.Parallel()
    41  
    42  	filter, err := NewFilter(&config.ReplicaConfig{
    43  		Filter: &config.FilterConfig{
    44  			Rules: []string{"sns.*", "ecom.*", "!sns.log", "!ecom.test"},
    45  		},
    46  	}, "")
    47  	require.Nil(t, err)
    48  	require.True(t, filter.ShouldIgnoreTable("other", ""))
    49  	require.True(t, filter.ShouldIgnoreTable("other", "what"))
    50  	require.False(t, filter.ShouldIgnoreTable("sns", ""))
    51  	require.False(t, filter.ShouldIgnoreTable("ecom", "order"))
    52  	require.False(t, filter.ShouldIgnoreTable("ecom", "order"))
    53  	require.True(t, filter.ShouldIgnoreTable("ecom", "test"))
    54  	require.True(t, filter.ShouldIgnoreTable("sns", "log"))
    55  	require.True(t, filter.ShouldIgnoreTable("information_schema", ""))
    56  
    57  	filter, err = NewFilter(&config.ReplicaConfig{
    58  		Filter: &config.FilterConfig{
    59  			// 1. match all schema and table
    60  			// 2. do not match test.season
    61  			// 3. match all table of schema school
    62  			// 4. do not match table school.teacher
    63  			Rules: []string{"*.*", "!test.season", "school.*", "!school.teacher"},
    64  		},
    65  	}, "")
    66  	require.True(t, filter.ShouldIgnoreTable("test", "season"))
    67  	require.False(t, filter.ShouldIgnoreTable("other", ""))
    68  	require.False(t, filter.ShouldIgnoreTable("school", "student"))
    69  	require.True(t, filter.ShouldIgnoreTable("school", "teacher"))
    70  	require.Nil(t, err)
    71  
    72  	filter, err = NewFilter(&config.ReplicaConfig{
    73  		Filter: &config.FilterConfig{
    74  			// 1. match all schema and table
    75  			// 2. do not match test.season
    76  			// 3. match all table of schema school
    77  			// 4. do not match table school.teacher
    78  			Rules: []string{"*.*", "!test.t1", "!test.t2"},
    79  		},
    80  	}, "")
    81  	require.False(t, filter.ShouldIgnoreTable("test", "season"))
    82  	require.True(t, filter.ShouldIgnoreTable("test", "t1"))
    83  	require.True(t, filter.ShouldIgnoreTable("test", "t2"))
    84  	require.Nil(t, err)
    85  }
    86  
    87  func TestShouldIgnoreDMLEvent(t *testing.T) {
    88  	t.Parallel()
    89  
    90  	testCases := []struct {
    91  		cases []struct {
    92  			schema string
    93  			table  string
    94  			ts     uint64
    95  			ignore bool
    96  		}
    97  		ignoreTxnStartTs []uint64
    98  		rules            []string
    99  	}{
   100  		{
   101  			cases: []struct {
   102  				schema string
   103  				table  string
   104  				ts     uint64
   105  				ignore bool
   106  			}{
   107  				{"sns", "ttta", 1, true},
   108  				{"ecom", "aabb", 2, false},
   109  				{"sns", "log", 3, true},
   110  				{"sns", "log", 4, true},
   111  				{"ecom", "test", 5, true},
   112  				{"test", "test", 6, true},
   113  				{"ecom", "log", 6, false},
   114  			},
   115  			ignoreTxnStartTs: []uint64{1, 3},
   116  			rules:            []string{"sns.*", "ecom.*", "!sns.log", "!ecom.test"},
   117  		},
   118  		{
   119  			cases: []struct {
   120  				schema string
   121  				table  string
   122  				ts     uint64
   123  				ignore bool
   124  			}{
   125  				{"S", "D1", 1, true},
   126  				{"S", "Da", 1, false},
   127  				{"S", "Db", 1, false},
   128  				{"S", "Daa", 1, false},
   129  			},
   130  			ignoreTxnStartTs: []uint64{},
   131  			rules:            []string{"*.*", "!S.D[!a-d]"},
   132  		},
   133  	}
   134  
   135  	for _, ftc := range testCases {
   136  		filter, err := NewFilter(&config.ReplicaConfig{
   137  			Filter: &config.FilterConfig{
   138  				IgnoreTxnStartTs: ftc.ignoreTxnStartTs,
   139  				Rules:            ftc.rules,
   140  			},
   141  		}, "")
   142  		require.Nil(t, err)
   143  		for _, tc := range ftc.cases {
   144  			dml := &model.RowChangedEvent{
   145  				TableInfo: &model.TableInfo{
   146  					TableName: model.TableName{
   147  						Schema: tc.schema,
   148  						Table:  tc.table,
   149  					},
   150  				},
   151  				StartTs: tc.ts,
   152  			}
   153  			ignoreDML, err := filter.ShouldIgnoreDMLEvent(dml, model.RowChangedDatums{}, nil)
   154  			require.Nil(t, err)
   155  			require.Equal(t, ignoreDML, tc.ignore)
   156  		}
   157  	}
   158  }
   159  
   160  func TestShouldIgnoreDDL(t *testing.T) {
   161  	t.Parallel()
   162  
   163  	testCases := []struct {
   164  		cases []struct {
   165  			startTs uint64
   166  			schema  string
   167  			table   string
   168  			query   string
   169  			ddlType timodel.ActionType
   170  			ignore  bool
   171  		}
   172  		rules        []string
   173  		ignoredTs    []uint64
   174  		eventFilters []*config.EventFilterRule
   175  	}{
   176  		{ // cases ignore by startTs
   177  			cases: []struct {
   178  				startTs uint64
   179  				schema  string
   180  				table   string
   181  				query   string
   182  				ddlType timodel.ActionType
   183  				ignore  bool
   184  			}{
   185  				{1, "ts", "", "create database test", timodel.ActionCreateSchema, true},
   186  				{2, "ts", "student", "drop database test2", timodel.ActionDropSchema, true},
   187  				{
   188  					3, "ts", "teacher", "ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci",
   189  					timodel.ActionModifySchemaCharsetAndCollate, true,
   190  				},
   191  				{4, "ts", "man", "create table test.t1(a int primary key)", timodel.ActionCreateTable, false},
   192  				{5, "ts", "fruit", "create table test.t1(a int primary key)", timodel.ActionCreateTable, false},
   193  				{6, "ts", "insect", "create table test.t1(a int primary key)", timodel.ActionCreateTable, false},
   194  			},
   195  			rules:     []string{"*.*"},
   196  			ignoredTs: []uint64{1, 2, 3},
   197  		},
   198  		{ // cases ignore by ddl type.
   199  			cases: []struct {
   200  				startTs uint64
   201  				schema  string
   202  				table   string
   203  				query   string
   204  				ddlType timodel.ActionType
   205  				ignore  bool
   206  			}{
   207  				{1, "event", "", "drop table t1", timodel.ActionDropTable, true},
   208  				{1, "event", "January", "drop index i on t1", timodel.ActionDropIndex, true},
   209  				{1, "event", "February", "drop index x2 on t2", timodel.ActionDropIndex, true},
   210  				{1, "event", "March", "create table t2(age int)", timodel.ActionCreateTable, false},
   211  				{1, "event", "April", "create table t2(age int)", timodel.ActionCreateTable, false},
   212  				{1, "event", "May", "create table t2(age int)", timodel.ActionCreateTable, false},
   213  			},
   214  			rules: []string{"*.*"},
   215  			eventFilters: []*config.EventFilterRule{
   216  				{
   217  					Matcher: []string{"event.*"},
   218  					IgnoreEvent: []bf.EventType{
   219  						bf.AlterTable, bf.DropTable,
   220  					},
   221  				},
   222  			},
   223  			ignoredTs: []uint64{},
   224  		},
   225  		{ // cases ignore by ddl query
   226  			cases: []struct {
   227  				startTs uint64
   228  				schema  string
   229  				table   string
   230  				query   string
   231  				ddlType timodel.ActionType
   232  				ignore  bool
   233  			}{
   234  				{1, "sql_pattern", "t1", "CREATE DATABASE sql_pattern", timodel.ActionCreateSchema, false},
   235  				{1, "sql_pattern", "t1", "DROP DATABASE sql_pattern", timodel.ActionDropSchema, true},
   236  				{
   237  					1, "sql_pattern", "t1",
   238  					"ALTER DATABASE `test_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'",
   239  					timodel.ActionModifySchemaCharsetAndCollate,
   240  					false,
   241  				},
   242  				{1, "sql_pattern", "t1", "CREATE TABLE t1(id int primary key)", timodel.ActionCreateTable, false},
   243  				{1, "sql_pattern", "t1", "DROP TABLE t1", timodel.ActionDropTable, true},
   244  				{1, "sql_pattern", "t1", "CREATE VIEW test.v AS SELECT * FROM t", timodel.ActionCreateView, true},
   245  			},
   246  			rules: []string{"*.*"},
   247  			eventFilters: []*config.EventFilterRule{
   248  				{
   249  					Matcher:   []string{"sql_pattern.*"},
   250  					IgnoreSQL: []string{"^DROP TABLE", "^CREATE VIEW", "^DROP DATABASE"},
   251  				},
   252  			},
   253  			ignoredTs: []uint64{},
   254  		},
   255  	}
   256  
   257  	for _, ftc := range testCases {
   258  		filter, err := NewFilter(&config.ReplicaConfig{
   259  			Filter: &config.FilterConfig{
   260  				Rules:            ftc.rules,
   261  				EventFilters:     ftc.eventFilters,
   262  				IgnoreTxnStartTs: ftc.ignoredTs,
   263  			},
   264  		}, "")
   265  		require.Nil(t, err)
   266  		for _, tc := range ftc.cases {
   267  			ddl := &model.DDLEvent{
   268  				StartTs: tc.startTs,
   269  				TableInfo: &model.TableInfo{
   270  					TableName: model.TableName{
   271  						Schema: tc.schema,
   272  						Table:  tc.table,
   273  					},
   274  				},
   275  				Query: tc.query,
   276  				Type:  tc.ddlType,
   277  			}
   278  			ignore, err := filter.ShouldIgnoreDDLEvent(ddl)
   279  			require.NoError(t, err)
   280  			require.Equal(t, tc.ignore, ignore, "%#v", tc)
   281  		}
   282  	}
   283  }
   284  
   285  func TestShouldDiscardDDL(t *testing.T) {
   286  	t.Parallel()
   287  	testCases := []struct {
   288  		cases []struct {
   289  			schema  string
   290  			table   string
   291  			query   string
   292  			ddlType timodel.ActionType
   293  			ignore  bool
   294  		}
   295  		rules        []string
   296  		eventFilters []*config.EventFilterRule
   297  	}{
   298  		{
   299  			// Discard by not allowed DDL type cases.
   300  			cases: []struct {
   301  				schema  string
   302  				table   string
   303  				query   string
   304  				ddlType timodel.ActionType
   305  				ignore  bool
   306  			}{
   307  				{"sns", "", "create database test", timodel.ActionCreateSchema, false},
   308  				{"sns", "", "drop database test", timodel.ActionDropSchema, false},
   309  				{"test", "", "create database test", timodel.ActionCreateSequence, true},
   310  			},
   311  			rules: []string{"*.*"},
   312  		},
   313  		{
   314  			// Discard by table name cases.
   315  			cases: []struct {
   316  				schema  string
   317  				table   string
   318  				query   string
   319  				ddlType timodel.ActionType
   320  				ignore  bool
   321  			}{
   322  				{"sns", "", "create database test", timodel.ActionCreateSchema, false},
   323  				{"sns", "", "drop database test", timodel.ActionDropSchema, false},
   324  				{
   325  					"sns", "", "ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci",
   326  					timodel.ActionModifySchemaCharsetAndCollate, false,
   327  				},
   328  				{"ecom", "", "create database test", timodel.ActionCreateSchema, false},
   329  				{"ecom", "aa", "create table test.t1(a int primary key)", timodel.ActionCreateTable, false},
   330  				{"ecom", "", "create database test", timodel.ActionCreateSchema, false},
   331  				{"test", "", "create database test", timodel.ActionCreateSchema, true},
   332  			},
   333  			rules: []string{"sns.*", "ecom.*", "!sns.log", "!ecom.test"},
   334  		},
   335  		{
   336  			// Discard by schema name cases.
   337  			cases: []struct {
   338  				schema  string
   339  				table   string
   340  				query   string
   341  				ddlType timodel.ActionType
   342  				ignore  bool
   343  			}{
   344  				{"schema", "C1", "create database test", timodel.ActionCreateSchema, false},
   345  				{"test", "", "drop database test1", timodel.ActionDropSchema, true},
   346  				{
   347  					"dbname", "", "ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci",
   348  					timodel.ActionModifySchemaCharsetAndCollate, true,
   349  				},
   350  				{"test", "aa", "create table test.t1(a int primary key)", timodel.ActionCreateTable, true},
   351  				{"schema", "C1", "create table test.t1(a int primary key)", timodel.ActionCreateTable, false},
   352  				{"schema", "", "create table test.t1(a int primary key)", timodel.ActionCreateTable, true},
   353  			},
   354  			rules: []string{"schema.C1"},
   355  		},
   356  	}
   357  
   358  	for _, ftc := range testCases {
   359  		filter, err := NewFilter(&config.ReplicaConfig{
   360  			Filter: &config.FilterConfig{
   361  				Rules:        ftc.rules,
   362  				EventFilters: ftc.eventFilters,
   363  			},
   364  		}, "")
   365  		require.Nil(t, err)
   366  		for _, tc := range ftc.cases {
   367  			ignore := filter.ShouldDiscardDDL(tc.ddlType, tc.schema, tc.table)
   368  			require.Equal(t, tc.ignore, ignore, "%#v", tc)
   369  		}
   370  	}
   371  }
   372  
   373  func TestIsAllowedDDL(t *testing.T) {
   374  	require.Len(t, ddlWhiteListMap, 36)
   375  	type testCase struct {
   376  		timodel.ActionType
   377  		allowed bool
   378  	}
   379  	testCases := make([]testCase, 0, len(ddlWhiteListMap))
   380  	for ddlType := range ddlWhiteListMap {
   381  		testCases = append(testCases, testCase{ddlType, true})
   382  	}
   383  	testCases = append(testCases, testCase{timodel.ActionAddForeignKey, false})
   384  	testCases = append(testCases, testCase{timodel.ActionDropForeignKey, false})
   385  	testCases = append(testCases, testCase{timodel.ActionCreateSequence, false})
   386  	testCases = append(testCases, testCase{timodel.ActionAlterSequence, false})
   387  	testCases = append(testCases, testCase{timodel.ActionDropSequence, false})
   388  	for _, tc := range testCases {
   389  		require.Equal(t, tc.allowed, isAllowedDDL(tc.ActionType), "%#v", tc)
   390  	}
   391  }
   392  
   393  func TestIsSchemaDDL(t *testing.T) {
   394  	cases := []struct {
   395  		actionType timodel.ActionType
   396  		isSchema   bool
   397  	}{
   398  		{timodel.ActionCreateSchema, true},
   399  		{timodel.ActionDropSchema, true},
   400  		{timodel.ActionModifySchemaCharsetAndCollate, true},
   401  		{timodel.ActionCreateTable, false},
   402  		{timodel.ActionDropTable, false},
   403  		{timodel.ActionTruncateTable, false},
   404  		{timodel.ActionAddColumn, false},
   405  	}
   406  	for _, tc := range cases {
   407  		require.Equal(t, tc.isSchema, IsSchemaDDL(tc.actionType), "%#v", tc)
   408  	}
   409  }