vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/replicator_plan_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 vreplication
    18  
    19  import (
    20  	"encoding/json"
    21  	"strings"
    22  	"testing"
    23  
    24  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  
    28  	"vitess.io/vitess/go/sqltypes"
    29  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    30  )
    31  
    32  type TestReplicatorPlan struct {
    33  	VStreamFilter *binlogdatapb.Filter
    34  	TargetTables  []string
    35  	TablePlans    map[string]*TestTablePlan
    36  }
    37  
    38  type TestTablePlan struct {
    39  	TargetName   string
    40  	SendRule     string
    41  	InsertFront  string   `json:",omitempty"`
    42  	InsertValues string   `json:",omitempty"`
    43  	InsertOnDup  string   `json:",omitempty"`
    44  	Insert       string   `json:",omitempty"`
    45  	Update       string   `json:",omitempty"`
    46  	Delete       string   `json:",omitempty"`
    47  	PKReferences []string `json:",omitempty"`
    48  }
    49  
    50  func TestBuildPlayerPlan(t *testing.T) {
    51  	testcases := []struct {
    52  		input  *binlogdatapb.Filter
    53  		plan   *TestReplicatorPlan
    54  		planpk *TestReplicatorPlan
    55  		err    string
    56  	}{{
    57  		// Regular expression
    58  		input: &binlogdatapb.Filter{
    59  			Rules: []*binlogdatapb.Rule{{
    60  				Match: "/.*",
    61  			}},
    62  		},
    63  		plan: &TestReplicatorPlan{
    64  			VStreamFilter: &binlogdatapb.Filter{
    65  				Rules: []*binlogdatapb.Rule{{
    66  					Match:  "t1",
    67  					Filter: "select * from t1",
    68  				}},
    69  			},
    70  			TargetTables: []string{"t1"},
    71  			TablePlans: map[string]*TestTablePlan{
    72  				"t1": {
    73  					TargetName: "t1",
    74  					SendRule:   "t1",
    75  				},
    76  			},
    77  		},
    78  		planpk: &TestReplicatorPlan{
    79  			VStreamFilter: &binlogdatapb.Filter{
    80  				Rules: []*binlogdatapb.Rule{{
    81  					Match:  "t1",
    82  					Filter: "select * from t1",
    83  				}},
    84  			},
    85  			TargetTables: []string{"t1"},
    86  			TablePlans: map[string]*TestTablePlan{
    87  				"t1": {
    88  					TargetName: "t1",
    89  					SendRule:   "t1",
    90  				},
    91  			},
    92  		},
    93  	}, {
    94  		// Regular with keyrange
    95  		input: &binlogdatapb.Filter{
    96  			Rules: []*binlogdatapb.Rule{{
    97  				Match:  "/.*",
    98  				Filter: "-80",
    99  			}},
   100  		},
   101  		plan: &TestReplicatorPlan{
   102  			VStreamFilter: &binlogdatapb.Filter{
   103  				Rules: []*binlogdatapb.Rule{{
   104  					Match:  "t1",
   105  					Filter: "select * from t1 where in_keyrange('-80')",
   106  				}},
   107  			},
   108  			TargetTables: []string{"t1"},
   109  			TablePlans: map[string]*TestTablePlan{
   110  				"t1": {
   111  					TargetName: "t1",
   112  					SendRule:   "t1",
   113  				},
   114  			},
   115  		},
   116  		planpk: &TestReplicatorPlan{
   117  			VStreamFilter: &binlogdatapb.Filter{
   118  				Rules: []*binlogdatapb.Rule{{
   119  					Match:  "t1",
   120  					Filter: "select * from t1 where in_keyrange('-80')",
   121  				}},
   122  			},
   123  			TargetTables: []string{"t1"},
   124  			TablePlans: map[string]*TestTablePlan{
   125  				"t1": {
   126  					TargetName: "t1",
   127  					SendRule:   "t1",
   128  				},
   129  			},
   130  		},
   131  	}, {
   132  		// '*' expression
   133  		input: &binlogdatapb.Filter{
   134  			Rules: []*binlogdatapb.Rule{{
   135  				Match:  "t1",
   136  				Filter: "select * from t2",
   137  			}},
   138  		},
   139  		plan: &TestReplicatorPlan{
   140  			VStreamFilter: &binlogdatapb.Filter{
   141  				Rules: []*binlogdatapb.Rule{{
   142  					Match:  "t2",
   143  					Filter: "select * from t2",
   144  				}},
   145  			},
   146  			TargetTables: []string{"t1"},
   147  			TablePlans: map[string]*TestTablePlan{
   148  				"t2": {
   149  					TargetName: "t1",
   150  					SendRule:   "t2",
   151  				},
   152  			},
   153  		},
   154  		planpk: &TestReplicatorPlan{
   155  			VStreamFilter: &binlogdatapb.Filter{
   156  				Rules: []*binlogdatapb.Rule{{
   157  					Match:  "t2",
   158  					Filter: "select * from t2",
   159  				}},
   160  			},
   161  			TargetTables: []string{"t1"},
   162  			TablePlans: map[string]*TestTablePlan{
   163  				"t2": {
   164  					TargetName: "t1",
   165  					SendRule:   "t2",
   166  				},
   167  			},
   168  		},
   169  	}, {
   170  		// Explicit columns
   171  		input: &binlogdatapb.Filter{
   172  			Rules: []*binlogdatapb.Rule{{
   173  				Match:  "t1",
   174  				Filter: "select c1, c2 from t2",
   175  			}},
   176  		},
   177  		plan: &TestReplicatorPlan{
   178  			VStreamFilter: &binlogdatapb.Filter{
   179  				Rules: []*binlogdatapb.Rule{{
   180  					Match:  "t2",
   181  					Filter: "select c1, c2 from t2",
   182  				}},
   183  			},
   184  			TargetTables: []string{"t1"},
   185  			TablePlans: map[string]*TestTablePlan{
   186  				"t2": {
   187  					TargetName:   "t1",
   188  					SendRule:     "t2",
   189  					PKReferences: []string{"c1"},
   190  					InsertFront:  "insert into t1(c1,c2)",
   191  					InsertValues: "(:a_c1,:a_c2)",
   192  					Insert:       "insert into t1(c1,c2) values (:a_c1,:a_c2)",
   193  					Update:       "update t1 set c2=:a_c2 where c1=:b_c1",
   194  					Delete:       "delete from t1 where c1=:b_c1",
   195  				},
   196  			},
   197  		},
   198  		planpk: &TestReplicatorPlan{
   199  			VStreamFilter: &binlogdatapb.Filter{
   200  				Rules: []*binlogdatapb.Rule{{
   201  					Match:  "t2",
   202  					Filter: "select c1, c2, pk1, pk2 from t2",
   203  				}},
   204  			},
   205  			TargetTables: []string{"t1"},
   206  			TablePlans: map[string]*TestTablePlan{
   207  				"t2": {
   208  					TargetName:   "t1",
   209  					SendRule:     "t2",
   210  					PKReferences: []string{"c1", "pk1", "pk2"},
   211  					InsertFront:  "insert into t1(c1,c2)",
   212  					InsertValues: "(:a_c1,:a_c2)",
   213  					Insert:       "insert into t1(c1,c2) select :a_c1, :a_c2 from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   214  					Update:       "update t1 set c2=:a_c2 where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   215  					Delete:       "delete from t1 where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   216  				},
   217  			},
   218  		},
   219  	}, {
   220  		// partial group by
   221  		input: &binlogdatapb.Filter{
   222  			Rules: []*binlogdatapb.Rule{{
   223  				Match:  "t1",
   224  				Filter: "select c1, c2, c3 from t2 group by c3, c1",
   225  			}},
   226  		},
   227  		plan: &TestReplicatorPlan{
   228  			VStreamFilter: &binlogdatapb.Filter{
   229  				Rules: []*binlogdatapb.Rule{{
   230  					Match:  "t2",
   231  					Filter: "select c1, c2, c3 from t2",
   232  				}},
   233  			},
   234  			TargetTables: []string{"t1"},
   235  			TablePlans: map[string]*TestTablePlan{
   236  				"t2": {
   237  					TargetName:   "t1",
   238  					SendRule:     "t2",
   239  					PKReferences: []string{"c1"},
   240  					InsertFront:  "insert into t1(c1,c2,c3)",
   241  					InsertValues: "(:a_c1,:a_c2,:a_c3)",
   242  					InsertOnDup:  "on duplicate key update c2=values(c2)",
   243  					Insert:       "insert into t1(c1,c2,c3) values (:a_c1,:a_c2,:a_c3) on duplicate key update c2=values(c2)",
   244  					Update:       "update t1 set c2=:a_c2 where c1=:b_c1",
   245  					Delete:       "update t1 set c2=null where c1=:b_c1",
   246  				},
   247  			},
   248  		},
   249  		planpk: &TestReplicatorPlan{
   250  			VStreamFilter: &binlogdatapb.Filter{
   251  				Rules: []*binlogdatapb.Rule{{
   252  					Match:  "t2",
   253  					Filter: "select c1, c2, c3, pk1, pk2 from t2",
   254  				}},
   255  			},
   256  			TargetTables: []string{"t1"},
   257  			TablePlans: map[string]*TestTablePlan{
   258  				"t2": {
   259  					TargetName:   "t1",
   260  					SendRule:     "t2",
   261  					PKReferences: []string{"c1", "pk1", "pk2"},
   262  					InsertFront:  "insert into t1(c1,c2,c3)",
   263  					InsertValues: "(:a_c1,:a_c2,:a_c3)",
   264  					InsertOnDup:  "on duplicate key update c2=values(c2)",
   265  					Insert:       "insert into t1(c1,c2,c3) select :a_c1, :a_c2, :a_c3 from dual where (:a_pk1,:a_pk2) <= (1,'aaa') on duplicate key update c2=values(c2)",
   266  					Update:       "update t1 set c2=:a_c2 where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   267  					Delete:       "update t1 set c2=null where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   268  				},
   269  			},
   270  		},
   271  	}, {
   272  		// full group by
   273  		input: &binlogdatapb.Filter{
   274  			Rules: []*binlogdatapb.Rule{{
   275  				Match:  "t1",
   276  				Filter: "select c1, c2, c3 from t2 group by c3, c1, c2",
   277  			}},
   278  		},
   279  		plan: &TestReplicatorPlan{
   280  			VStreamFilter: &binlogdatapb.Filter{
   281  				Rules: []*binlogdatapb.Rule{{
   282  					Match:  "t2",
   283  					Filter: "select c1, c2, c3 from t2",
   284  				}},
   285  			},
   286  			TargetTables: []string{"t1"},
   287  			TablePlans: map[string]*TestTablePlan{
   288  				"t2": {
   289  					TargetName:   "t1",
   290  					SendRule:     "t2",
   291  					PKReferences: []string{"c1"},
   292  					InsertFront:  "insert ignore into t1(c1,c2,c3)",
   293  					InsertValues: "(:a_c1,:a_c2,:a_c3)",
   294  					Insert:       "insert ignore into t1(c1,c2,c3) values (:a_c1,:a_c2,:a_c3)",
   295  					Update:       "insert ignore into t1(c1,c2,c3) values (:a_c1,:a_c2,:a_c3)",
   296  				},
   297  			},
   298  		},
   299  		planpk: &TestReplicatorPlan{
   300  			VStreamFilter: &binlogdatapb.Filter{
   301  				Rules: []*binlogdatapb.Rule{{
   302  					Match:  "t2",
   303  					Filter: "select c1, c2, c3, pk1, pk2 from t2",
   304  				}},
   305  			},
   306  			TargetTables: []string{"t1"},
   307  			TablePlans: map[string]*TestTablePlan{
   308  				"t2": {
   309  					TargetName:   "t1",
   310  					SendRule:     "t2",
   311  					PKReferences: []string{"c1", "pk1", "pk2"},
   312  					InsertFront:  "insert ignore into t1(c1,c2,c3)",
   313  					InsertValues: "(:a_c1,:a_c2,:a_c3)",
   314  					Insert:       "insert ignore into t1(c1,c2,c3) select :a_c1, :a_c2, :a_c3 from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   315  					Update:       "insert ignore into t1(c1,c2,c3) select :a_c1, :a_c2, :a_c3 from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   316  				},
   317  			},
   318  		},
   319  	}, {
   320  		input: &binlogdatapb.Filter{
   321  			Rules: []*binlogdatapb.Rule{{
   322  				Match:  "t1",
   323  				Filter: "select foo(a) as c1, foo(a, b) as c2, c c3 from t1",
   324  			}},
   325  		},
   326  		plan: &TestReplicatorPlan{
   327  			VStreamFilter: &binlogdatapb.Filter{
   328  				Rules: []*binlogdatapb.Rule{{
   329  					Match:  "t1",
   330  					Filter: "select a, a, b, c from t1",
   331  				}},
   332  			},
   333  			TargetTables: []string{"t1"},
   334  			TablePlans: map[string]*TestTablePlan{
   335  				"t1": {
   336  					TargetName:   "t1",
   337  					SendRule:     "t1",
   338  					PKReferences: []string{"a"},
   339  					InsertFront:  "insert into t1(c1,c2,c3)",
   340  					InsertValues: "(foo(:a_a),foo(:a_a, :a_b),:a_c)",
   341  					Insert:       "insert into t1(c1,c2,c3) values (foo(:a_a),foo(:a_a, :a_b),:a_c)",
   342  					Update:       "update t1 set c2=foo(:a_a, :a_b), c3=:a_c where c1=(foo(:b_a))",
   343  					Delete:       "delete from t1 where c1=(foo(:b_a))",
   344  				},
   345  			},
   346  		},
   347  		planpk: &TestReplicatorPlan{
   348  			VStreamFilter: &binlogdatapb.Filter{
   349  				Rules: []*binlogdatapb.Rule{{
   350  					Match:  "t1",
   351  					Filter: "select a, a, b, c, pk1, pk2 from t1",
   352  				}},
   353  			},
   354  			TargetTables: []string{"t1"},
   355  			TablePlans: map[string]*TestTablePlan{
   356  				"t1": {
   357  					TargetName:   "t1",
   358  					SendRule:     "t1",
   359  					PKReferences: []string{"a", "pk1", "pk2"},
   360  					InsertFront:  "insert into t1(c1,c2,c3)",
   361  					InsertValues: "(foo(:a_a),foo(:a_a, :a_b),:a_c)",
   362  					Insert:       "insert into t1(c1,c2,c3) select foo(:a_a), foo(:a_a, :a_b), :a_c from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   363  					Update:       "update t1 set c2=foo(:a_a, :a_b), c3=:a_c where c1=(foo(:b_a)) and (:b_pk1,:b_pk2) <= (1,'aaa')",
   364  					Delete:       "delete from t1 where c1=(foo(:b_a)) and (:b_pk1,:b_pk2) <= (1,'aaa')",
   365  				},
   366  			},
   367  		},
   368  	}, {
   369  		input: &binlogdatapb.Filter{
   370  			Rules: []*binlogdatapb.Rule{{
   371  				Match:  "t1",
   372  				Filter: "select a + b as c1, c as c2 from t1",
   373  			}},
   374  		},
   375  		plan: &TestReplicatorPlan{
   376  			VStreamFilter: &binlogdatapb.Filter{
   377  				Rules: []*binlogdatapb.Rule{{
   378  					Match:  "t1",
   379  					Filter: "select a, b, c from t1",
   380  				}},
   381  			},
   382  			TargetTables: []string{"t1"},
   383  			TablePlans: map[string]*TestTablePlan{
   384  				"t1": {
   385  					TargetName:   "t1",
   386  					SendRule:     "t1",
   387  					PKReferences: []string{"a", "b"},
   388  					InsertFront:  "insert into t1(c1,c2)",
   389  					InsertValues: "(:a_a + :a_b,:a_c)",
   390  					Insert:       "insert into t1(c1,c2) values (:a_a + :a_b,:a_c)",
   391  					Update:       "update t1 set c2=:a_c where c1=(:b_a + :b_b)",
   392  					Delete:       "delete from t1 where c1=(:b_a + :b_b)",
   393  				},
   394  			},
   395  		},
   396  		planpk: &TestReplicatorPlan{
   397  			VStreamFilter: &binlogdatapb.Filter{
   398  				Rules: []*binlogdatapb.Rule{{
   399  					Match:  "t1",
   400  					Filter: "select a, b, c, pk1, pk2 from t1",
   401  				}},
   402  			},
   403  			TargetTables: []string{"t1"},
   404  			TablePlans: map[string]*TestTablePlan{
   405  				"t1": {
   406  					TargetName:   "t1",
   407  					SendRule:     "t1",
   408  					PKReferences: []string{"a", "b", "pk1", "pk2"},
   409  					InsertFront:  "insert into t1(c1,c2)",
   410  					InsertValues: "(:a_a + :a_b,:a_c)",
   411  					Insert:       "insert into t1(c1,c2) select :a_a + :a_b, :a_c from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   412  					Update:       "update t1 set c2=:a_c where c1=(:b_a + :b_b) and (:b_pk1,:b_pk2) <= (1,'aaa')",
   413  					Delete:       "delete from t1 where c1=(:b_a + :b_b) and (:b_pk1,:b_pk2) <= (1,'aaa')",
   414  				},
   415  			},
   416  		},
   417  	}, {
   418  		// Keywords as names.
   419  		input: &binlogdatapb.Filter{
   420  			Rules: []*binlogdatapb.Rule{{
   421  				Match:  "t1",
   422  				Filter: "select c1, c2, `primary` from `primary`",
   423  			}},
   424  		},
   425  		plan: &TestReplicatorPlan{
   426  			VStreamFilter: &binlogdatapb.Filter{
   427  				Rules: []*binlogdatapb.Rule{{
   428  					Match:  "primary",
   429  					Filter: "select c1, c2, `primary` from `primary`",
   430  				}},
   431  			},
   432  			TargetTables: []string{"t1"},
   433  			TablePlans: map[string]*TestTablePlan{
   434  				"primary": {
   435  					TargetName:   "t1",
   436  					SendRule:     "primary",
   437  					PKReferences: []string{"c1"},
   438  					InsertFront:  "insert into t1(c1,c2,`primary`)",
   439  					InsertValues: "(:a_c1,:a_c2,:a_primary)",
   440  					Insert:       "insert into t1(c1,c2,`primary`) values (:a_c1,:a_c2,:a_primary)",
   441  					Update:       "update t1 set c2=:a_c2, `primary`=:a_primary where c1=:b_c1",
   442  					Delete:       "delete from t1 where c1=:b_c1",
   443  				},
   444  			},
   445  		},
   446  		planpk: &TestReplicatorPlan{
   447  			VStreamFilter: &binlogdatapb.Filter{
   448  				Rules: []*binlogdatapb.Rule{{
   449  					Match:  "primary",
   450  					Filter: "select c1, c2, `primary`, pk1, pk2 from `primary`",
   451  				}},
   452  			},
   453  			TargetTables: []string{"t1"},
   454  			TablePlans: map[string]*TestTablePlan{
   455  				"primary": {
   456  					TargetName:   "t1",
   457  					SendRule:     "primary",
   458  					PKReferences: []string{"c1", "pk1", "pk2"},
   459  					InsertFront:  "insert into t1(c1,c2,`primary`)",
   460  					InsertValues: "(:a_c1,:a_c2,:a_primary)",
   461  					Insert:       "insert into t1(c1,c2,`primary`) select :a_c1, :a_c2, :a_primary from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   462  					Update:       "update t1 set c2=:a_c2, `primary`=:a_primary where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   463  					Delete:       "delete from t1 where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   464  				},
   465  			},
   466  		},
   467  	}, {
   468  		// keyspace_id
   469  		input: &binlogdatapb.Filter{
   470  			Rules: []*binlogdatapb.Rule{{
   471  				Match:  "t1",
   472  				Filter: "select c1, c2, keyspace_id() ksid from t1",
   473  			}},
   474  		},
   475  		plan: &TestReplicatorPlan{
   476  			VStreamFilter: &binlogdatapb.Filter{
   477  				Rules: []*binlogdatapb.Rule{{
   478  					Match:  "t1",
   479  					Filter: "select c1, c2, keyspace_id() from t1",
   480  				}},
   481  			},
   482  			TargetTables: []string{"t1"},
   483  			TablePlans: map[string]*TestTablePlan{
   484  				"t1": {
   485  					TargetName:   "t1",
   486  					SendRule:     "t1",
   487  					PKReferences: []string{"c1"},
   488  					InsertFront:  "insert into t1(c1,c2,ksid)",
   489  					InsertValues: "(:a_c1,:a_c2,:a_keyspace_id)",
   490  					Insert:       "insert into t1(c1,c2,ksid) values (:a_c1,:a_c2,:a_keyspace_id)",
   491  					Update:       "update t1 set c2=:a_c2, ksid=:a_keyspace_id where c1=:b_c1",
   492  					Delete:       "delete from t1 where c1=:b_c1",
   493  				},
   494  			},
   495  		},
   496  		planpk: &TestReplicatorPlan{
   497  			VStreamFilter: &binlogdatapb.Filter{
   498  				Rules: []*binlogdatapb.Rule{{
   499  					Match:  "t1",
   500  					Filter: "select c1, c2, keyspace_id(), pk1, pk2 from t1",
   501  				}},
   502  			},
   503  			TargetTables: []string{"t1"},
   504  			TablePlans: map[string]*TestTablePlan{
   505  				"t1": {
   506  					TargetName:   "t1",
   507  					SendRule:     "t1",
   508  					PKReferences: []string{"c1", "pk1", "pk2"},
   509  					InsertFront:  "insert into t1(c1,c2,ksid)",
   510  					InsertValues: "(:a_c1,:a_c2,:a_keyspace_id)",
   511  					Insert:       "insert into t1(c1,c2,ksid) select :a_c1, :a_c2, :a_keyspace_id from dual where (:a_pk1,:a_pk2) <= (1,'aaa')",
   512  					Update:       "update t1 set c2=:a_c2, ksid=:a_keyspace_id where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   513  					Delete:       "delete from t1 where c1=:b_c1 and (:b_pk1,:b_pk2) <= (1,'aaa')",
   514  				},
   515  			},
   516  		},
   517  	}, {
   518  		// syntax error
   519  		input: &binlogdatapb.Filter{
   520  			Rules: []*binlogdatapb.Rule{{
   521  				Match:  "t1",
   522  				Filter: "bad query",
   523  			}},
   524  		},
   525  		err: "syntax error at position 4 near 'bad'",
   526  	}, {
   527  		// not a select
   528  		input: &binlogdatapb.Filter{
   529  			Rules: []*binlogdatapb.Rule{{
   530  				Match:  "t1",
   531  				Filter: "update t1 set val=1",
   532  			}},
   533  		},
   534  		err: "unexpected: update t1 set val = 1",
   535  	}, {
   536  		// no distinct
   537  		input: &binlogdatapb.Filter{
   538  			Rules: []*binlogdatapb.Rule{{
   539  				Match:  "t1",
   540  				Filter: "select distinct c1 from t1",
   541  			}},
   542  		},
   543  		err: "unexpected: select distinct c1 from t1",
   544  	}, {
   545  		// no ',' join
   546  		input: &binlogdatapb.Filter{
   547  			Rules: []*binlogdatapb.Rule{{
   548  				Match:  "t1",
   549  				Filter: "select * from t1, t2",
   550  			}},
   551  		},
   552  		err: "unexpected: select * from t1, t2",
   553  	}, {
   554  		// no join
   555  		input: &binlogdatapb.Filter{
   556  			Rules: []*binlogdatapb.Rule{{
   557  				Match:  "t1",
   558  				Filter: "select * from t1 join t2",
   559  			}},
   560  		},
   561  		err: "unexpected: select * from t1 join t2",
   562  	}, {
   563  		// no subqueries
   564  		input: &binlogdatapb.Filter{
   565  			Rules: []*binlogdatapb.Rule{{
   566  				Match:  "t1",
   567  				Filter: "select * from (select * from t2) as a",
   568  			}},
   569  		},
   570  		err: "unexpected: select * from (select * from t2) as a",
   571  	}, {
   572  		// cannot combine '*' with other
   573  		input: &binlogdatapb.Filter{
   574  			Rules: []*binlogdatapb.Rule{{
   575  				Match:  "t1",
   576  				Filter: "select *, c1 from t1",
   577  			}},
   578  		},
   579  		err: "unexpected: select *, c1 from t1",
   580  	}, {
   581  		// cannot combine '*' with other (different code path)
   582  		input: &binlogdatapb.Filter{
   583  			Rules: []*binlogdatapb.Rule{{
   584  				Match:  "t1",
   585  				Filter: "select c1, * from t1",
   586  			}},
   587  		},
   588  		err: "unexpected: *",
   589  	}, {
   590  		// no distinct in func
   591  		input: &binlogdatapb.Filter{
   592  			Rules: []*binlogdatapb.Rule{{
   593  				Match:  "t1",
   594  				Filter: "select hour(distinct c1) as a from t1",
   595  			}},
   596  		},
   597  		err: "syntax error at position 21 near 'distinct'",
   598  	}, {
   599  		// funcs need alias
   600  		input: &binlogdatapb.Filter{
   601  			Rules: []*binlogdatapb.Rule{{
   602  				Match:  "t1",
   603  				Filter: "select hour(c1) from t1",
   604  			}},
   605  		},
   606  		err: "expression needs an alias: hour(c1)",
   607  	}, {
   608  		// only count(*)
   609  		input: &binlogdatapb.Filter{
   610  			Rules: []*binlogdatapb.Rule{{
   611  				Match:  "t1",
   612  				Filter: "select count(c1) as c from t1",
   613  			}},
   614  		},
   615  		err: "only count(*) is supported: count(c1)",
   616  	}, {
   617  		// no sum(*)
   618  		input: &binlogdatapb.Filter{
   619  			Rules: []*binlogdatapb.Rule{{
   620  				Match:  "t1",
   621  				Filter: "select sum(*) as c from t1",
   622  			}},
   623  		},
   624  		err: "syntax error at position 13",
   625  	}, {
   626  		// sum should have only one argument
   627  		input: &binlogdatapb.Filter{
   628  			Rules: []*binlogdatapb.Rule{{
   629  				Match:  "t1",
   630  				Filter: "select sum(a, b) as c from t1",
   631  			}},
   632  		},
   633  		err: "syntax error at position 14",
   634  	}, {
   635  		// no complex expr in sum
   636  		input: &binlogdatapb.Filter{
   637  			Rules: []*binlogdatapb.Rule{{
   638  				Match:  "t1",
   639  				Filter: "select sum(a + b) as c from t1",
   640  			}},
   641  		},
   642  		err: "unexpected: sum(a + b)",
   643  	}, {
   644  		// no complex expr in group by
   645  		input: &binlogdatapb.Filter{
   646  			Rules: []*binlogdatapb.Rule{{
   647  				Match:  "t1",
   648  				Filter: "select a from t1 group by a + 1",
   649  			}},
   650  		},
   651  		err: "unexpected: a + 1",
   652  	}, {
   653  		// group by does not reference alias
   654  		input: &binlogdatapb.Filter{
   655  			Rules: []*binlogdatapb.Rule{{
   656  				Match:  "t1",
   657  				Filter: "select a as b from t1 group by a",
   658  			}},
   659  		},
   660  		err: "group by expression does not reference an alias in the select list: a",
   661  	}, {
   662  		// cannot group by aggr
   663  		input: &binlogdatapb.Filter{
   664  			Rules: []*binlogdatapb.Rule{{
   665  				Match:  "t1",
   666  				Filter: "select count(*) as a from t1 group by a",
   667  			}},
   668  		},
   669  		err: "group by expression is not allowed to reference an aggregate expression: a",
   670  	}}
   671  
   672  	PrimaryKeyInfos := map[string][]*ColumnInfo{
   673  		"t1": {&ColumnInfo{Name: "c1", IsPK: true}},
   674  	}
   675  
   676  	copyState := map[string]*sqltypes.Result{
   677  		"t1": sqltypes.MakeTestResult(
   678  			sqltypes.MakeTestFields(
   679  				"pk1|pk2",
   680  				"int64|varchar",
   681  			),
   682  			"1|aaa",
   683  		),
   684  	}
   685  
   686  	for _, tcase := range testcases {
   687  		plan, err := buildReplicatorPlan(getSource(tcase.input), PrimaryKeyInfos, nil, binlogplayer.NewStats())
   688  		gotPlan, _ := json.Marshal(plan)
   689  		wantPlan, _ := json.Marshal(tcase.plan)
   690  		if string(gotPlan) != string(wantPlan) {
   691  			t.Errorf("Filter(%v):\n%s, want\n%s", tcase.input, gotPlan, wantPlan)
   692  		}
   693  		gotErr := ""
   694  		if err != nil {
   695  			gotErr = err.Error()
   696  		}
   697  		if gotErr != tcase.err {
   698  			t.Errorf("Filter err(%v): %s, want %v", tcase.input, gotErr, tcase.err)
   699  		}
   700  
   701  		plan, err = buildReplicatorPlan(getSource(tcase.input), PrimaryKeyInfos, copyState, binlogplayer.NewStats())
   702  		if err != nil {
   703  			continue
   704  		}
   705  		gotPlan, _ = json.Marshal(plan)
   706  		wantPlan, _ = json.Marshal(tcase.planpk)
   707  		if string(gotPlan) != string(wantPlan) {
   708  			t.Errorf("Filter(%v,copyState):\n%s, want\n%s", tcase.input, gotPlan, wantPlan)
   709  		}
   710  	}
   711  }
   712  
   713  func getSource(filter *binlogdatapb.Filter) *binlogdatapb.BinlogSource {
   714  	return &binlogdatapb.BinlogSource{Filter: filter}
   715  }
   716  
   717  func TestBuildPlayerPlanNoDup(t *testing.T) {
   718  	PrimaryKeyInfos := map[string][]*ColumnInfo{
   719  		"t1": {&ColumnInfo{Name: "c1"}},
   720  		"t2": {&ColumnInfo{Name: "c2"}},
   721  	}
   722  	input := &binlogdatapb.Filter{
   723  		Rules: []*binlogdatapb.Rule{{
   724  			Match:  "t1",
   725  			Filter: "select * from t",
   726  		}, {
   727  			Match:  "t2",
   728  			Filter: "select * from t",
   729  		}},
   730  	}
   731  	_, err := buildReplicatorPlan(getSource(input), PrimaryKeyInfos, nil, binlogplayer.NewStats())
   732  	want := "more than one target for source table t"
   733  	if err == nil || !strings.Contains(err.Error(), want) {
   734  		t.Errorf("buildReplicatorPlan err: %v, must contain: %v", err, want)
   735  	}
   736  }
   737  
   738  func TestBuildPlayerPlanExclude(t *testing.T) {
   739  	PrimaryKeyInfos := map[string][]*ColumnInfo{
   740  		"t1": {&ColumnInfo{Name: "c1"}},
   741  		"t2": {&ColumnInfo{Name: "c2"}},
   742  	}
   743  	input := &binlogdatapb.Filter{
   744  		Rules: []*binlogdatapb.Rule{{
   745  			Match:  "t2",
   746  			Filter: "exclude",
   747  		}, {
   748  			Match:  "/.*",
   749  			Filter: "",
   750  		}},
   751  	}
   752  	plan, err := buildReplicatorPlan(getSource(input), PrimaryKeyInfos, nil, binlogplayer.NewStats())
   753  	assert.NoError(t, err)
   754  
   755  	want := &TestReplicatorPlan{
   756  		VStreamFilter: &binlogdatapb.Filter{
   757  			Rules: []*binlogdatapb.Rule{{
   758  				Match:  "t1",
   759  				Filter: "select * from t1",
   760  			}},
   761  		},
   762  		TargetTables: []string{"t1"},
   763  		TablePlans: map[string]*TestTablePlan{
   764  			"t1": {
   765  				TargetName: "t1",
   766  				SendRule:   "t1",
   767  			},
   768  		},
   769  	}
   770  
   771  	gotPlan, _ := json.Marshal(plan)
   772  	wantPlan, _ := json.Marshal(want)
   773  	assert.Equal(t, string(gotPlan), string(wantPlan))
   774  }