vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/vstreamer/planbuilder_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vstreamer 18 19 import ( 20 "fmt" 21 "testing" 22 23 "vitess.io/vitess/go/mysql/collations" 24 "vitess.io/vitess/go/vt/proto/topodata" 25 26 "github.com/stretchr/testify/require" 27 28 "vitess.io/vitess/go/test/utils" 29 30 "github.com/stretchr/testify/assert" 31 32 "vitess.io/vitess/go/json2" 33 "vitess.io/vitess/go/mysql" 34 "vitess.io/vitess/go/sqltypes" 35 "vitess.io/vitess/go/vt/vtgate/vindexes" 36 37 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 vschemapb "vitess.io/vitess/go/vt/proto/vschema" 40 ) 41 42 var testLocalVSchema *localVSchema 43 44 func init() { 45 input := `{ 46 "sharded": true, 47 "vindexes": { 48 "hash": { 49 "type": "hash" 50 }, 51 "region_vdx": { 52 "type": "region_experimental", 53 "params": { 54 "region_bytes": "1" 55 } 56 } 57 }, 58 "tables": { 59 "t1": { 60 "column_vindexes": [ 61 { 62 "column": "id", 63 "name": "hash" 64 } 65 ] 66 }, 67 "regional": { 68 "column_vindexes": [ 69 { 70 "columns": [ 71 "region", 72 "id" 73 ], 74 "name": "region_vdx" 75 } 76 ] 77 } 78 } 79 }` 80 var kspb vschemapb.Keyspace 81 if err := json2.Unmarshal([]byte(input), &kspb); err != nil { 82 panic(fmt.Errorf("Unmarshal failed: %v", err)) 83 } 84 srvVSchema := &vschemapb.SrvVSchema{ 85 Keyspaces: map[string]*vschemapb.Keyspace{ 86 "ks": &kspb, 87 }, 88 } 89 vschema := vindexes.BuildVSchema(srvVSchema) 90 testLocalVSchema = &localVSchema{ 91 keyspace: "ks", 92 vschema: vschema, 93 } 94 } 95 96 func TestMustSendDDL(t *testing.T) { 97 filter := &binlogdatapb.Filter{ 98 Rules: []*binlogdatapb.Rule{{ 99 Match: "/t1.*/", 100 }, { 101 Match: "t2", 102 }}, 103 } 104 testcases := []struct { 105 sql string 106 db string 107 output bool 108 }{{ 109 sql: "create database db", 110 output: false, 111 }, { 112 sql: "create table foo(id int)", 113 output: false, 114 }, { 115 sql: "create table db.foo(id int)", 116 output: false, 117 }, { 118 sql: "create table mydb.foo(id int)", 119 output: false, 120 }, { 121 sql: "create table t1a(id int)", 122 output: true, 123 }, { 124 sql: "create table db.t1a(id int)", 125 output: false, 126 }, { 127 sql: "create table mydb.t1a(id int)", 128 output: true, 129 }, { 130 sql: "rename table t1a to foo, foo to bar", 131 output: true, 132 }, { 133 sql: "rename table foo to t1a, foo to bar", 134 output: true, 135 }, { 136 sql: "rename table foo to bar, t1a to bar", 137 output: true, 138 }, { 139 sql: "rename table foo to bar, bar to foo", 140 output: false, 141 }, { 142 sql: "drop table t1a, foo", 143 output: true, 144 }, { 145 sql: "drop table foo, t1a", 146 output: true, 147 }, { 148 sql: "drop table foo, bar", 149 output: false, 150 }, { 151 sql: "bad query", 152 output: true, 153 }, { 154 sql: "select * from t", 155 output: true, 156 }, { 157 sql: "drop table t2", 158 output: true, 159 }, { 160 sql: "create table t1a(id int)", 161 db: "db", 162 output: false, 163 }, { 164 sql: "create table t1a(id int)", 165 db: "mydb", 166 output: true, 167 }} 168 for _, tcase := range testcases { 169 q := mysql.Query{SQL: tcase.sql, Database: tcase.db} 170 got := mustSendDDL(q, "mydb", filter) 171 if got != tcase.output { 172 t.Errorf("%v: %v, want %v", q, got, tcase.output) 173 } 174 } 175 } 176 177 func TestPlanBuilder(t *testing.T) { 178 t1 := &Table{ 179 Name: "t1", 180 Fields: []*querypb.Field{{ 181 Name: "id", 182 Type: sqltypes.Int64, 183 }, { 184 Name: "val", 185 Type: sqltypes.VarBinary, 186 }}, 187 } 188 // t1alt has no id column 189 t1alt := &Table{ 190 Name: "t1", 191 Fields: []*querypb.Field{{ 192 Name: "val", 193 Type: sqltypes.VarBinary, 194 }}, 195 } 196 t2 := &Table{ 197 Name: "t2", 198 Fields: []*querypb.Field{{ 199 Name: "id", 200 Type: sqltypes.Int64, 201 }, { 202 Name: "val", 203 Type: sqltypes.VarBinary, 204 }}, 205 } 206 regional := &Table{ 207 Name: "regional", 208 Fields: []*querypb.Field{{ 209 Name: "region", 210 Type: sqltypes.Int64, 211 }, { 212 Name: "id", 213 Type: sqltypes.Int64, 214 }, { 215 Name: "val", 216 Type: sqltypes.VarBinary, 217 }}, 218 } 219 220 testcases := []struct { 221 inTable *Table 222 inRule *binlogdatapb.Rule 223 outPlan *Plan 224 outErr string 225 }{{ 226 inTable: t1, 227 inRule: &binlogdatapb.Rule{Match: "/.*/"}, 228 outPlan: &Plan{ 229 ColExprs: []ColExpr{{ 230 ColNum: 0, 231 Field: &querypb.Field{ 232 Name: "id", 233 Type: sqltypes.Int64, 234 }, 235 }, { 236 ColNum: 1, 237 Field: &querypb.Field{ 238 Name: "val", 239 Type: sqltypes.VarBinary, 240 }, 241 }}, 242 }, 243 }, { 244 inTable: t1, 245 inRule: &binlogdatapb.Rule{Match: "/.*/", Filter: "-80"}, 246 outPlan: &Plan{ 247 ColExprs: []ColExpr{{ 248 ColNum: 0, 249 Field: &querypb.Field{ 250 Name: "id", 251 Type: sqltypes.Int64, 252 }, 253 }, { 254 ColNum: 1, 255 Field: &querypb.Field{ 256 Name: "val", 257 Type: sqltypes.VarBinary, 258 }, 259 }}, 260 Filters: []Filter{{ 261 Opcode: VindexMatch, 262 ColNum: 0, 263 Value: sqltypes.NULL, 264 Vindex: nil, 265 VindexColumns: []int{0}, 266 KeyRange: nil, 267 }}, 268 }, 269 }, { 270 inTable: t1, 271 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select * from t1"}, 272 outPlan: &Plan{ 273 ColExprs: []ColExpr{{ 274 ColNum: 0, 275 Field: &querypb.Field{ 276 Name: "id", 277 Type: sqltypes.Int64, 278 }, 279 }, { 280 ColNum: 1, 281 Field: &querypb.Field{ 282 Name: "val", 283 Type: sqltypes.VarBinary, 284 }, 285 }}, 286 }, 287 }, { 288 inTable: t1, 289 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1"}, 290 outPlan: &Plan{ 291 ColExprs: []ColExpr{{ 292 ColNum: 0, 293 Field: &querypb.Field{ 294 Name: "id", 295 Type: sqltypes.Int64, 296 }, 297 }, { 298 ColNum: 1, 299 Field: &querypb.Field{ 300 Name: "val", 301 Type: sqltypes.VarBinary, 302 }, 303 }}, 304 }, 305 }, { 306 inTable: t1, 307 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select val, id from t1"}, 308 outPlan: &Plan{ 309 ColExprs: []ColExpr{{ 310 ColNum: 1, 311 Field: &querypb.Field{ 312 Name: "val", 313 Type: sqltypes.VarBinary, 314 }, 315 }, { 316 ColNum: 0, 317 Field: &querypb.Field{ 318 Name: "id", 319 Type: sqltypes.Int64, 320 }, 321 }}, 322 }, 323 }, { 324 inTable: t1, 325 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select val, id from t1 where in_keyrange(id, 'hash', '-80')"}, 326 outPlan: &Plan{ 327 ColExprs: []ColExpr{{ 328 ColNum: 1, 329 Field: &querypb.Field{ 330 Name: "val", 331 Type: sqltypes.VarBinary, 332 }, 333 }, { 334 ColNum: 0, 335 Field: &querypb.Field{ 336 Name: "id", 337 Type: sqltypes.Int64, 338 }, 339 }}, 340 Filters: []Filter{{ 341 Opcode: VindexMatch, 342 ColNum: 0, 343 Value: sqltypes.NULL, 344 Vindex: nil, 345 VindexColumns: []int{0}, 346 KeyRange: nil, 347 }}, 348 }, 349 }, { 350 inTable: t1, 351 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select val, id from t1 where in_keyrange('-80')"}, 352 outPlan: &Plan{ 353 ColExprs: []ColExpr{{ 354 ColNum: 1, 355 Field: &querypb.Field{ 356 Name: "val", 357 Type: sqltypes.VarBinary, 358 }, 359 }, { 360 ColNum: 0, 361 Field: &querypb.Field{ 362 Name: "id", 363 Type: sqltypes.Int64, 364 }, 365 }}, 366 Filters: []Filter{{ 367 Opcode: VindexMatch, 368 ColNum: 0, 369 Value: sqltypes.NULL, 370 Vindex: nil, 371 VindexColumns: []int{0}, 372 KeyRange: nil, 373 }}, 374 }, 375 }, { 376 inTable: t1, 377 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select val, id from t1 where id = 1"}, 378 outPlan: &Plan{ 379 ColExprs: []ColExpr{{ 380 ColNum: 1, 381 Field: &querypb.Field{ 382 Name: "val", 383 Type: sqltypes.VarBinary, 384 }, 385 }, { 386 ColNum: 0, 387 Field: &querypb.Field{ 388 Name: "id", 389 Type: sqltypes.Int64, 390 }, 391 }}, 392 Filters: []Filter{{ 393 Opcode: Equal, 394 ColNum: 0, 395 Value: sqltypes.NewInt64(1), 396 Vindex: nil, 397 VindexColumns: nil, 398 KeyRange: nil, 399 }}, 400 }, 401 }, { 402 inTable: t2, 403 inRule: &binlogdatapb.Rule{Match: "/t1/"}, 404 }, { 405 inTable: regional, 406 inRule: &binlogdatapb.Rule{Match: "regional", Filter: "select val, id from regional where in_keyrange('-80')"}, 407 outPlan: &Plan{ 408 ColExprs: []ColExpr{{ 409 ColNum: 2, 410 Field: &querypb.Field{ 411 Name: "val", 412 Type: sqltypes.VarBinary, 413 }, 414 }, { 415 ColNum: 1, 416 Field: &querypb.Field{ 417 Name: "id", 418 Type: sqltypes.Int64, 419 }, 420 }}, 421 Filters: []Filter{{ 422 Opcode: VindexMatch, 423 ColNum: 0, 424 Value: sqltypes.NULL, 425 Vindex: nil, 426 VindexColumns: []int{0, 1}, 427 KeyRange: nil, 428 }}, 429 }, 430 }, { 431 inTable: regional, 432 inRule: &binlogdatapb.Rule{Match: "regional", Filter: "select id, keyspace_id() from regional"}, 433 outPlan: &Plan{ 434 ColExprs: []ColExpr{{ 435 ColNum: 1, 436 Field: &querypb.Field{ 437 Name: "id", 438 Type: sqltypes.Int64, 439 }, 440 }, { 441 Field: &querypb.Field{ 442 Name: "keyspace_id", 443 Type: sqltypes.VarBinary, 444 }, 445 Vindex: testLocalVSchema.vschema.Keyspaces["ks"].Vindexes["region_vdx"], 446 VindexColumns: []int{0, 1}, 447 }}, 448 }, 449 }, { 450 inTable: t1, 451 inRule: &binlogdatapb.Rule{Match: "/*/"}, 452 outErr: "error parsing regexp: missing argument to repetition operator: `*`", 453 }, { 454 inTable: t2, 455 inRule: &binlogdatapb.Rule{Match: "/.*/", Filter: "-80"}, 456 outErr: `table t2 not found`, 457 }, { 458 inTable: t1alt, 459 inRule: &binlogdatapb.Rule{Match: "/.*/", Filter: "-80"}, 460 outErr: `column id not found in table t1`, 461 }, { 462 inTable: t1, 463 inRule: &binlogdatapb.Rule{Match: "/.*/", Filter: "80"}, 464 outErr: `malformed spec: doesn't define a range: "80"`, 465 }, { 466 inTable: t1, 467 inRule: &binlogdatapb.Rule{Match: "/.*/", Filter: "-80-"}, 468 outErr: `error parsing keyrange: -80-`, 469 }, { 470 inTable: t1, 471 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "bad query"}, 472 outErr: `syntax error at position 4 near 'bad'`, 473 }, { 474 inTable: t1, 475 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "delete from t1"}, 476 outErr: `unsupported: delete from t1`, 477 }, { 478 inTable: t1, 479 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select * from t1, t2"}, 480 outErr: `unsupported: select * from t1, t2`, 481 }, { 482 inTable: t1, 483 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select * from t1 join t2"}, 484 outErr: `unsupported: select * from t1 join t2`, 485 }, { 486 inTable: t1, 487 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select * from a.t1"}, 488 outErr: `unsupported: select * from a.t1`, 489 }, { 490 inTable: t1, 491 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select * from t2"}, 492 outErr: `unsupported: select expression table t2 does not match the table entry name t1`, 493 }, { 494 inTable: t1, 495 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select *, id from t1"}, 496 outErr: `unsupported: *, id`, 497 }, { 498 inTable: t1, 499 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where max(id)"}, 500 outErr: `unsupported constraint: max(id)`, 501 }, { 502 inTable: t1, 503 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id)"}, 504 outErr: `unsupported: id`, 505 }, { 506 inTable: t1, 507 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(*, 'hash', '-80')"}, 508 outErr: `[BUG] unexpected: *sqlparser.StarExpr *`, 509 }, { 510 inTable: t1, 511 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(1, 'hash', '-80')"}, 512 outErr: `[BUG] unexpected: *sqlparser.Literal 1`, 513 }, { 514 inTable: t1, 515 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id, 'lookup', '-80')"}, 516 outErr: `vindex must be Unique to be used for VReplication: lookup`, 517 }, { 518 inTable: t1, 519 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id, 'hash', '80')"}, 520 outErr: `malformed spec: doesn't define a range: "80"`, 521 }, { 522 inTable: t1, 523 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id, 'hash', '-80-')"}, 524 outErr: `unexpected in_keyrange parameter: '-80-'`, 525 }, { 526 // analyzeExpr tests. 527 inTable: t1, 528 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, * from t1"}, 529 outErr: `unsupported: *`, 530 }, { 531 inTable: t1, 532 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select none from t1"}, 533 outErr: "column `none` not found in table t1", 534 }, { 535 inTable: t1, 536 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val, max(val) from t1"}, 537 outErr: `unsupported function: max(val)`, 538 }, { 539 inTable: t1, 540 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id+1, val from t1"}, 541 outErr: `unsupported: id + 1`, 542 }, { 543 inTable: t1, 544 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select t1.id, val from t1"}, 545 outErr: `unsupported qualifier for column: t1.id`, 546 }, { 547 // selString 548 inTable: t1, 549 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id, *, '-80')"}, 550 outErr: `unsupported: *`, 551 }, { 552 inTable: t1, 553 inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id, 1+1, '-80')"}, 554 outErr: `unsupported: 1 + 1`, 555 }} 556 for _, tcase := range testcases { 557 t.Run(tcase.inRule.String(), func(t *testing.T) { 558 plan, err := buildPlan(tcase.inTable, testLocalVSchema, &binlogdatapb.Filter{ 559 Rules: []*binlogdatapb.Rule{tcase.inRule}, 560 }) 561 562 if tcase.outErr != "" { 563 assert.Nil(t, plan) 564 assert.EqualError(t, err, tcase.outErr) 565 return 566 } 567 568 require.NoError(t, err) 569 if tcase.outPlan == nil { 570 require.Nil(t, plan) 571 return 572 } 573 574 require.NotNil(t, plan) 575 plan.Table = nil 576 for ind := range plan.Filters { 577 plan.Filters[ind].KeyRange = nil 578 if plan.Filters[ind].Opcode == VindexMatch { 579 plan.Filters[ind].Value = sqltypes.NULL 580 } 581 plan.Filters[ind].Vindex = nil 582 plan.Filters[ind].Vindex = nil 583 } 584 utils.MustMatch(t, tcase.outPlan, plan) 585 }) 586 } 587 } 588 589 func TestPlanBuilderFilterComparison(t *testing.T) { 590 t1 := &Table{ 591 Name: "t1", 592 Fields: []*querypb.Field{{ 593 Name: "id", 594 Type: sqltypes.Int64, 595 }, { 596 Name: "val", 597 Type: sqltypes.VarBinary, 598 }}, 599 } 600 hashVindex, err := vindexes.NewHash("hash", nil) 601 require.NoError(t, err) 602 testcases := []struct { 603 name string 604 inFilter string 605 outFilters []Filter 606 outErr string 607 }{{ 608 name: "equal", 609 inFilter: "select * from t1 where id = 1", 610 outFilters: []Filter{{Opcode: Equal, ColNum: 0, Value: sqltypes.NewInt64(1)}}, 611 }, { 612 name: "not-equal", 613 inFilter: "select * from t1 where id <> 1", 614 outFilters: []Filter{{Opcode: NotEqual, ColNum: 0, Value: sqltypes.NewInt64(1)}}, 615 }, { 616 name: "greater", 617 inFilter: "select * from t1 where val > 'abc'", 618 outFilters: []Filter{{Opcode: GreaterThan, ColNum: 1, Value: sqltypes.NewVarChar("abc")}}, 619 }, { 620 name: "greater-than", 621 inFilter: "select * from t1 where id >= 1", 622 outFilters: []Filter{{Opcode: GreaterThanEqual, ColNum: 0, Value: sqltypes.NewInt64(1)}}, 623 }, { 624 name: "less-than-with-and", 625 inFilter: "select * from t1 where id < 2 and val <= 'xyz'", 626 outFilters: []Filter{{Opcode: LessThan, ColNum: 0, Value: sqltypes.NewInt64(2)}, 627 {Opcode: LessThanEqual, ColNum: 1, Value: sqltypes.NewVarChar("xyz")}, 628 }, 629 }, { 630 name: "vindex-and-operators", 631 inFilter: "select * from t1 where in_keyrange(id, 'hash', '-80') and id = 2 and val <> 'xyz'", 632 outFilters: []Filter{ 633 { 634 Opcode: VindexMatch, 635 ColNum: 0, 636 Value: sqltypes.NULL, 637 Vindex: hashVindex, 638 VindexColumns: []int{0}, 639 KeyRange: &topodata.KeyRange{ 640 Start: nil, 641 End: []byte("\200"), 642 }, 643 }, 644 {Opcode: Equal, ColNum: 0, Value: sqltypes.NewInt64(2)}, 645 {Opcode: NotEqual, ColNum: 1, Value: sqltypes.NewVarChar("xyz")}, 646 }, 647 }} 648 649 for _, tcase := range testcases { 650 t.Run(tcase.name, func(t *testing.T) { 651 plan, err := buildPlan(t1, testLocalVSchema, &binlogdatapb.Filter{ 652 Rules: []*binlogdatapb.Rule{{Match: "t1", Filter: tcase.inFilter}}, 653 }) 654 655 if tcase.outErr != "" { 656 assert.Nil(t, plan) 657 assert.EqualError(t, err, tcase.outErr) 658 return 659 } 660 require.NotNil(t, plan) 661 require.ElementsMatchf(t, tcase.outFilters, plan.Filters, "want %+v, got: %+v", tcase.outFilters, plan.Filters) 662 }) 663 } 664 } 665 666 func TestCompare(t *testing.T) { 667 type testcase struct { 668 opcode Opcode 669 columnValue, filterValue sqltypes.Value 670 want bool 671 } 672 int1 := sqltypes.NewInt32(1) 673 int2 := sqltypes.NewInt32(2) 674 testcases := []*testcase{ 675 {opcode: Equal, columnValue: int1, filterValue: int1, want: true}, 676 {opcode: Equal, columnValue: int1, filterValue: int2, want: false}, 677 {opcode: Equal, columnValue: int1, filterValue: sqltypes.NULL, want: false}, 678 {opcode: LessThan, columnValue: int2, filterValue: int1, want: false}, 679 {opcode: LessThan, columnValue: int1, filterValue: int2, want: true}, 680 {opcode: LessThan, columnValue: int1, filterValue: sqltypes.NULL, want: false}, 681 {opcode: GreaterThan, columnValue: int2, filterValue: int1, want: true}, 682 {opcode: GreaterThan, columnValue: int1, filterValue: int2, want: false}, 683 {opcode: GreaterThan, columnValue: int1, filterValue: sqltypes.NULL, want: false}, 684 {opcode: NotEqual, columnValue: int1, filterValue: int1, want: false}, 685 {opcode: NotEqual, columnValue: int1, filterValue: int2, want: true}, 686 {opcode: NotEqual, columnValue: sqltypes.NULL, filterValue: int1, want: false}, 687 {opcode: LessThanEqual, columnValue: int1, filterValue: sqltypes.NULL, want: false}, 688 {opcode: GreaterThanEqual, columnValue: int2, filterValue: int1, want: true}, 689 {opcode: LessThanEqual, columnValue: int2, filterValue: int1, want: false}, 690 {opcode: GreaterThanEqual, columnValue: int1, filterValue: int1, want: true}, 691 {opcode: LessThanEqual, columnValue: int1, filterValue: int2, want: true}, 692 } 693 for _, tc := range testcases { 694 t.Run("", func(t *testing.T) { 695 got, err := compare(tc.opcode, tc.columnValue, tc.filterValue, collations.CollationUtf8mb4ID) 696 require.NoError(t, err) 697 require.Equal(t, tc.want, got) 698 }) 699 } 700 }