vitess.io/vitess@v0.16.2/go/vt/wrangler/vdiff_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 wrangler
    18  
    19  import (
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"vitess.io/vitess/go/mysql/collations"
    25  	"vitess.io/vitess/go/vt/sqlparser"
    26  	"vitess.io/vitess/go/vt/topo"
    27  	"vitess.io/vitess/go/vt/vtgate/engine"
    28  
    29  	"context"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  
    34  	"vitess.io/vitess/go/sqltypes"
    35  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    36  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    37  )
    38  
    39  func TestVDiffPlanSuccess(t *testing.T) {
    40  	schm := &tabletmanagerdatapb.SchemaDefinition{
    41  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
    42  			Name:              "t1",
    43  			Columns:           []string{"c1", "c2"},
    44  			PrimaryKeyColumns: []string{"c1"},
    45  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
    46  		}, {
    47  			Name:              "nonpktext",
    48  			Columns:           []string{"c1", "textcol"},
    49  			PrimaryKeyColumns: []string{"c1"},
    50  			Fields:            sqltypes.MakeTestFields("c1|textcol", "int64|varchar"),
    51  		}, {
    52  			Name:              "pktext",
    53  			Columns:           []string{"textcol", "c2"},
    54  			PrimaryKeyColumns: []string{"textcol"},
    55  			Fields:            sqltypes.MakeTestFields("textcol|c2", "varchar|int64"),
    56  		}, {
    57  			Name:              "multipk",
    58  			Columns:           []string{"c1", "c2"},
    59  			PrimaryKeyColumns: []string{"c1", "c2"},
    60  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
    61  		}, {
    62  			Name:              "aggr",
    63  			Columns:           []string{"c1", "c2", "c3", "c4"},
    64  			PrimaryKeyColumns: []string{"c1"},
    65  			Fields:            sqltypes.MakeTestFields("c1|c2|c3|c4", "int64|int64|int64|int64"),
    66  		}, {
    67  			Name:              "datze",
    68  			Columns:           []string{"id", "dt"},
    69  			PrimaryKeyColumns: []string{"id"},
    70  			Fields:            sqltypes.MakeTestFields("id|dt", "int64|datetime"),
    71  		}},
    72  	}
    73  
    74  	testcases := []struct {
    75  		input          *binlogdatapb.Rule
    76  		table          string
    77  		td             *tableDiffer
    78  		sourceTimeZone string
    79  	}{{
    80  		input: &binlogdatapb.Rule{
    81  			Match: "t1",
    82  		},
    83  		table: "t1",
    84  		td: &tableDiffer{
    85  			targetTable:      "t1",
    86  			sourceExpression: "select c1, c2 from t1 order by c1 asc",
    87  			targetExpression: "select c1, c2 from t1 order by c1 asc",
    88  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
    89  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
    90  			pkCols:           []int{0},
    91  			selectPks:        []int{0},
    92  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
    93  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
    94  		},
    95  	}, {
    96  		input: &binlogdatapb.Rule{
    97  			Match:  "t1",
    98  			Filter: "-80",
    99  		},
   100  		table: "t1",
   101  		td: &tableDiffer{
   102  			targetTable:      "t1",
   103  			sourceExpression: "select c1, c2 from t1 order by c1 asc",
   104  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   105  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   106  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   107  			pkCols:           []int{0},
   108  			selectPks:        []int{0},
   109  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   110  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   111  		},
   112  	}, {
   113  		input: &binlogdatapb.Rule{
   114  			Match:  "t1",
   115  			Filter: "select * from t1",
   116  		},
   117  		table: "t1",
   118  		td: &tableDiffer{
   119  			targetTable:      "t1",
   120  			sourceExpression: "select c1, c2 from t1 order by c1 asc",
   121  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   122  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   123  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   124  			pkCols:           []int{0},
   125  			selectPks:        []int{0},
   126  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   127  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   128  		},
   129  	}, {
   130  		input: &binlogdatapb.Rule{
   131  			Match:  "t1",
   132  			Filter: "select c2, c1 from t1",
   133  		},
   134  		table: "t1",
   135  		td: &tableDiffer{
   136  			targetTable:      "t1",
   137  			sourceExpression: "select c2, c1 from t1 order by c1 asc",
   138  			targetExpression: "select c2, c1 from t1 order by c1 asc",
   139  			compareCols:      []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}},
   140  			comparePKs:       []compareColInfo{{1, collations.Collation(nil), true}},
   141  			pkCols:           []int{1},
   142  			selectPks:        []int{1},
   143  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}),
   144  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}),
   145  		},
   146  	}, {
   147  		input: &binlogdatapb.Rule{
   148  			Match:  "t1",
   149  			Filter: "select c0 as c1, c2 from t2",
   150  		},
   151  		table: "t1",
   152  		td: &tableDiffer{
   153  			targetTable:      "t1",
   154  			sourceExpression: "select c0 as c1, c2 from t2 order by c1 asc",
   155  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   156  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   157  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   158  			pkCols:           []int{0},
   159  			selectPks:        []int{0},
   160  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   161  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   162  		},
   163  	}, {
   164  		// non-pk text column.
   165  		input: &binlogdatapb.Rule{
   166  			Match:  "nonpktext",
   167  			Filter: "select c1, textcol from nonpktext",
   168  		},
   169  		table: "nonpktext",
   170  		td: &tableDiffer{
   171  			targetTable:      "nonpktext",
   172  			sourceExpression: "select c1, textcol from nonpktext order by c1 asc",
   173  			targetExpression: "select c1, textcol from nonpktext order by c1 asc",
   174  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   175  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   176  			pkCols:           []int{0},
   177  			selectPks:        []int{0},
   178  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   179  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   180  		},
   181  	}, {
   182  		// non-pk text column, different order.
   183  		input: &binlogdatapb.Rule{
   184  			Match:  "nonpktext",
   185  			Filter: "select textcol, c1 from nonpktext",
   186  		},
   187  		table: "nonpktext",
   188  		td: &tableDiffer{
   189  			targetTable:      "nonpktext",
   190  			sourceExpression: "select textcol, c1 from nonpktext order by c1 asc",
   191  			targetExpression: "select textcol, c1 from nonpktext order by c1 asc",
   192  			compareCols:      []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}},
   193  			comparePKs:       []compareColInfo{{1, collations.Collation(nil), true}},
   194  			pkCols:           []int{1},
   195  			selectPks:        []int{1},
   196  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}),
   197  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}),
   198  		},
   199  	}, {
   200  		// pk text column.
   201  		input: &binlogdatapb.Rule{
   202  			Match:  "pktext",
   203  			Filter: "select textcol, c2 from pktext",
   204  		},
   205  		table: "pktext",
   206  		td: &tableDiffer{
   207  			targetTable:      "pktext",
   208  			sourceExpression: "select textcol, c2 from pktext order by textcol asc",
   209  			targetExpression: "select textcol, c2 from pktext order by textcol asc",
   210  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   211  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   212  			pkCols:           []int{0},
   213  			selectPks:        []int{0},
   214  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), false}}),
   215  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), false}}),
   216  		},
   217  	}, {
   218  		// pk text column, different order.
   219  		input: &binlogdatapb.Rule{
   220  			Match:  "pktext",
   221  			Filter: "select c2, textcol from pktext",
   222  		},
   223  		table: "pktext",
   224  		td: &tableDiffer{
   225  			targetTable:      "pktext",
   226  			sourceExpression: "select c2, textcol from pktext order by textcol asc",
   227  			targetExpression: "select c2, textcol from pktext order by textcol asc",
   228  			compareCols:      []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}},
   229  			comparePKs:       []compareColInfo{{1, collations.Collation(nil), true}},
   230  			pkCols:           []int{1},
   231  			selectPks:        []int{1},
   232  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}),
   233  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}),
   234  		},
   235  	}, {
   236  		// text column as expression.
   237  		input: &binlogdatapb.Rule{
   238  			Match:  "pktext",
   239  			Filter: "select c2, a+b as textcol from pktext",
   240  		},
   241  		table: "pktext",
   242  		td: &tableDiffer{
   243  			targetTable:      "pktext",
   244  			sourceExpression: "select c2, a + b as textcol from pktext order by textcol asc",
   245  			targetExpression: "select c2, textcol from pktext order by textcol asc",
   246  			compareCols:      []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}},
   247  			comparePKs:       []compareColInfo{{1, collations.Collation(nil), true}},
   248  			pkCols:           []int{1},
   249  			selectPks:        []int{1},
   250  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}),
   251  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}),
   252  		},
   253  	}, {
   254  		input: &binlogdatapb.Rule{
   255  			Match: "multipk",
   256  		},
   257  		table: "multipk",
   258  		td: &tableDiffer{
   259  			targetTable:      "multipk",
   260  			sourceExpression: "select c1, c2 from multipk order by c1 asc, c2 asc",
   261  			targetExpression: "select c1, c2 from multipk order by c1 asc, c2 asc",
   262  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}},
   263  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}},
   264  			pkCols:           []int{0, 1},
   265  			selectPks:        []int{0, 1},
   266  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}}),
   267  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}}),
   268  		},
   269  	}, {
   270  		// in_keyrange
   271  		input: &binlogdatapb.Rule{
   272  			Match:  "t1",
   273  			Filter: "select * from t1 where in_keyrange('-80')",
   274  		},
   275  		table: "t1",
   276  		td: &tableDiffer{
   277  			targetTable:      "t1",
   278  			sourceExpression: "select c1, c2 from t1 order by c1 asc",
   279  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   280  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   281  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   282  			pkCols:           []int{0},
   283  			selectPks:        []int{0},
   284  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   285  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   286  		},
   287  	}, {
   288  		// in_keyrange on RHS of AND.
   289  		// This is currently not a valid construct, but will be supported in the future.
   290  		input: &binlogdatapb.Rule{
   291  			Match:  "t1",
   292  			Filter: "select * from t1 where c2 = 2 and in_keyrange('-80')",
   293  		},
   294  		table: "t1",
   295  		td: &tableDiffer{
   296  			targetTable:      "t1",
   297  			sourceExpression: "select c1, c2 from t1 where c2 = 2 order by c1 asc",
   298  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   299  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   300  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   301  			pkCols:           []int{0},
   302  			selectPks:        []int{0},
   303  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   304  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   305  		},
   306  	}, {
   307  		// in_keyrange on LHS of AND.
   308  		// This is currently not a valid construct, but will be supported in the future.
   309  		input: &binlogdatapb.Rule{
   310  			Match:  "t1",
   311  			Filter: "select * from t1 where in_keyrange('-80') and c2 = 2",
   312  		},
   313  		table: "t1",
   314  		td: &tableDiffer{
   315  			targetTable:      "t1",
   316  			sourceExpression: "select c1, c2 from t1 where c2 = 2 order by c1 asc",
   317  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   318  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   319  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   320  			pkCols:           []int{0},
   321  			selectPks:        []int{0},
   322  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   323  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   324  		},
   325  	}, {
   326  		// in_keyrange on cascaded AND expression
   327  		// This is currently not a valid construct, but will be supported in the future.
   328  		input: &binlogdatapb.Rule{
   329  			Match:  "t1",
   330  			Filter: "select * from t1 where c2 = 2 and c1 = 1 and in_keyrange('-80')",
   331  		},
   332  		table: "t1",
   333  		td: &tableDiffer{
   334  			targetTable:      "t1",
   335  			sourceExpression: "select c1, c2 from t1 where c2 = 2 and c1 = 1 order by c1 asc",
   336  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   337  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   338  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   339  			pkCols:           []int{0},
   340  			selectPks:        []int{0},
   341  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   342  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   343  		},
   344  	}, {
   345  		// in_keyrange parenthesized
   346  		// This is currently not a valid construct, but will be supported in the future.
   347  		input: &binlogdatapb.Rule{
   348  			Match:  "t1",
   349  			Filter: "select * from t1 where (c2 = 2 and in_keyrange('-80'))",
   350  		},
   351  		table: "t1",
   352  		td: &tableDiffer{
   353  			targetTable:      "t1",
   354  			sourceExpression: "select c1, c2 from t1 where c2 = 2 order by c1 asc",
   355  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   356  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   357  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   358  			pkCols:           []int{0},
   359  			selectPks:        []int{0},
   360  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   361  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   362  		},
   363  	}, {
   364  		// group by
   365  		input: &binlogdatapb.Rule{
   366  			Match:  "t1",
   367  			Filter: "select * from t1 group by c1",
   368  		},
   369  		table: "t1",
   370  		td: &tableDiffer{
   371  			targetTable:      "t1",
   372  			sourceExpression: "select c1, c2 from t1 group by c1 order by c1 asc",
   373  			targetExpression: "select c1, c2 from t1 order by c1 asc",
   374  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   375  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   376  			pkCols:           []int{0},
   377  			selectPks:        []int{0},
   378  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   379  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   380  		},
   381  	}, {
   382  		// aggregations
   383  		input: &binlogdatapb.Rule{
   384  			Match:  "aggr",
   385  			Filter: "select c1, c2, count(*) as c3, sum(c4) as c4 from t1 group by c1",
   386  		},
   387  		table: "aggr",
   388  		td: &tableDiffer{
   389  			targetTable:      "aggr",
   390  			sourceExpression: "select c1, c2, count(*) as c3, sum(c4) as c4 from t1 group by c1 order by c1 asc",
   391  			targetExpression: "select c1, c2, c3, c4 from aggr order by c1 asc",
   392  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}, {2, collations.Collation(nil), false}, {3, collations.Collation(nil), false}},
   393  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   394  			pkCols:           []int{0},
   395  			selectPks:        []int{0},
   396  			sourcePrimitive: &engine.OrderedAggregate{
   397  				Aggregates: []*engine.AggregateParams{{
   398  					Opcode: engine.AggregateSum,
   399  					Col:    2,
   400  				}, {
   401  					Opcode: engine.AggregateSum,
   402  					Col:    3,
   403  				}},
   404  				GroupByKeys: []*engine.GroupByParams{{KeyCol: 0, WeightStringCol: -1}},
   405  				Input:       newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   406  			},
   407  			targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   408  		},
   409  	}, {
   410  		input: &binlogdatapb.Rule{
   411  			Match: "datze",
   412  		},
   413  		sourceTimeZone: "US/Pacific",
   414  		table:          "datze",
   415  		td: &tableDiffer{
   416  			targetTable:      "datze",
   417  			sourceExpression: "select id, dt from datze order by id asc",
   418  			targetExpression: "select id, convert_tz(dt, 'UTC', 'US/Pacific') as dt from datze order by id asc",
   419  			compareCols:      []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
   420  			comparePKs:       []compareColInfo{{0, collations.Collation(nil), true}},
   421  			pkCols:           []int{0},
   422  			selectPks:        []int{0},
   423  			sourcePrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   424  			targetPrimitive:  newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}),
   425  		},
   426  	}}
   427  
   428  	for _, tcase := range testcases {
   429  		t.Run(tcase.input.Filter, func(t *testing.T) {
   430  			filter := &binlogdatapb.Filter{Rules: []*binlogdatapb.Rule{tcase.input}}
   431  			df := &vdiff{sourceTimeZone: tcase.sourceTimeZone, targetTimeZone: "UTC"}
   432  			err := df.buildVDiffPlan(context.Background(), filter, schm, nil)
   433  			require.NoError(t, err, tcase.input)
   434  			require.Equal(t, 1, len(df.differs), tcase.input)
   435  			assert.Equal(t, tcase.td, df.differs[tcase.table], tcase.input)
   436  		})
   437  	}
   438  }
   439  
   440  func TestVDiffPlanFailure(t *testing.T) {
   441  	schm := &tabletmanagerdatapb.SchemaDefinition{
   442  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   443  			Name:              "t1",
   444  			Columns:           []string{"c1", "c2"},
   445  			PrimaryKeyColumns: []string{"c1"},
   446  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
   447  		}},
   448  	}
   449  
   450  	testcases := []struct {
   451  		input *binlogdatapb.Rule
   452  		err   string
   453  	}{{
   454  		input: &binlogdatapb.Rule{
   455  			Match:  "t1",
   456  			Filter: "bad query",
   457  		},
   458  		err: "syntax error at position 4 near 'bad'",
   459  	}, {
   460  		input: &binlogdatapb.Rule{
   461  			Match:  "t1",
   462  			Filter: "update t1 set c1=2",
   463  		},
   464  		err: "unexpected: update t1 set c1 = 2",
   465  	}, {
   466  		input: &binlogdatapb.Rule{
   467  			Match:  "t1",
   468  			Filter: "select c1+1 from t1",
   469  		},
   470  		err: "expression needs an alias: c1 + 1",
   471  	}, {
   472  		input: &binlogdatapb.Rule{
   473  			Match:  "t1",
   474  			Filter: "select next 2 values from t1",
   475  		},
   476  		err: "unexpected: select next 2 values from t1",
   477  	}, {
   478  		input: &binlogdatapb.Rule{
   479  			Match:  "t1",
   480  			Filter: "select c3 from t1",
   481  		},
   482  		err: "column c3 not found in table t1",
   483  	}}
   484  	for _, tcase := range testcases {
   485  		filter := &binlogdatapb.Filter{Rules: []*binlogdatapb.Rule{tcase.input}}
   486  		df := &vdiff{}
   487  		err := df.buildVDiffPlan(context.Background(), filter, schm, nil)
   488  		assert.EqualError(t, err, tcase.err, tcase.input)
   489  	}
   490  }
   491  
   492  func TestVDiffUnsharded(t *testing.T) {
   493  	env := newTestVDiffEnv(t, []string{"0"}, []string{"0"}, "", nil)
   494  	defer env.close()
   495  
   496  	schm := &tabletmanagerdatapb.SchemaDefinition{
   497  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   498  			Name:              "t1",
   499  			Columns:           []string{"c1", "c2"},
   500  			PrimaryKeyColumns: []string{"c1"},
   501  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
   502  		}},
   503  	}
   504  	env.tmc.schema = schm
   505  
   506  	fields := sqltypes.MakeTestFields(
   507  		"c1|c2",
   508  		"int64|int64",
   509  	)
   510  
   511  	testcases := []struct {
   512  		id      string
   513  		source  []*sqltypes.Result
   514  		target  []*sqltypes.Result
   515  		dr      *DiffReport
   516  		onlyPks bool
   517  		debug   bool
   518  	}{{
   519  		id: "1",
   520  		source: sqltypes.MakeTestStreamingResults(fields,
   521  			"1|3",
   522  			"2|4",
   523  			"---",
   524  			"3|1",
   525  		),
   526  		target: sqltypes.MakeTestStreamingResults(fields,
   527  			"1|3",
   528  			"---",
   529  			"2|4",
   530  			"3|1",
   531  		),
   532  		dr: &DiffReport{
   533  			ProcessedRows: 3,
   534  			MatchingRows:  3,
   535  			TableName:     "t1",
   536  		},
   537  	}, {
   538  		id: "2",
   539  		source: sqltypes.MakeTestStreamingResults(fields,
   540  			"1|3",
   541  		),
   542  		target: sqltypes.MakeTestStreamingResults(fields,
   543  			"1|3",
   544  			"---",
   545  			"2|4",
   546  			"3|1",
   547  		),
   548  		dr: &DiffReport{
   549  			ProcessedRows:   3,
   550  			MatchingRows:    1,
   551  			ExtraRowsTarget: 2,
   552  			TableName:       "t1",
   553  			ExtraRowsTargetDiffs: []*RowDiff{
   554  				{
   555  					Row: map[string]sqltypes.Value{
   556  						"c1": sqltypes.NewInt64(2),
   557  						"c2": sqltypes.NewInt64(4),
   558  					},
   559  					Query: "",
   560  				},
   561  			},
   562  		},
   563  	}, {
   564  		id: "3",
   565  		source: sqltypes.MakeTestStreamingResults(fields,
   566  			"1|3",
   567  			"---",
   568  			"2|4",
   569  			"3|1",
   570  		),
   571  		target: sqltypes.MakeTestStreamingResults(fields,
   572  			"1|3",
   573  		),
   574  		dr: &DiffReport{
   575  			ProcessedRows:   3,
   576  			MatchingRows:    1,
   577  			ExtraRowsSource: 2,
   578  			TableName:       "t1",
   579  			ExtraRowsSourceDiffs: []*RowDiff{
   580  				{
   581  					Row: map[string]sqltypes.Value{
   582  						"c1": sqltypes.NewInt64(2),
   583  						"c2": sqltypes.NewInt64(4),
   584  					},
   585  					Query: "",
   586  				},
   587  			},
   588  		},
   589  	}, {
   590  		id: "4",
   591  		source: sqltypes.MakeTestStreamingResults(fields,
   592  			"1|3",
   593  			"---",
   594  			"2|4",
   595  			"3|1",
   596  		),
   597  		target: sqltypes.MakeTestStreamingResults(fields,
   598  			"1|3",
   599  			"---",
   600  			"3|1",
   601  		),
   602  		dr: &DiffReport{
   603  			ProcessedRows:   3,
   604  			MatchingRows:    2,
   605  			ExtraRowsSource: 1,
   606  			TableName:       "t1",
   607  			ExtraRowsSourceDiffs: []*RowDiff{
   608  				{
   609  					Row: map[string]sqltypes.Value{
   610  						"c1": sqltypes.NewInt64(2),
   611  						"c2": sqltypes.NewInt64(4),
   612  					},
   613  					Query: "",
   614  				},
   615  			},
   616  		},
   617  	}, {
   618  		id: "5",
   619  		source: sqltypes.MakeTestStreamingResults(fields,
   620  			"1|3",
   621  			"---",
   622  			"3|1",
   623  		),
   624  		target: sqltypes.MakeTestStreamingResults(fields,
   625  			"1|3",
   626  			"---",
   627  			"2|4",
   628  			"3|1",
   629  		),
   630  		dr: &DiffReport{
   631  			ProcessedRows:   3,
   632  			MatchingRows:    2,
   633  			ExtraRowsTarget: 1,
   634  			TableName:       "t1",
   635  			ExtraRowsTargetDiffs: []*RowDiff{
   636  				{
   637  					Row: map[string]sqltypes.Value{
   638  						"c1": sqltypes.NewInt64(2),
   639  						"c2": sqltypes.NewInt64(4),
   640  					},
   641  					Query: "",
   642  				},
   643  			},
   644  		},
   645  	}, {
   646  		id: "6",
   647  		source: sqltypes.MakeTestStreamingResults(fields,
   648  			"1|3",
   649  			"---",
   650  			"2|3",
   651  			"3|1",
   652  		),
   653  		target: sqltypes.MakeTestStreamingResults(fields,
   654  			"1|3",
   655  			"---",
   656  			"2|4",
   657  			"3|1",
   658  		),
   659  		dr: &DiffReport{
   660  			ProcessedRows:  3,
   661  			MatchingRows:   2,
   662  			MismatchedRows: 1,
   663  			TableName:      "t1",
   664  			MismatchedRowsSample: []*DiffMismatch{
   665  				{
   666  					Source: &RowDiff{Row: map[string]sqltypes.Value{
   667  						"c1": sqltypes.NewInt64(2),
   668  						"c2": sqltypes.NewInt64(3),
   669  					},
   670  						Query: "",
   671  					},
   672  					Target: &RowDiff{Row: map[string]sqltypes.Value{
   673  						"c1": sqltypes.NewInt64(2),
   674  						"c2": sqltypes.NewInt64(4),
   675  					},
   676  						Query: "",
   677  					},
   678  				},
   679  			},
   680  		},
   681  	}, {
   682  		id:      "7",
   683  		onlyPks: true,
   684  		source: sqltypes.MakeTestStreamingResults(fields,
   685  			"1|3",
   686  			"---",
   687  			"2|3",
   688  			"3|1",
   689  		),
   690  		target: sqltypes.MakeTestStreamingResults(fields,
   691  			"1|3",
   692  			"---",
   693  			"2|4",
   694  			"3|1",
   695  		),
   696  		dr: &DiffReport{
   697  			ProcessedRows:  3,
   698  			MatchingRows:   2,
   699  			MismatchedRows: 1,
   700  			TableName:      "t1",
   701  			MismatchedRowsSample: []*DiffMismatch{
   702  				{
   703  					Source: &RowDiff{Row: map[string]sqltypes.Value{
   704  						"c1": sqltypes.NewInt64(2),
   705  					},
   706  						Query: "",
   707  					},
   708  					Target: &RowDiff{Row: map[string]sqltypes.Value{
   709  						"c1": sqltypes.NewInt64(2),
   710  					},
   711  						Query: "",
   712  					},
   713  				},
   714  			},
   715  		},
   716  	}, {
   717  		id:    "8",
   718  		debug: true,
   719  		source: sqltypes.MakeTestStreamingResults(fields,
   720  			"1|3",
   721  			"---",
   722  			"2|3",
   723  			"3|1",
   724  		),
   725  		target: sqltypes.MakeTestStreamingResults(fields,
   726  			"1|3",
   727  			"---",
   728  			"2|4",
   729  			"3|1",
   730  		),
   731  		dr: &DiffReport{
   732  			ProcessedRows:  3,
   733  			MatchingRows:   2,
   734  			MismatchedRows: 1,
   735  			TableName:      "t1",
   736  			MismatchedRowsSample: []*DiffMismatch{
   737  				{
   738  					Source: &RowDiff{Row: map[string]sqltypes.Value{
   739  						"c1": sqltypes.NewInt64(2),
   740  						"c2": sqltypes.NewInt64(3),
   741  					},
   742  						Query: "select c1, c2 from t1 where c1=2;",
   743  					},
   744  					Target: &RowDiff{Row: map[string]sqltypes.Value{
   745  						"c1": sqltypes.NewInt64(2),
   746  						"c2": sqltypes.NewInt64(4),
   747  					},
   748  						Query: "select c1, c2 from t1 where c1=2;",
   749  					},
   750  				},
   751  			},
   752  		},
   753  	}}
   754  
   755  	for _, tcase := range testcases {
   756  		t.Run(tcase.id, func(t *testing.T) {
   757  			env.tablets[101].setResults("select c1, c2 from t1 order by c1 asc", vdiffSourceGtid, tcase.source)
   758  			env.tablets[201].setResults("select c1, c2 from t1 order by c1 asc", vdiffTargetPrimaryPosition, tcase.target)
   759  
   760  			dr, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 30*time.Second, "", 100, "", tcase.debug, tcase.onlyPks, 100)
   761  			require.NoError(t, err)
   762  			assert.Equal(t, tcase.dr, dr["t1"], tcase.id)
   763  		})
   764  	}
   765  }
   766  
   767  func TestVDiffSharded(t *testing.T) {
   768  	// Also test that highest position ""MariaDB/5-456-892" will be used
   769  	// if lower positions are found.
   770  	env := newTestVDiffEnv(t, []string{"-40", "40-"}, []string{"-80", "80-"}, "", map[string]string{
   771  		"-40-80": "MariaDB/5-456-890",
   772  		"40-80-": "MariaDB/5-456-891",
   773  	})
   774  	defer env.close()
   775  
   776  	schm := &tabletmanagerdatapb.SchemaDefinition{
   777  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   778  			Name:              "t1",
   779  			Columns:           []string{"c1", "c2"},
   780  			PrimaryKeyColumns: []string{"c1"},
   781  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
   782  		},
   783  			{
   784  				Name:              "_t1_gho",
   785  				Columns:           []string{"c1", "c2", "c3"},
   786  				PrimaryKeyColumns: []string{"c2"},
   787  				Fields:            sqltypes.MakeTestFields("c1|c2|c3", "int64|int64|int64"),
   788  			}},
   789  	}
   790  
   791  	env.tmc.schema = schm
   792  
   793  	query := "select c1, c2 from t1 order by c1 asc"
   794  	fields := sqltypes.MakeTestFields(
   795  		"c1|c2",
   796  		"int64|int64",
   797  	)
   798  
   799  	env.tablets[101].setResults(
   800  		query,
   801  		vdiffSourceGtid,
   802  		sqltypes.MakeTestStreamingResults(fields,
   803  			"1|3",
   804  			"2|4",
   805  		),
   806  	)
   807  	env.tablets[111].setResults(
   808  		query,
   809  		vdiffSourceGtid,
   810  		sqltypes.MakeTestStreamingResults(fields,
   811  			"3|4",
   812  		),
   813  	)
   814  	env.tablets[201].setResults(
   815  		query,
   816  		vdiffTargetPrimaryPosition,
   817  		sqltypes.MakeTestStreamingResults(fields,
   818  			"1|3",
   819  		),
   820  	)
   821  	env.tablets[211].setResults(
   822  		query,
   823  		vdiffTargetPrimaryPosition,
   824  		sqltypes.MakeTestStreamingResults(fields,
   825  			"2|4",
   826  			"3|4",
   827  		),
   828  	)
   829  
   830  	dr, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   831  	require.NoError(t, err)
   832  	wantdr := &DiffReport{
   833  		ProcessedRows: 3,
   834  		MatchingRows:  3,
   835  		TableName:     "t1",
   836  	}
   837  	assert.Equal(t, wantdr, dr["t1"])
   838  }
   839  
   840  func TestVDiffAggregates(t *testing.T) {
   841  	env := newTestVDiffEnv(t, []string{"-40", "40-"}, []string{"-80", "80-"}, "select c1, count(*) c2, sum(c3) c3 from t group by c1", nil)
   842  	defer env.close()
   843  
   844  	schm := &tabletmanagerdatapb.SchemaDefinition{
   845  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   846  			Name:              "t1",
   847  			Columns:           []string{"c1", "c2", "c3"},
   848  			PrimaryKeyColumns: []string{"c1"},
   849  			Fields:            sqltypes.MakeTestFields("c1|c2|c3", "int64|int64|int64"),
   850  		}},
   851  	}
   852  	env.tmc.schema = schm
   853  
   854  	sourceQuery := "select c1, count(*) as c2, sum(c3) as c3 from t group by c1 order by c1 asc"
   855  	fields := sqltypes.MakeTestFields(
   856  		"c1|c2|c3",
   857  		"int64|int64|int64",
   858  	)
   859  
   860  	env.tablets[101].setResults(
   861  		sourceQuery,
   862  		vdiffSourceGtid,
   863  		sqltypes.MakeTestStreamingResults(fields,
   864  			"1|3|4",
   865  			"2|4|5",
   866  			"4|5|6",
   867  		),
   868  	)
   869  	env.tablets[111].setResults(
   870  		sourceQuery,
   871  		vdiffSourceGtid,
   872  		sqltypes.MakeTestStreamingResults(fields,
   873  			"1|1|1",
   874  			"3|2|2",
   875  			"5|3|3",
   876  		),
   877  	)
   878  	targetQuery := "select c1, c2, c3 from t1 order by c1 asc"
   879  	env.tablets[201].setResults(
   880  		targetQuery,
   881  		vdiffTargetPrimaryPosition,
   882  		sqltypes.MakeTestStreamingResults(fields,
   883  			"1|4|5",
   884  			"5|3|3",
   885  		),
   886  	)
   887  	env.tablets[211].setResults(
   888  		targetQuery,
   889  		vdiffTargetPrimaryPosition,
   890  		sqltypes.MakeTestStreamingResults(fields,
   891  			"2|4|5",
   892  			"3|2|2",
   893  			"4|5|6",
   894  		),
   895  	)
   896  
   897  	dr, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   898  	require.NoError(t, err)
   899  	wantdr := &DiffReport{
   900  		ProcessedRows: 5,
   901  		MatchingRows:  5,
   902  		TableName:     "t1",
   903  	}
   904  	assert.Equal(t, wantdr, dr["t1"])
   905  }
   906  
   907  func TestVDiffDefaults(t *testing.T) {
   908  	env := newTestVDiffEnv(t, []string{"0"}, []string{"0"}, "", nil)
   909  	defer env.close()
   910  
   911  	schm := &tabletmanagerdatapb.SchemaDefinition{
   912  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   913  			Name:              "t1",
   914  			Columns:           []string{"c1", "c2"},
   915  			PrimaryKeyColumns: []string{"c1"},
   916  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
   917  		}},
   918  	}
   919  	env.tmc.schema = schm
   920  
   921  	fields := sqltypes.MakeTestFields(
   922  		"c1|c2",
   923  		"int64|int64",
   924  	)
   925  
   926  	source := sqltypes.MakeTestStreamingResults(fields,
   927  		"1|3",
   928  		"2|4",
   929  		"---",
   930  		"3|1",
   931  	)
   932  	target := source
   933  	env.tablets[101].setResults("select c1, c2 from t1 order by c1 asc", vdiffSourceGtid, source)
   934  	env.tablets[201].setResults("select c1, c2 from t1 order by c1 asc", vdiffTargetPrimaryPosition, target)
   935  
   936  	_, err := env.wr.VDiff(context.Background(), "target", env.workflow, "", "", "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   937  	require.NoError(t, err)
   938  	_, err = env.wr.VDiff(context.Background(), "target", env.workflow, "", env.cell, "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   939  	require.NoError(t, err)
   940  
   941  	var df map[string]*DiffReport
   942  	df, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   943  	require.NoError(t, err)
   944  	require.Equal(t, df["t1"].ProcessedRows, 3)
   945  	df, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 30*time.Second, "", 1, "", false /*debug*/, false /*onlyPks*/, 100)
   946  	require.NoError(t, err)
   947  	require.Equal(t, df["t1"].ProcessedRows, 1)
   948  	df, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 30*time.Second, "", 0, "", false /*debug*/, false /*onlyPks*/, 100)
   949  	require.NoError(t, err)
   950  	require.Equal(t, df["t1"].ProcessedRows, 0)
   951  
   952  	_, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 1*time.Nanosecond, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   953  	require.Error(t, err)
   954  	err = topo.CheckKeyspaceLocked(context.Background(), "target")
   955  	require.EqualErrorf(t, err, "keyspace target is not locked (no locksInfo)", "")
   956  	err = topo.CheckKeyspaceLocked(context.Background(), "source")
   957  	require.EqualErrorf(t, err, "keyspace source is not locked (no locksInfo)", "")
   958  }
   959  
   960  func TestVDiffReplicationWait(t *testing.T) {
   961  	env := newTestVDiffEnv(t, []string{"0"}, []string{"0"}, "", nil)
   962  	defer env.close()
   963  
   964  	schm := &tabletmanagerdatapb.SchemaDefinition{
   965  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   966  			Name:              "t1",
   967  			Columns:           []string{"c1", "c2"},
   968  			PrimaryKeyColumns: []string{"c1"},
   969  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
   970  		}},
   971  	}
   972  	env.tmc.schema = schm
   973  
   974  	fields := sqltypes.MakeTestFields(
   975  		"c1|c2",
   976  		"int64|int64",
   977  	)
   978  
   979  	source := sqltypes.MakeTestStreamingResults(fields,
   980  		"1|3",
   981  		"2|4",
   982  		"---",
   983  		"3|1",
   984  	)
   985  	target := source
   986  	env.tablets[101].setResults("select c1, c2 from t1 order by c1 asc", vdiffSourceGtid, source)
   987  	env.tablets[201].setResults("select c1, c2 from t1 order by c1 asc", vdiffTargetPrimaryPosition, target)
   988  
   989  	_, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 0*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100)
   990  	require.Error(t, err)
   991  	require.True(t, strings.Contains(err.Error(), "context deadline exceeded"))
   992  }
   993  
   994  func TestVDiffFindPKs(t *testing.T) {
   995  
   996  	testcases := []struct {
   997  		name         string
   998  		table        *tabletmanagerdatapb.TableDefinition
   999  		targetSelect *sqlparser.Select
  1000  		tdIn         *tableDiffer
  1001  		tdOut        *tableDiffer
  1002  		errorString  string
  1003  	}{
  1004  		{
  1005  			name: "",
  1006  			table: &tabletmanagerdatapb.TableDefinition{
  1007  				Name:              "t1",
  1008  				Columns:           []string{"c1", "c2"},
  1009  				PrimaryKeyColumns: []string{"c1"},
  1010  				Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
  1011  			},
  1012  			targetSelect: &sqlparser.Select{
  1013  				SelectExprs: sqlparser.SelectExprs{
  1014  					&sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c1")}},
  1015  					&sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c2")}},
  1016  				},
  1017  			},
  1018  			tdIn: &tableDiffer{
  1019  				compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), false}},
  1020  				comparePKs:  []compareColInfo{},
  1021  				pkCols:      []int{},
  1022  			},
  1023  			tdOut: &tableDiffer{
  1024  				compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}},
  1025  				comparePKs:  []compareColInfo{{0, collations.Collation(nil), true}},
  1026  				pkCols:      []int{0},
  1027  				selectPks:   []int{0},
  1028  			},
  1029  		}, {
  1030  			name: "",
  1031  			table: &tabletmanagerdatapb.TableDefinition{
  1032  				Name:              "t1",
  1033  				Columns:           []string{"c1", "c2", "c3", "c4"},
  1034  				PrimaryKeyColumns: []string{"c1", "c4"},
  1035  				Fields:            sqltypes.MakeTestFields("c1|c2|c3|c4", "int64|int64|varchar|int64"),
  1036  			},
  1037  			targetSelect: &sqlparser.Select{
  1038  				SelectExprs: sqlparser.SelectExprs{
  1039  					&sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c1")}},
  1040  					&sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c2")}},
  1041  					&sqlparser.AliasedExpr{Expr: &sqlparser.FuncExpr{Name: sqlparser.NewIdentifierCI("c3")}},
  1042  					&sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c4")}},
  1043  				},
  1044  			},
  1045  			tdIn: &tableDiffer{
  1046  				compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), false}, {2, collations.Collation(nil), false}, {3, collations.Collation(nil), false}},
  1047  				comparePKs:  []compareColInfo{},
  1048  				pkCols:      []int{},
  1049  			},
  1050  			tdOut: &tableDiffer{
  1051  				compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}, {2, collations.Collation(nil), false}, {3, collations.Collation(nil), true}},
  1052  				comparePKs:  []compareColInfo{{0, collations.Collation(nil), true}, {3, collations.Collation(nil), true}},
  1053  				pkCols:      []int{0, 3},
  1054  				selectPks:   []int{0, 3},
  1055  			},
  1056  		},
  1057  	}
  1058  
  1059  	for _, tc := range testcases {
  1060  		t.Run(tc.name, func(t *testing.T) {
  1061  			_, err := findPKs(tc.table, tc.targetSelect, tc.tdIn)
  1062  			require.NoError(t, err)
  1063  			require.EqualValues(t, tc.tdOut, tc.tdIn)
  1064  		})
  1065  	}
  1066  
  1067  }
  1068  
  1069  func TestVDiffPlanInclude(t *testing.T) {
  1070  	schm := &tabletmanagerdatapb.SchemaDefinition{
  1071  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
  1072  			Name:              "t1",
  1073  			Columns:           []string{"c1", "c2"},
  1074  			PrimaryKeyColumns: []string{"c1"},
  1075  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
  1076  		}, {
  1077  			Name:              "t2",
  1078  			Columns:           []string{"c1", "c2"},
  1079  			PrimaryKeyColumns: []string{"c1"},
  1080  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
  1081  		}, {
  1082  			Name:              "t3",
  1083  			Columns:           []string{"c1", "c2"},
  1084  			PrimaryKeyColumns: []string{"c1"},
  1085  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
  1086  		}, {
  1087  			Name:              "t4",
  1088  			Columns:           []string{"c1", "c2"},
  1089  			PrimaryKeyColumns: []string{"c1"},
  1090  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
  1091  		}},
  1092  	}
  1093  
  1094  	df := &vdiff{}
  1095  	rule := &binlogdatapb.Rule{
  1096  		Match: "/.*",
  1097  	}
  1098  	filter := &binlogdatapb.Filter{Rules: []*binlogdatapb.Rule{rule}}
  1099  	var err error
  1100  	err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t2"})
  1101  	require.NoError(t, err)
  1102  	require.Equal(t, 1, len(df.differs))
  1103  	err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t2", "t3"})
  1104  	require.NoError(t, err)
  1105  	require.Equal(t, 2, len(df.differs))
  1106  	err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t1", "t2", "t3"})
  1107  	require.NoError(t, err)
  1108  	require.Equal(t, 3, len(df.differs))
  1109  	err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t1", "t2", "t3", "t4"})
  1110  	require.NoError(t, err)
  1111  	require.Equal(t, 4, len(df.differs))
  1112  	err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t1", "t2", "t3", "t5"})
  1113  	require.Error(t, err)
  1114  }