github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/filter/expr_filter_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 filter
    15  
    16  import (
    17  	"testing"
    18  
    19  	"github.com/pingcap/errors"
    20  	"github.com/pingcap/tidb/pkg/util/dbterror/plannererrors"
    21  	"github.com/pingcap/tiflow/cdc/model"
    22  	"github.com/pingcap/tiflow/dm/pkg/utils"
    23  	"github.com/pingcap/tiflow/pkg/config"
    24  	cerror "github.com/pingcap/tiflow/pkg/errors"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func TestShouldSkipDMLBasic(t *testing.T) {
    29  	helper := newTestHelper(t)
    30  	defer helper.close()
    31  	helper.getTk().MustExec("use test;")
    32  
    33  	type innerCase struct {
    34  		schema string
    35  		table  string
    36  		// set preColumns to non nil to indicate this case is for update
    37  		preColumns []*model.ColumnData
    38  		// set columns to non nil to indicate this case is for insert
    39  		// set columns to nil to indicate this case is for delete
    40  		columns []*model.ColumnData
    41  		preRow  []interface{}
    42  		row     []interface{}
    43  		ignore  bool
    44  	}
    45  
    46  	type testCase struct {
    47  		ddl   string
    48  		cfg   *config.FilterConfig
    49  		cases []innerCase
    50  	}
    51  
    52  	testCases := []testCase{
    53  		{
    54  			ddl: "create table test.student(id int primary key, name char(50), age int, gender char(10))",
    55  			cfg: &config.FilterConfig{
    56  				EventFilters: []*config.EventFilterRule{
    57  					{
    58  						Matcher:                  []string{"test.student"},
    59  						IgnoreInsertValueExpr:    "age >= 20 or gender = 'female'",
    60  						IgnoreDeleteValueExpr:    "age >= 32 and age < 48",
    61  						IgnoreUpdateOldValueExpr: "gender = 'male'",
    62  						IgnoreUpdateNewValueExpr: "age > 28",
    63  					},
    64  				},
    65  			},
    66  			cases: []innerCase{
    67  				{ // table name does not configure in matcher, no rule to filter it
    68  					schema: "test",
    69  					table:  "teacher",
    70  					columns: []*model.ColumnData{
    71  						{ColumnID: 0},
    72  					},
    73  					row:    []interface{}{999, "Will", 39, "male"},
    74  					ignore: false,
    75  				},
    76  				{ // schema name does not configure in matcher, no rule to filter it
    77  					schema: "no",
    78  					table:  "student",
    79  					columns: []*model.ColumnData{
    80  						{ColumnID: 0},
    81  					},
    82  					row:    []interface{}{888, "Li", 45, "male"},
    83  					ignore: false,
    84  				},
    85  				{ // insert
    86  					schema: "test",
    87  					table:  "student",
    88  					columns: []*model.ColumnData{
    89  						{ColumnID: 0},
    90  					},
    91  					row:    []interface{}{1, "Dongmen", 20, "male"},
    92  					ignore: true,
    93  				},
    94  				{ // insert
    95  					schema: "test",
    96  					table:  "student",
    97  					columns: []*model.ColumnData{
    98  						{ColumnID: 0},
    99  					},
   100  					row:    []interface{}{2, "Rustin", 18, "male"},
   101  					ignore: false,
   102  				},
   103  				{ // insert
   104  					schema: "test",
   105  					table:  "student",
   106  					columns: []*model.ColumnData{
   107  						{ColumnID: 0},
   108  					},
   109  					row:    []interface{}{3, "Susan", 3, "female"},
   110  					ignore: true,
   111  				},
   112  				{ // delete
   113  					schema: "test",
   114  					table:  "student",
   115  					preColumns: []*model.ColumnData{
   116  						{ColumnID: 0},
   117  					},
   118  					preRow: []interface{}{4, "Helen", 18, "female"},
   119  					ignore: false,
   120  				},
   121  				{ // delete
   122  					schema: "test",
   123  					table:  "student",
   124  					preColumns: []*model.ColumnData{
   125  						{ColumnID: 0},
   126  					},
   127  					preRow: []interface{}{5, "Madonna", 32, "female"},
   128  					ignore: true,
   129  				},
   130  				{ // delete
   131  					schema: "test",
   132  					table:  "student",
   133  					preColumns: []*model.ColumnData{
   134  						{ColumnID: 0},
   135  					},
   136  					preRow: []interface{}{6, "Madison", 48, "male"},
   137  					ignore: false,
   138  				},
   139  				{ // update, filler by new value
   140  					schema: "test",
   141  					table:  "student",
   142  					preColumns: []*model.ColumnData{
   143  						{ColumnID: 0},
   144  					},
   145  					preRow: []interface{}{7, "Marry", 28, "female"},
   146  					columns: []*model.ColumnData{
   147  						{ColumnID: 0},
   148  					},
   149  					row:    []interface{}{7, "Marry", 32, "female"},
   150  					ignore: true,
   151  				},
   152  				{ // update
   153  					schema: "test",
   154  					table:  "student",
   155  					preColumns: []*model.ColumnData{
   156  						{ColumnID: 0},
   157  					},
   158  					preRow: []interface{}{8, "Marilyn", 18, "female"},
   159  					columns: []*model.ColumnData{
   160  						{ColumnID: 0},
   161  					},
   162  					row:    []interface{}{8, "Monroe", 22, "female"},
   163  					ignore: false,
   164  				},
   165  				{ // update, filter by old value
   166  					schema: "test",
   167  					table:  "student",
   168  					preColumns: []*model.ColumnData{
   169  						{ColumnID: 0},
   170  					},
   171  					preRow: []interface{}{9, "Andreja", 25, "male"},
   172  					columns: []*model.ColumnData{
   173  						{ColumnID: 0},
   174  					},
   175  					row:    []interface{}{9, "Andreja", 25, "female"},
   176  					ignore: true,
   177  				},
   178  			},
   179  		},
   180  		{
   181  			ddl: "create table test.computer(id int primary key, brand char(50), price int)",
   182  			cfg: &config.FilterConfig{
   183  				EventFilters: []*config.EventFilterRule{
   184  					{
   185  						Matcher:               []string{"test.*"},
   186  						IgnoreInsertValueExpr: "price > 10000",
   187  					},
   188  				},
   189  			},
   190  			cases: []innerCase{
   191  				{ // insert
   192  					schema: "test",
   193  					table:  "computer",
   194  					columns: []*model.ColumnData{
   195  						{ColumnID: 0},
   196  					},
   197  					row:    []interface{}{1, "apple", 12888},
   198  					ignore: true,
   199  				},
   200  				{ // insert
   201  					schema: "test",
   202  					table:  "computer",
   203  					columns: []*model.ColumnData{
   204  						{ColumnID: 0},
   205  					},
   206  					row:    []interface{}{2, "microsoft", 5888},
   207  					ignore: false,
   208  				},
   209  			},
   210  		},
   211  		{ // test case for gbk charset
   212  			ddl: "create table test.poet(id int primary key, name varchar(50) CHARACTER SET GBK COLLATE gbk_bin, works char(100))",
   213  			cfg: &config.FilterConfig{
   214  				EventFilters: []*config.EventFilterRule{
   215  					{
   216  						Matcher:               []string{"*.*"},
   217  						IgnoreInsertValueExpr: "id <= 1 or name='辛弃疾' or works='离骚'",
   218  					},
   219  				},
   220  			},
   221  			cases: []innerCase{
   222  				{ // insert
   223  					schema: "test",
   224  					table:  "poet",
   225  					columns: []*model.ColumnData{
   226  						{ColumnID: 0},
   227  					},
   228  					row:    []interface{}{1, "李白", "静夜思"},
   229  					ignore: true,
   230  				},
   231  				{ // insert
   232  					schema: "test",
   233  					table:  "poet",
   234  					columns: []*model.ColumnData{
   235  						{ColumnID: 0},
   236  					},
   237  					row:    []interface{}{2, "杜甫", "石壕吏"},
   238  					ignore: false,
   239  				},
   240  				{ // insert
   241  					schema: "test",
   242  					table:  "poet",
   243  					columns: []*model.ColumnData{
   244  						{ColumnID: 0},
   245  					},
   246  					row:    []interface{}{4, "屈原", "离骚"},
   247  					ignore: true,
   248  				},
   249  				{ // insert
   250  					schema: "test",
   251  					table:  "poet",
   252  					columns: []*model.ColumnData{
   253  						{ColumnID: 0},
   254  					},
   255  					row:    []interface{}{3, "辛弃疾", "众里寻他千百度"},
   256  					ignore: true,
   257  				},
   258  			},
   259  		},
   260  		{
   261  			ddl: "create table test.season(id int primary key, name char(50), start char(100), end char(100))",
   262  			cfg: &config.FilterConfig{
   263  				EventFilters: []*config.EventFilterRule{
   264  					{ // do not ignore any event of test.season table
   265  						// and ignore events of !test.season table by configure SQL expression.
   266  						Matcher:                  []string{"*.*", "!test.season"},
   267  						IgnoreInsertValueExpr:    "id >= 1",
   268  						IgnoreUpdateNewValueExpr: "id >= 1",
   269  					},
   270  				},
   271  			},
   272  			cases: []innerCase{
   273  				{ // do not ignore any event of test.season table
   274  					schema: "test",
   275  					table:  "season",
   276  					columns: []*model.ColumnData{
   277  						{ColumnID: 0},
   278  					},
   279  					row:    []interface{}{1, "Spring", "January", "March"},
   280  					ignore: false,
   281  				},
   282  				{ // do not ignore any event of test.season table
   283  					schema: "test",
   284  					table:  "season",
   285  					preColumns: []*model.ColumnData{
   286  						{ColumnID: 0},
   287  					},
   288  					preRow: []interface{}{2, "Summer", "April", "June"},
   289  					columns: []*model.ColumnData{
   290  						{ColumnID: 0},
   291  					},
   292  					row:    []interface{}{2, "Summer", "April", "July"},
   293  					ignore: false,
   294  				},
   295  				{ // ignore insert event of test.autumn table
   296  					schema: "test",
   297  					table:  "autumn",
   298  					columns: []*model.ColumnData{
   299  						{ColumnID: 0},
   300  					},
   301  					row:    []interface{}{3, "Autumn", "July", "September"},
   302  					ignore: true,
   303  				},
   304  				{ // ignore update event of test.winter table
   305  					schema: "test",
   306  					table:  "winter",
   307  					preColumns: []*model.ColumnData{
   308  						{ColumnID: 0},
   309  					},
   310  					preRow: []interface{}{4, "Winter", "October", "January"},
   311  					columns: []*model.ColumnData{
   312  						{ColumnID: 0},
   313  					},
   314  					row:    []interface{}{4, "Winter", "October", "December"},
   315  					ignore: true,
   316  				},
   317  			},
   318  		},
   319  	}
   320  
   321  	sessCtx := utils.ZeroSessionCtx
   322  
   323  	for _, tc := range testCases {
   324  		tableInfo := helper.execDDL(tc.ddl)
   325  		f, err := newExprFilter("", tc.cfg)
   326  		require.Nil(t, err)
   327  		for _, c := range tc.cases {
   328  			rowDatums, err := utils.AdjustBinaryProtocolForDatum(sessCtx, c.row, tableInfo.Columns)
   329  			require.Nil(t, err)
   330  			preRowDatums, err := utils.AdjustBinaryProtocolForDatum(sessCtx, c.preRow, tableInfo.Columns)
   331  			require.Nil(t, err)
   332  			row := &model.RowChangedEvent{
   333  				TableInfo: &model.TableInfo{
   334  					TableName: model.TableName{
   335  						Schema: c.schema,
   336  						Table:  c.table,
   337  					},
   338  				},
   339  				Columns:    c.columns,
   340  				PreColumns: c.preColumns,
   341  			}
   342  			rawRow := model.RowChangedDatums{
   343  				RowDatums:    rowDatums,
   344  				PreRowDatums: preRowDatums,
   345  			}
   346  			ignore, err := f.shouldSkipDML(row, rawRow, tableInfo)
   347  			require.Nil(t, err)
   348  			require.Equal(t, c.ignore, ignore, "case: %+v", c, rowDatums)
   349  		}
   350  	}
   351  }
   352  
   353  // This test case is for testing when there are syntax error
   354  // or unknown error in the expression the return error type and message
   355  // are as expected.
   356  func TestShouldSkipDMLError(t *testing.T) {
   357  	helper := newTestHelper(t)
   358  	defer helper.close()
   359  	helper.getTk().MustExec("use test;")
   360  
   361  	type innerCase struct {
   362  		schema string
   363  		table  string
   364  		// set preColumns to non nil to indicate this case is for update
   365  		preColumns []*model.ColumnData
   366  		// set columns to non nil to indicate this case is for insert
   367  		// set columns to nil to indicate this case is for delete
   368  		columns []*model.ColumnData
   369  		preRow  []interface{}
   370  		row     []interface{}
   371  		ignore  bool
   372  		err     error
   373  		errMsg  string
   374  	}
   375  
   376  	type testCase struct {
   377  		ddl   string
   378  		cfg   *config.FilterConfig
   379  		cases []innerCase
   380  	}
   381  
   382  	testCases := []testCase{
   383  		{
   384  			ddl: "create table test.student(id int primary key, name char(50), age int, gender char(10))",
   385  			cfg: &config.FilterConfig{
   386  				EventFilters: []*config.EventFilterRule{
   387  					{
   388  						Matcher:                  []string{"test.student"},
   389  						IgnoreInsertValueExpr:    "age >= 20 or gender = 'female' and mather='a'",
   390  						IgnoreDeleteValueExpr:    "age >= 32 and and age < 48",
   391  						IgnoreUpdateOldValueExpr: "gender = 'male' and error(age) > 20",
   392  						IgnoreUpdateNewValueExpr: "age > 28",
   393  					},
   394  				},
   395  			},
   396  			cases: []innerCase{
   397  				{ // insert
   398  					schema: "test",
   399  					table:  "student",
   400  					columns: []*model.ColumnData{
   401  						{ColumnID: 0},
   402  					},
   403  					row:    []interface{}{999, "Will", 39, "male"},
   404  					ignore: false,
   405  					err:    cerror.ErrExpressionColumnNotFound,
   406  					errMsg: "Cannot find column 'mather' from table 'test.student' in",
   407  				},
   408  				{ // update
   409  					schema: "test",
   410  					table:  "student",
   411  					preColumns: []*model.ColumnData{
   412  						{ColumnID: 0},
   413  					},
   414  					preRow: []interface{}{876, "Li", 45, "female"},
   415  					columns: []*model.ColumnData{
   416  						{ColumnID: 0},
   417  					},
   418  					row:    []interface{}{1, "Dongmen", 20, "male"},
   419  					ignore: false,
   420  					err:    cerror.ErrExpressionParseFailed,
   421  					errMsg: "There is a syntax error in",
   422  				},
   423  				{ // delete
   424  					schema: "test",
   425  					table:  "student",
   426  					preColumns: []*model.ColumnData{
   427  						{ColumnID: 0},
   428  					},
   429  					preRow: []interface{}{876, "Li", 45, "female"},
   430  					ignore: false,
   431  					err:    cerror.ErrExpressionParseFailed,
   432  					errMsg: "There is a syntax error in",
   433  				},
   434  			},
   435  		},
   436  	}
   437  
   438  	sessCtx := utils.NewSessionCtx(map[string]string{
   439  		"time_zone": "UTC",
   440  	})
   441  
   442  	for _, tc := range testCases {
   443  		tableInfo := helper.execDDL(tc.ddl)
   444  		f, err := newExprFilter("", tc.cfg)
   445  		require.Nil(t, err)
   446  		for _, c := range tc.cases {
   447  			rowDatums, err := utils.AdjustBinaryProtocolForDatum(sessCtx, c.row, tableInfo.Columns)
   448  			require.Nil(t, err)
   449  			preRowDatums, err := utils.AdjustBinaryProtocolForDatum(sessCtx, c.preRow, tableInfo.Columns)
   450  			require.Nil(t, err)
   451  			row := &model.RowChangedEvent{
   452  				TableInfo: &model.TableInfo{
   453  					TableName: model.TableName{
   454  						Schema: c.schema,
   455  						Table:  c.table,
   456  					},
   457  				},
   458  				Columns:    c.columns,
   459  				PreColumns: c.preColumns,
   460  			}
   461  			rawRow := model.RowChangedDatums{
   462  				RowDatums:    rowDatums,
   463  				PreRowDatums: preRowDatums,
   464  			}
   465  			ignore, err := f.shouldSkipDML(row, rawRow, tableInfo)
   466  			require.True(t, errors.ErrorEqual(c.err, err), "case: %+v", c, err)
   467  			require.Contains(t, err.Error(), c.errMsg)
   468  			require.Equal(t, c.ignore, ignore)
   469  		}
   470  	}
   471  }
   472  
   473  // This test case is for testing when a table is updated,
   474  // the filter will works as expected.
   475  func TestShouldSkipDMLTableUpdated(t *testing.T) {
   476  	helper := newTestHelper(t)
   477  	defer helper.close()
   478  	helper.getTk().MustExec("use test;")
   479  
   480  	type innerCase struct {
   481  		schema    string
   482  		table     string
   483  		updateDDl string
   484  		// set preColumns to non nil to indicate this case is for update
   485  		preColumns []*model.ColumnData
   486  		// set columns to non nil to indicate this case is for insert
   487  		// set columns to nil to indicate this case is for delete
   488  		columns []*model.ColumnData
   489  		preRow  []interface{}
   490  		row     []interface{}
   491  		ignore  bool
   492  		err     error
   493  		errMsg  string
   494  	}
   495  
   496  	type testCase struct {
   497  		ddl   string
   498  		cfg   *config.FilterConfig
   499  		cases []innerCase
   500  	}
   501  
   502  	testCases := []testCase{
   503  		{ // add new column case.
   504  			ddl: "create table test.student(id int primary key, name char(50), age int, gender char(10))",
   505  			cfg: &config.FilterConfig{
   506  				EventFilters: []*config.EventFilterRule{
   507  					{
   508  						Matcher:                  []string{"test.student"},
   509  						IgnoreInsertValueExpr:    "age >= 20 and mather = 'Marisa'",
   510  						IgnoreDeleteValueExpr:    "age >= 32 and mather = 'Maria'",
   511  						IgnoreUpdateOldValueExpr: "gender = 'female'",
   512  						IgnoreUpdateNewValueExpr: "age > 28",
   513  					},
   514  				},
   515  			},
   516  			cases: []innerCase{
   517  				{ // insert
   518  					schema: "test",
   519  					table:  "student",
   520  					columns: []*model.ColumnData{
   521  						{ColumnID: 0},
   522  					},
   523  					row:    []interface{}{999, "Will", 39, "male"},
   524  					ignore: false,
   525  					err:    cerror.ErrExpressionColumnNotFound,
   526  					errMsg: "Cannot find column 'mather' from table 'test.student' in",
   527  				},
   528  				{ // insert
   529  					schema: "test",
   530  					table:  "student",
   531  					// we execute updateDDl to update the table info
   532  					updateDDl: "ALTER TABLE student ADD COLUMN mather char(50)",
   533  					columns: []*model.ColumnData{
   534  						{ColumnID: 0},
   535  					},
   536  					row:    []interface{}{999, "Will", 39, "male", "Marry"},
   537  					ignore: false,
   538  				},
   539  				{ // update
   540  					schema: "test",
   541  					table:  "student",
   542  					preColumns: []*model.ColumnData{
   543  						{ColumnID: 0},
   544  					},
   545  					preRow: []interface{}{876, "Li", 45, "female"},
   546  					columns: []*model.ColumnData{
   547  						{ColumnID: 0},
   548  					},
   549  					row:    []interface{}{1, "Dongmen", 20, "male"},
   550  					ignore: true,
   551  				},
   552  				{ // delete
   553  					schema: "test",
   554  					table:  "student",
   555  					preColumns: []*model.ColumnData{
   556  						{ColumnID: 0},
   557  					},
   558  					preRow: []interface{}{876, "Li", 45, "female", "Maria"},
   559  					ignore: true,
   560  				},
   561  			},
   562  		},
   563  		{ // drop column case
   564  			ddl: "create table test.worker(id int primary key, name char(50), age int, gender char(10), company char(50))",
   565  			cfg: &config.FilterConfig{
   566  				EventFilters: []*config.EventFilterRule{
   567  					{
   568  						Matcher:                  []string{"test.worker"},
   569  						IgnoreInsertValueExpr:    "age >= 20 and company = 'Apple'",
   570  						IgnoreDeleteValueExpr:    "age >= 32 and company = 'Google'",
   571  						IgnoreUpdateOldValueExpr: "gender = 'female'",
   572  						IgnoreUpdateNewValueExpr: "age > 28",
   573  					},
   574  				},
   575  			},
   576  			cases: []innerCase{
   577  				{ // insert
   578  					schema: "test",
   579  					table:  "worker",
   580  					columns: []*model.ColumnData{
   581  						{ColumnID: 0},
   582  					},
   583  					row:    []interface{}{999, "Will", 39, "male", "Apple"},
   584  					ignore: true,
   585  				},
   586  				{ // insert
   587  					schema: "test",
   588  					table:  "worker",
   589  					columns: []*model.ColumnData{
   590  						{ColumnID: 0},
   591  					},
   592  					row:    []interface{}{11, "Tom", 21, "male", "FaceBook"},
   593  					ignore: false,
   594  				},
   595  				{ // update
   596  					schema: "test",
   597  					table:  "worker",
   598  					preColumns: []*model.ColumnData{
   599  						{ColumnID: 0},
   600  					},
   601  					preRow: []interface{}{876, "Li", 45, "female"},
   602  					columns: []*model.ColumnData{
   603  						{ColumnID: 0},
   604  					},
   605  					row:    []interface{}{1, "Dongmen", 20, "male"},
   606  					ignore: true,
   607  				},
   608  				{ // delete
   609  					schema: "test",
   610  					table:  "worker",
   611  					preColumns: []*model.ColumnData{
   612  						{ColumnID: 0},
   613  					},
   614  					preRow: []interface{}{876, "Li", 45, "female", "Google"},
   615  					ignore: true,
   616  				},
   617  				{ // insert
   618  					schema:    "test",
   619  					table:     "worker",
   620  					updateDDl: "ALTER TABLE worker DROP COLUMN company",
   621  					columns: []*model.ColumnData{
   622  						{ColumnID: 0},
   623  					},
   624  					row:    []interface{}{999, "Will", 39, "male"},
   625  					ignore: false,
   626  					err:    cerror.ErrExpressionColumnNotFound,
   627  					errMsg: "Cannot find column 'company' from table 'test.worker' in",
   628  				},
   629  			},
   630  		},
   631  	}
   632  
   633  	sessCtx := utils.NewSessionCtx(map[string]string{
   634  		"time_zone": "UTC",
   635  	})
   636  
   637  	for _, tc := range testCases {
   638  		tableInfo := helper.execDDL(tc.ddl)
   639  		f, err := newExprFilter("", tc.cfg)
   640  		require.Nil(t, err)
   641  		for _, c := range tc.cases {
   642  			if c.updateDDl != "" {
   643  				tableInfo = helper.execDDL(c.updateDDl)
   644  			}
   645  			rowDatums, err := utils.AdjustBinaryProtocolForDatum(sessCtx, c.row, tableInfo.Columns)
   646  			require.Nil(t, err)
   647  			preRowDatums, err := utils.AdjustBinaryProtocolForDatum(sessCtx, c.preRow, tableInfo.Columns)
   648  			require.Nil(t, err)
   649  			row := &model.RowChangedEvent{
   650  				TableInfo: &model.TableInfo{
   651  					TableName: model.TableName{
   652  						Schema: c.schema,
   653  						Table:  c.table,
   654  					},
   655  				},
   656  				Columns:    c.columns,
   657  				PreColumns: c.preColumns,
   658  			}
   659  			rawRow := model.RowChangedDatums{
   660  				RowDatums:    rowDatums,
   661  				PreRowDatums: preRowDatums,
   662  			}
   663  			ignore, err := f.shouldSkipDML(row, rawRow, tableInfo)
   664  			require.True(t, errors.ErrorEqual(c.err, err), "case: %+v", c, err)
   665  			if err != nil {
   666  				require.Contains(t, err.Error(), c.errMsg)
   667  			}
   668  			require.Equal(t, c.ignore, ignore, "case: %+v", c)
   669  		}
   670  	}
   671  }
   672  
   673  func TestVerify(t *testing.T) {
   674  	helper := newTestHelper(t)
   675  	defer helper.close()
   676  	helper.getTk().MustExec("use test;")
   677  
   678  	type testCase struct {
   679  		ddls   []string
   680  		cfg    *config.FilterConfig
   681  		err    error
   682  		errMsg string
   683  	}
   684  
   685  	testCases := []testCase{
   686  		{
   687  			ddls: []string{
   688  				"create table test.worker(id int primary key, name char(50), age int, company char(50), gender char(50))",
   689  				"create table test.student(id int primary key, name char(50), age int, school char(50))",
   690  				"create table test.teacher(id int primary key, name char(50), age int, school char(50))",
   691  				"create table test.parent(id int primary key, name char(50), age int, company char(50))",
   692  			},
   693  			cfg: &config.FilterConfig{
   694  				EventFilters: []*config.EventFilterRule{
   695  					{
   696  						Matcher:                  []string{"test.worker"},
   697  						IgnoreInsertValueExpr:    "age >= 20 and company = 'Apple'",
   698  						IgnoreDeleteValueExpr:    "age >= 32 and company = 'Google'",
   699  						IgnoreUpdateOldValueExpr: "gender = 'female'",
   700  						IgnoreUpdateNewValueExpr: "age > 28",
   701  					},
   702  					{
   703  						Matcher:               []string{"test.student"},
   704  						IgnoreInsertValueExpr: "age < 20 and school = 'guanghua'",
   705  						IgnoreDeleteValueExpr: "age < 11 and school = 'dongfang'",
   706  					},
   707  					{
   708  						Matcher:               []string{"test.nonExist"},
   709  						IgnoreInsertValueExpr: "age < 20 or id > 100",
   710  						IgnoreDeleteValueExpr: "age > 100 or id < 20",
   711  					},
   712  					{
   713  						Matcher:                  []string{"test.parent"},
   714  						IgnoreUpdateNewValueExpr: "company = 'Apple'",
   715  					},
   716  					{
   717  						Matcher:                  []string{"*.*"},
   718  						IgnoreUpdateNewValueExpr: "id <= 100",
   719  					},
   720  				},
   721  			},
   722  		},
   723  		{
   724  			ddls: []string{
   725  				"create table test.child(id int primary key, name char(50), age int, parent_id int, school char(50))",
   726  			},
   727  			cfg: &config.FilterConfig{
   728  				EventFilters: []*config.EventFilterRule{
   729  					{
   730  						Matcher:               []string{"test.child"},
   731  						IgnoreInsertValueExpr: "company = 'Apple'",
   732  					},
   733  				},
   734  			},
   735  			err:    cerror.ErrExpressionColumnNotFound,
   736  			errMsg: "Cannot find column 'company' from table 'test.child' in",
   737  		},
   738  		{
   739  			ddls: []string{
   740  				"create table test.fruit(id int primary key, name char(50), price int)",
   741  			},
   742  			cfg: &config.FilterConfig{
   743  				EventFilters: []*config.EventFilterRule{
   744  					{
   745  						Matcher:               []string{"test.fruit"},
   746  						IgnoreInsertValueExpr: "error(price) == null",
   747  					},
   748  				},
   749  			},
   750  			err:    cerror.ErrExpressionParseFailed,
   751  			errMsg: "There is a syntax error in",
   752  		},
   753  	}
   754  
   755  	for _, tc := range testCases {
   756  		var tableInfos []*model.TableInfo
   757  		for _, ddl := range tc.ddls {
   758  			ti := helper.execDDL(ddl)
   759  			tableInfos = append(tableInfos, ti)
   760  		}
   761  		f, err := newExprFilter("", tc.cfg)
   762  		require.Nil(t, err)
   763  		err = f.verify(tableInfos)
   764  		require.True(t, errors.ErrorEqual(tc.err, err), "case: %+v", tc, err)
   765  		if err != nil {
   766  			require.Contains(t, err.Error(), tc.errMsg)
   767  		}
   768  	}
   769  }
   770  
   771  func TestGetColumnFromError(t *testing.T) {
   772  	type testCase struct {
   773  		err      error
   774  		expected string
   775  	}
   776  
   777  	testCases := []testCase{
   778  		{
   779  			err:      plannererrors.ErrUnknownColumn.FastGenByArgs("mother", "expression"),
   780  			expected: "mother",
   781  		},
   782  		{
   783  			err:      plannererrors.ErrUnknownColumn.FastGenByArgs("company", "expression"),
   784  			expected: "company",
   785  		},
   786  		{
   787  			err:      errors.New("what ever"),
   788  			expected: "what ever",
   789  		},
   790  	}
   791  
   792  	for _, tc := range testCases {
   793  		column := getColumnFromError(tc.err)
   794  		require.Equal(t, tc.expected, column, "case: %+v", tc)
   795  	}
   796  }