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 }