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  }