vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/update_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 engine
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"testing"
    23  
    24  	"vitess.io/vitess/go/mysql/collations"
    25  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    26  
    27  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    28  
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"vitess.io/vitess/go/sqltypes"
    32  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    33  
    34  	querypb "vitess.io/vitess/go/vt/proto/query"
    35  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    36  )
    37  
    38  func TestUpdateUnsharded(t *testing.T) {
    39  	upd := &Update{
    40  		DML: &DML{
    41  			RoutingParameters: &RoutingParameters{
    42  				Opcode: Unsharded,
    43  				Keyspace: &vindexes.Keyspace{
    44  					Name:    "ks",
    45  					Sharded: false,
    46  				},
    47  			},
    48  
    49  			Query: "dummy_update",
    50  		},
    51  	}
    52  
    53  	vc := newDMLTestVCursor("0")
    54  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
    55  	require.NoError(t, err)
    56  	vc.ExpectLog(t, []string{
    57  		`ResolveDestinations ks [] Destinations:DestinationAllShards()`,
    58  		`ExecuteMultiShard ks.0: dummy_update {} true true`,
    59  	})
    60  
    61  	// Failure cases
    62  	vc = &loggingVCursor{shardErr: errors.New("shard_error")}
    63  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
    64  	require.EqualError(t, err, `shard_error`)
    65  
    66  	vc = &loggingVCursor{}
    67  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
    68  	require.EqualError(t, err, `Keyspace 'ks' does not have exactly one shard: []`)
    69  }
    70  
    71  func TestUpdateEqual(t *testing.T) {
    72  	vindex, _ := vindexes.NewHash("", nil)
    73  	upd := &Update{
    74  		DML: &DML{
    75  			RoutingParameters: &RoutingParameters{
    76  				Opcode: Equal,
    77  				Keyspace: &vindexes.Keyspace{
    78  					Name:    "ks",
    79  					Sharded: true,
    80  				},
    81  				Vindex: vindex,
    82  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1)},
    83  			},
    84  			Query: "dummy_update",
    85  		},
    86  	}
    87  
    88  	vc := newDMLTestVCursor("-20", "20-")
    89  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
    90  	require.NoError(t, err)
    91  	vc.ExpectLog(t, []string{
    92  		`ResolveDestinations ks [type:INT64 value:"1"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6)`,
    93  		`ExecuteMultiShard ks.-20: dummy_update {} true true`,
    94  	})
    95  
    96  	// Failure case
    97  	upd.Values = []evalengine.Expr{evalengine.NewBindVar("aa", collations.TypedCollation{})}
    98  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
    99  	require.EqualError(t, err, `query arguments missing for aa`)
   100  }
   101  
   102  func TestUpdateEqualMultiCol(t *testing.T) {
   103  	vindex, _ := vindexes.NewRegionExperimental("", map[string]string{"region_bytes": "1"})
   104  	upd := &Update{
   105  		DML: &DML{
   106  			RoutingParameters: &RoutingParameters{
   107  				Opcode: Equal,
   108  				Keyspace: &vindexes.Keyspace{
   109  					Name:    "ks",
   110  					Sharded: true,
   111  				},
   112  				Vindex: vindex,
   113  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1), evalengine.NewLiteralInt(2)},
   114  			},
   115  			Query: "dummy_update",
   116  		},
   117  	}
   118  
   119  	vc := newDMLTestVCursor("-20", "20-")
   120  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   121  	require.NoError(t, err)
   122  	vc.ExpectLog(t, []string{
   123  		`ResolveDestinationsMultiCol ks [[INT64(1) INT64(2)]] Destinations:DestinationKeyspaceID(0106e7ea22ce92708f)`,
   124  		`ExecuteMultiShard ks.-20: dummy_update {} true true`,
   125  	})
   126  }
   127  
   128  func TestUpdateScatter(t *testing.T) {
   129  	vindex, _ := vindexes.NewHash("", nil)
   130  	upd := &Update{
   131  		DML: &DML{
   132  			RoutingParameters: &RoutingParameters{
   133  				Opcode: Scatter,
   134  				Keyspace: &vindexes.Keyspace{
   135  					Name:    "ks",
   136  					Sharded: true,
   137  				},
   138  				Vindex: vindex,
   139  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1)},
   140  			},
   141  			Query: "dummy_update",
   142  		},
   143  	}
   144  
   145  	vc := newDMLTestVCursor("-20", "20-")
   146  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   147  	require.NoError(t, err)
   148  
   149  	vc.ExpectLog(t, []string{
   150  		`ResolveDestinations ks [] Destinations:DestinationAllShards()`,
   151  		`ExecuteMultiShard ks.-20: dummy_update {} ks.20-: dummy_update {} true false`,
   152  	})
   153  
   154  	// works with multishard autocommit
   155  	upd = &Update{
   156  		DML: &DML{
   157  			RoutingParameters: &RoutingParameters{
   158  				Opcode: Scatter,
   159  				Keyspace: &vindexes.Keyspace{
   160  					Name:    "ks",
   161  					Sharded: true,
   162  				},
   163  				Vindex: vindex,
   164  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1)},
   165  			},
   166  			Query:                "dummy_update",
   167  			MultiShardAutocommit: true,
   168  		},
   169  	}
   170  
   171  	vc = newDMLTestVCursor("-20", "20-")
   172  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   173  	require.NoError(t, err)
   174  
   175  	vc.ExpectLog(t, []string{
   176  		`ResolveDestinations ks [] Destinations:DestinationAllShards()`,
   177  		`ExecuteMultiShard ks.-20: dummy_update {} ks.20-: dummy_update {} true true`,
   178  	})
   179  }
   180  
   181  func TestUpdateEqualNoRoute(t *testing.T) {
   182  	vindex, _ := vindexes.NewLookupUnique("", map[string]string{
   183  		"table": "lkp",
   184  		"from":  "from",
   185  		"to":    "toc",
   186  	})
   187  	upd := &Update{
   188  		DML: &DML{
   189  			RoutingParameters: &RoutingParameters{
   190  				Opcode: Equal,
   191  				Keyspace: &vindexes.Keyspace{
   192  					Name:    "ks",
   193  					Sharded: true,
   194  				},
   195  				Vindex: vindex,
   196  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1)},
   197  			},
   198  			Query: "dummy_update",
   199  		},
   200  	}
   201  
   202  	vc := newDMLTestVCursor("0")
   203  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   204  	require.NoError(t, err)
   205  	vc.ExpectLog(t, []string{
   206  		// This lookup query will return no rows. So, the DML will not be sent anywhere.
   207  		`Execute select from, toc from lkp where from in ::from from: type:TUPLE values:{type:INT64 value:"1"} false`,
   208  		`ResolveDestinations ks [type:INT64 value:"1"] Destinations:DestinationNone()`,
   209  	})
   210  }
   211  
   212  func TestUpdateEqualNoScatter(t *testing.T) {
   213  	t.Skip("planner does not produces this plan anymore")
   214  	vindex, _ := vindexes.NewLookupUnique("", map[string]string{
   215  		"table":      "lkp",
   216  		"from":       "from",
   217  		"to":         "toc",
   218  		"write_only": "true",
   219  	})
   220  	upd := &Update{
   221  		DML: &DML{
   222  			RoutingParameters: &RoutingParameters{
   223  				Opcode: Equal,
   224  				Keyspace: &vindexes.Keyspace{
   225  					Name:    "ks",
   226  					Sharded: true,
   227  				},
   228  				Vindex: vindex,
   229  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1)},
   230  			},
   231  			Query: "dummy_update",
   232  		},
   233  	}
   234  
   235  	vc := newDMLTestVCursor("0")
   236  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   237  	require.EqualError(t, err, `cannot map vindex to unique keyspace id: DestinationKeyRange(-)`)
   238  }
   239  
   240  func TestUpdateEqualChangedVindex(t *testing.T) {
   241  	ks := buildTestVSchema().Keyspaces["sharded"]
   242  	upd := &Update{
   243  		DML: &DML{
   244  			RoutingParameters: &RoutingParameters{
   245  				Opcode:   Equal,
   246  				Keyspace: ks.Keyspace,
   247  				Vindex:   ks.Vindexes["hash"],
   248  				Values:   []evalengine.Expr{evalengine.NewLiteralInt(1)},
   249  			},
   250  			Query: "dummy_update",
   251  			Table: []*vindexes.Table{
   252  				ks.Tables["t1"],
   253  			},
   254  			OwnedVindexQuery: "dummy_subquery",
   255  			KsidVindex:       ks.Vindexes["hash"],
   256  			KsidLength:       1,
   257  		},
   258  		ChangedVindexValues: map[string]*VindexValues{
   259  			"twocol": {
   260  				PvMap: map[string]evalengine.Expr{
   261  					"c1": evalengine.NewLiteralInt(1),
   262  					"c2": evalengine.NewLiteralInt(2),
   263  				},
   264  				Offset: 4,
   265  			},
   266  			"onecol": {
   267  				PvMap: map[string]evalengine.Expr{
   268  					"c3": evalengine.NewLiteralInt(3),
   269  				},
   270  				Offset: 5,
   271  			},
   272  		},
   273  	}
   274  
   275  	results := []*sqltypes.Result{sqltypes.MakeTestResult(
   276  		sqltypes.MakeTestFields(
   277  			"id|c1|c2|c3|twocol|onecol",
   278  			"int64|int64|int64|int64|int64|int64",
   279  		),
   280  		"1|4|5|6|0|0",
   281  	)}
   282  	vc := newDMLTestVCursor("-20", "20-")
   283  	vc.results = results
   284  
   285  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   286  	require.NoError(t, err)
   287  	vc.ExpectLog(t, []string{
   288  		`ResolveDestinations sharded [type:INT64 value:"1"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6)`,
   289  		// ResolveDestinations is hard-coded to return -20.
   290  		// It gets used to perform the subquery to fetch the changing column values.
   291  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   292  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   293  		// 4,5 have to be replaced by 1,2 (the new values).
   294  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   295  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   296  		// 6 has to be replaced by 3.
   297  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   298  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   299  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   300  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   301  	})
   302  
   303  	// No rows changing
   304  	vc = newDMLTestVCursor("-20", "20-")
   305  
   306  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   307  	require.NoError(t, err)
   308  	vc.ExpectLog(t, []string{
   309  		`ResolveDestinations sharded [type:INT64 value:"1"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6)`,
   310  		// ResolveDestinations is hard-coded to return -20.
   311  		// It gets used to perform the subquery to fetch the changing column values.
   312  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   313  		// Subquery returns no rows. So, no vindexes are updated. We still pass-through the original update.
   314  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   315  	})
   316  
   317  	// multiple rows changing.
   318  	results = []*sqltypes.Result{sqltypes.MakeTestResult(
   319  		sqltypes.MakeTestFields(
   320  			"id|c1|c2|c3|twocol|onecol",
   321  			"int64|int64|int64|int64|int64|int64",
   322  		),
   323  		"1|4|5|6|0|0",
   324  		"1|7|8|9|0|0",
   325  	)}
   326  	vc = newDMLTestVCursor("-20", "20-")
   327  	vc.results = results
   328  
   329  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   330  	require.NoError(t, err)
   331  	vc.ExpectLog(t, []string{
   332  		`ResolveDestinations sharded [type:INT64 value:"1"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6)`,
   333  		// ResolveDestinations is hard-coded to return -20.
   334  		// It gets used to perform the subquery to fetch the changing column values.
   335  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   336  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   337  		// 4,5 have to be replaced by 1,2 (the new values).
   338  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   339  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   340  		// 6 has to be replaced by 3.
   341  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   342  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   343  		// 7,8 have to be replaced by 1,2 (the new values).
   344  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"7" from2: type:INT64 value:"8" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   345  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   346  		// 9 has to be replaced by 3.
   347  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"9" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   348  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   349  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   350  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   351  	})
   352  
   353  	// multiple rows changing, but only some vindex actually changes
   354  	results = []*sqltypes.Result{sqltypes.MakeTestResult(
   355  		sqltypes.MakeTestFields(
   356  			"id|c1|c2|c3|twocol|onecol",
   357  			"int64|int64|int64|int64|int64|int64",
   358  		),
   359  		"1|4|5|6|0|1", // twocol changes
   360  		"1|7|8|9|1|0", // onecol changes
   361  	)}
   362  	vc = newDMLTestVCursor("-20", "20-")
   363  	vc.results = results
   364  
   365  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   366  	require.NoError(t, err)
   367  	vc.ExpectLog(t, []string{
   368  		`ResolveDestinations sharded [type:INT64 value:"1"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6)`,
   369  		// ResolveDestinations is hard-coded to return -20.
   370  		// It gets used to perform the subquery to fetch the changing column values.
   371  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   372  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   373  		// 4,5 have to be replaced by 1,2 (the new values).
   374  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   375  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   376  		// 9 has to be replaced by 3.
   377  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"9" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   378  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   379  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   380  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   381  	})
   382  
   383  }
   384  
   385  func TestUpdateEqualMultiColChangedVindex(t *testing.T) {
   386  	ks := buildTestVSchema().Keyspaces["sharded"]
   387  	upd := &Update{
   388  		DML: &DML{
   389  			RoutingParameters: &RoutingParameters{
   390  				Opcode:   Equal,
   391  				Keyspace: ks.Keyspace,
   392  				Vindex:   ks.Vindexes["rg_vdx"],
   393  				Values:   []evalengine.Expr{evalengine.NewLiteralInt(1), evalengine.NewLiteralInt(2)},
   394  			},
   395  			Query: "dummy_update",
   396  			Table: []*vindexes.Table{
   397  				ks.Tables["rg_tbl"],
   398  			},
   399  			OwnedVindexQuery: "dummy_subquery",
   400  			KsidVindex:       ks.Vindexes["rg_vdx"],
   401  			KsidLength:       2,
   402  		},
   403  		ChangedVindexValues: map[string]*VindexValues{
   404  			"lkp_rg": {
   405  				PvMap: map[string]evalengine.Expr{
   406  					"colc": evalengine.NewLiteralInt(5),
   407  				},
   408  				Offset: 3,
   409  			},
   410  		},
   411  	}
   412  
   413  	results := []*sqltypes.Result{sqltypes.MakeTestResult(
   414  		sqltypes.MakeTestFields(
   415  			"cola|colb|colc|colc=5",
   416  			"int64|int64|int64|int64",
   417  		),
   418  		"1|2|4|0",
   419  	)}
   420  	vc := newDMLTestVCursor("-20", "20-")
   421  	vc.results = results
   422  
   423  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   424  	require.NoError(t, err)
   425  	vc.ExpectLog(t, []string{
   426  		`ResolveDestinationsMultiCol sharded [[INT64(1) INT64(2)]] Destinations:DestinationKeyspaceID(0106e7ea22ce92708f)`,
   427  		// ResolveDestinations is hard-coded to return -20.
   428  		// It gets used to perform the subquery to fetch the changing column values.
   429  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   430  		// 4 has to be replaced by 5.
   431  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"4" toc: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   432  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   433  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   434  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   435  	})
   436  
   437  	// No rows changing
   438  	vc.Rewind()
   439  	vc.results = nil
   440  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   441  	require.NoError(t, err)
   442  	vc.ExpectLog(t, []string{
   443  		`ResolveDestinationsMultiCol sharded [[INT64(1) INT64(2)]] Destinations:DestinationKeyspaceID(0106e7ea22ce92708f)`,
   444  		// ResolveDestinations is hard-coded to return -20.
   445  		// It gets used to perform the subquery to fetch the changing column values.
   446  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   447  		// Subquery returns no rows. So, no vindexes are updated. We still pass-through the original update.
   448  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   449  	})
   450  
   451  	// multiple rows changing.
   452  	results = []*sqltypes.Result{sqltypes.MakeTestResult(
   453  		sqltypes.MakeTestFields(
   454  			"cola|colb|colc|colc=5",
   455  			"int64|int64|int64|int64",
   456  		),
   457  		"1|2|4|0",
   458  		"1|2|6|0",
   459  	)}
   460  	vc.Rewind()
   461  	vc.results = results
   462  
   463  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   464  	require.NoError(t, err)
   465  	vc.ExpectLog(t, []string{
   466  		`ResolveDestinationsMultiCol sharded [[INT64(1) INT64(2)]] Destinations:DestinationKeyspaceID(0106e7ea22ce92708f)`,
   467  		// ResolveDestinations is hard-coded to return -20.
   468  		// It gets used to perform the subquery to fetch the changing column values.
   469  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   470  		// 4 has to be replaced by 5.
   471  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"4" toc: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   472  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   473  		// 6 has to be replaced by 5.
   474  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   475  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   476  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   477  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   478  	})
   479  
   480  	// multiple rows changing, but only some rows actually changes
   481  	results = []*sqltypes.Result{sqltypes.MakeTestResult(
   482  		sqltypes.MakeTestFields(
   483  			"cola|colb|colc|colc=5",
   484  			"int64|int64|int64|int64",
   485  		),
   486  		"1|2|5|1",
   487  		"1|2|7|0",
   488  	)}
   489  	vc.Rewind()
   490  	vc.results = results
   491  
   492  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   493  	require.NoError(t, err)
   494  	vc.ExpectLog(t, []string{
   495  		`ResolveDestinationsMultiCol sharded [[INT64(1) INT64(2)]] Destinations:DestinationKeyspaceID(0106e7ea22ce92708f)`,
   496  		// ResolveDestinations is hard-coded to return -20.
   497  		// It gets used to perform the subquery to fetch the changing column values.
   498  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   499  		// 7 has to be replaced by 5.
   500  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"7" toc: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   501  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x01\x06\xe7\xea\"Βp\x8f" true`,
   502  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   503  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   504  	})
   505  }
   506  
   507  func TestUpdateScatterChangedVindex(t *testing.T) {
   508  	// update t1 set c1 = 1, c2 = 2, c3 = 3
   509  	ks := buildTestVSchema().Keyspaces["sharded"]
   510  	upd := &Update{
   511  		DML: &DML{
   512  			RoutingParameters: &RoutingParameters{
   513  				Opcode:   Scatter,
   514  				Keyspace: ks.Keyspace,
   515  			},
   516  			Query: "dummy_update",
   517  			Table: []*vindexes.Table{
   518  				ks.Tables["t1"],
   519  			},
   520  			OwnedVindexQuery: "dummy_subquery",
   521  			KsidVindex:       ks.Vindexes["hash"],
   522  			KsidLength:       1,
   523  		},
   524  		ChangedVindexValues: map[string]*VindexValues{
   525  			"twocol": {
   526  				PvMap: map[string]evalengine.Expr{
   527  					"c1": evalengine.NewLiteralInt(1),
   528  					"c2": evalengine.NewLiteralInt(2),
   529  				},
   530  				Offset: 4,
   531  			},
   532  			"onecol": {
   533  				PvMap: map[string]evalengine.Expr{
   534  					"c3": evalengine.NewLiteralInt(3),
   535  				},
   536  				Offset: 5,
   537  			},
   538  		},
   539  	}
   540  
   541  	results := []*sqltypes.Result{sqltypes.MakeTestResult(
   542  		sqltypes.MakeTestFields(
   543  			"id|c1|c2|c3|twocol|onecol",
   544  			"int64|int64|int64|int64|int64|int64",
   545  		),
   546  		"1|4|5|6|0|0",
   547  	)}
   548  	vc := newDMLTestVCursor("-20", "20-")
   549  	vc.results = results
   550  
   551  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   552  	require.NoError(t, err)
   553  	vc.ExpectLog(t, []string{
   554  		`ResolveDestinations sharded [] Destinations:DestinationAllShards()`,
   555  		`ExecuteMultiShard sharded.-20: dummy_subquery {} sharded.20-: dummy_subquery {} false false`,
   556  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   557  		// 4,5 have to be replaced by 1,2 (the new values).
   558  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   559  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   560  		// 6 has to be replaced by 3.
   561  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   562  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   563  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   564  		`ExecuteMultiShard sharded.-20: dummy_update {} sharded.20-: dummy_update {} true false`,
   565  	})
   566  
   567  	// No rows changing
   568  	vc = newDMLTestVCursor("-20", "20-")
   569  
   570  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   571  	if err != nil {
   572  		t.Fatal(err)
   573  	}
   574  	vc.ExpectLog(t, []string{
   575  		`ResolveDestinations sharded [] Destinations:DestinationAllShards()`,
   576  		// ResolveDestinations is hard-coded to return -20.
   577  		// It gets used to perform the subquery to fetch the changing column values.
   578  		`ExecuteMultiShard sharded.-20: dummy_subquery {} sharded.20-: dummy_subquery {} false false`,
   579  		// Subquery returns no rows. So, no vindexes are deleted. We still pass-through the original delete.
   580  		`ExecuteMultiShard sharded.-20: dummy_update {} sharded.20-: dummy_update {} true false`,
   581  	})
   582  
   583  	// Update can affect multiple rows
   584  	results = []*sqltypes.Result{sqltypes.MakeTestResult(
   585  		sqltypes.MakeTestFields(
   586  			"id|c1|c2|c3|twocol|onecol",
   587  			"int64|int64|int64|int64|int64|int64",
   588  		),
   589  		"1|4|5|6|0|0",
   590  		"1|7|8|9|0|0",
   591  	)}
   592  	vc = newDMLTestVCursor("-20", "20-")
   593  	vc.results = results
   594  
   595  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   596  	require.NoError(t, err)
   597  	vc.ExpectLog(t, []string{
   598  		`ResolveDestinations sharded [] Destinations:DestinationAllShards()`,
   599  		// ResolveDestinations is hard-coded to return -20.
   600  		// It gets used to perform the subquery to fetch the changing column values.
   601  		`ExecuteMultiShard sharded.-20: dummy_subquery {} sharded.20-: dummy_subquery {} false false`,
   602  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   603  		// 4,5 have to be replaced by 1,2 (the new values).
   604  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   605  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   606  		// 6 has to be replaced by 3.
   607  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   608  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   609  		// Those values are returned as 7,8 for twocol and 9 for onecol.
   610  		// 7,8 have to be replaced by 1,2 (the new values).
   611  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"7" from2: type:INT64 value:"8" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   612  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   613  		// 9 has to be replaced by 3.
   614  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"9" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   615  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   616  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   617  		`ExecuteMultiShard sharded.-20: dummy_update {} sharded.20-: dummy_update {} true false`,
   618  	})
   619  
   620  }
   621  
   622  func TestUpdateIn(t *testing.T) {
   623  	ks := buildTestVSchema().Keyspaces["sharded"]
   624  	upd := &Update{
   625  		DML: &DML{
   626  			RoutingParameters: &RoutingParameters{
   627  				Opcode:   IN,
   628  				Keyspace: ks.Keyspace,
   629  				Vindex:   ks.Vindexes["hash"],
   630  				Values: []evalengine.Expr{evalengine.TupleExpr{
   631  					evalengine.NewLiteralInt(1),
   632  					evalengine.NewLiteralInt(2),
   633  				}}},
   634  			Query: "dummy_update",
   635  		},
   636  	}
   637  
   638  	vc := newDMLTestVCursor("-20", "20-")
   639  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   640  	require.NoError(t, err)
   641  	vc.ExpectLog(t, []string{
   642  		`ResolveDestinations sharded [type:INT64 value:"1" type:INT64 value:"2"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6),DestinationKeyspaceID(06e7ea22ce92708f)`,
   643  		// ResolveDestinations is hard-coded to return -20.
   644  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   645  	})
   646  }
   647  
   648  func TestUpdateInStreamExecute(t *testing.T) {
   649  	ks := buildTestVSchema().Keyspaces["sharded"]
   650  	upd := &Update{DML: &DML{
   651  		RoutingParameters: &RoutingParameters{
   652  			Opcode:   IN,
   653  			Keyspace: ks.Keyspace,
   654  			Vindex:   ks.Vindexes["hash"],
   655  			Values: []evalengine.Expr{evalengine.TupleExpr{
   656  				evalengine.NewLiteralInt(1),
   657  				evalengine.NewLiteralInt(2),
   658  			}}},
   659  		Query: "dummy_update",
   660  	}}
   661  
   662  	vc := newDMLTestVCursor("-20", "20-")
   663  	err := upd.TryStreamExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false, func(result *sqltypes.Result) error {
   664  		return nil
   665  	})
   666  	require.NoError(t, err)
   667  	vc.ExpectLog(t, []string{
   668  		`ResolveDestinations sharded [type:INT64 value:"1" type:INT64 value:"2"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6),DestinationKeyspaceID(06e7ea22ce92708f)`,
   669  		// ResolveDestinations is hard-coded to return -20.
   670  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   671  	})
   672  }
   673  
   674  func TestUpdateInMultiCol(t *testing.T) {
   675  	ks := buildTestVSchema().Keyspaces["sharded"]
   676  	upd := &Update{DML: &DML{
   677  		RoutingParameters: &RoutingParameters{
   678  			Opcode:   IN,
   679  			Keyspace: ks.Keyspace,
   680  			Vindex:   ks.Vindexes["rg_vdx"],
   681  			Values: []evalengine.Expr{
   682  				evalengine.TupleExpr{evalengine.NewLiteralInt(1), evalengine.NewLiteralInt(2)},
   683  				evalengine.TupleExpr{evalengine.NewLiteralInt(3), evalengine.NewLiteralInt(4)},
   684  			},
   685  		},
   686  		Query: "dummy_update",
   687  	}}
   688  
   689  	vc := newDMLTestVCursor("-20", "20-")
   690  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   691  	require.NoError(t, err)
   692  	vc.ExpectLog(t, []string{
   693  		`ResolveDestinationsMultiCol sharded [[INT64(1) INT64(3)] [INT64(1) INT64(4)] [INT64(2) INT64(3)] [INT64(2) INT64(4)]] Destinations:DestinationKeyspaceID(014eb190c9a2fa169c),DestinationKeyspaceID(01d2fd8867d50d2dfe),DestinationKeyspaceID(024eb190c9a2fa169c),DestinationKeyspaceID(02d2fd8867d50d2dfe)`,
   694  		// ResolveDestinations is hard-coded to return -20.
   695  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   696  	})
   697  }
   698  
   699  func TestUpdateInChangedVindex(t *testing.T) {
   700  	ks := buildTestVSchema().Keyspaces["sharded"]
   701  	upd := &Update{
   702  		DML: &DML{
   703  			RoutingParameters: &RoutingParameters{
   704  				Opcode:   IN,
   705  				Keyspace: ks.Keyspace,
   706  				Vindex:   ks.Vindexes["hash"],
   707  				Values: []evalengine.Expr{evalengine.TupleExpr{
   708  					evalengine.NewLiteralInt(1),
   709  					evalengine.NewLiteralInt(2),
   710  				}},
   711  			},
   712  			Query: "dummy_update",
   713  			Table: []*vindexes.Table{
   714  				ks.Tables["t1"],
   715  			},
   716  			OwnedVindexQuery: "dummy_subquery",
   717  			KsidVindex:       ks.Vindexes["hash"],
   718  			KsidLength:       1,
   719  		},
   720  		ChangedVindexValues: map[string]*VindexValues{
   721  			"twocol": {
   722  				PvMap: map[string]evalengine.Expr{
   723  					"c1": evalengine.NewLiteralInt(1),
   724  					"c2": evalengine.NewLiteralInt(2),
   725  				},
   726  				Offset: 4,
   727  			},
   728  			"onecol": {
   729  				PvMap: map[string]evalengine.Expr{
   730  					"c3": evalengine.NewLiteralInt(3),
   731  				},
   732  				Offset: 5,
   733  			},
   734  		},
   735  	}
   736  
   737  	results := []*sqltypes.Result{sqltypes.MakeTestResult(
   738  		sqltypes.MakeTestFields(
   739  			"id|c1|c2|c3|twocol|onecol",
   740  			"int64|int64|int64|int64|int64|int64",
   741  		),
   742  		"1|4|5|6|0|0",
   743  		"2|21|22|23|0|0",
   744  	)}
   745  	vc := newDMLTestVCursor("-20", "20-")
   746  	vc.results = results
   747  
   748  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   749  	require.NoError(t, err)
   750  	vc.ExpectLog(t, []string{
   751  		`ResolveDestinations sharded [type:INT64 value:"1" type:INT64 value:"2"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6),DestinationKeyspaceID(06e7ea22ce92708f)`,
   752  		// ResolveDestinations is hard-coded to return -20.
   753  		// It gets used to perform the subquery to fetch the changing column values.
   754  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   755  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   756  		// 4,5 have to be replaced by 1,2 (the new values).
   757  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   758  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   759  		// 6 has to be replaced by 3.
   760  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   761  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   762  		// 21,22 have to be replaced by 1,2 (the new values).
   763  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"21" from2: type:INT64 value:"22" toc: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   764  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   765  		// 23 has to be replaced by 3.
   766  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"23" toc: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   767  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   768  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   769  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   770  	})
   771  
   772  	// No rows changing
   773  	vc = newDMLTestVCursor("-20", "20-")
   774  
   775  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   776  	require.NoError(t, err)
   777  	vc.ExpectLog(t, []string{
   778  		`ResolveDestinations sharded [type:INT64 value:"1" type:INT64 value:"2"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6),DestinationKeyspaceID(06e7ea22ce92708f)`,
   779  		// ResolveDestinations is hard-coded to return -20.
   780  		// It gets used to perform the subquery to fetch the changing column values.
   781  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   782  		// Subquery returns no rows. So, no vindexes are updated. We still pass-through the original update.
   783  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   784  	})
   785  
   786  	// multiple rows changing.
   787  	results = []*sqltypes.Result{sqltypes.MakeTestResult(
   788  		sqltypes.MakeTestFields(
   789  			"id|c1|c2|c3|twocol|onecol",
   790  			"int64|int64|int64|int64|int64|int64",
   791  		),
   792  		"1|4|5|6|0|0",
   793  		"1|7|8|9|0|0",
   794  		"2|21|22|23|0|0",
   795  	)}
   796  	vc = newDMLTestVCursor("-20", "20-")
   797  	vc.results = results
   798  
   799  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   800  	require.NoError(t, err)
   801  	vc.ExpectLog(t, []string{
   802  		`ResolveDestinations sharded [type:INT64 value:"1" type:INT64 value:"2"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6),DestinationKeyspaceID(06e7ea22ce92708f)`,
   803  		// ResolveDestinations is hard-coded to return -20.
   804  		// It gets used to perform the subquery to fetch the changing column values.
   805  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   806  		// Those values are returned as 4,5 for twocol and 6 for onecol.
   807  		// 4,5 have to be replaced by 1,2 (the new values).
   808  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"4" from2: type:INT64 value:"5" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   809  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   810  		// 6 has to be replaced by 3.
   811  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   812  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   813  		// 7,8 have to be replaced by 1,2 (the new values).
   814  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"7" from2: type:INT64 value:"8" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   815  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   816  		// 9 has to be replaced by 3.
   817  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"9" toc: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   818  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x16k@\xb4J\xbaK\xd6" true`,
   819  		// 21,22 have to be replaced by 1,2 (the new values).
   820  		`Execute delete from lkp2 where from1 = :from1 and from2 = :from2 and toc = :toc from1: type:INT64 value:"21" from2: type:INT64 value:"22" toc: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   821  		`Execute insert into lkp2(from1, from2, toc) values(:from1_0, :from2_0, :toc_0) from1_0: type:INT64 value:"1" from2_0: type:INT64 value:"2" toc_0: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   822  		// 23 has to be replaced by 3.
   823  		`Execute delete from lkp1 where from = :from and toc = :toc from: type:INT64 value:"23" toc: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   824  		`Execute insert into lkp1(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"3" toc_0: type:VARBINARY value:"\x06\xe7\xea\"Βp\x8f" true`,
   825  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   826  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   827  	})
   828  }
   829  
   830  func TestUpdateInChangedVindexMultiCol(t *testing.T) {
   831  	ks := buildTestVSchema().Keyspaces["sharded"]
   832  	upd := &Update{
   833  		DML: &DML{
   834  			RoutingParameters: &RoutingParameters{
   835  				Opcode:   IN,
   836  				Keyspace: ks.Keyspace,
   837  				Vindex:   ks.Vindexes["rg_vdx"],
   838  				Values: []evalengine.Expr{
   839  					evalengine.TupleExpr{evalengine.NewLiteralInt(1), evalengine.NewLiteralInt(2)},
   840  					evalengine.NewLiteralInt(3),
   841  				},
   842  			},
   843  			Query: "dummy_update",
   844  			Table: []*vindexes.Table{
   845  				ks.Tables["rg_tbl"],
   846  			},
   847  			OwnedVindexQuery: "dummy_subquery",
   848  			KsidVindex:       ks.Vindexes["rg_vdx"],
   849  			KsidLength:       2,
   850  		},
   851  		ChangedVindexValues: map[string]*VindexValues{
   852  			"lkp_rg": {
   853  				PvMap: map[string]evalengine.Expr{
   854  					"colc": evalengine.NewLiteralInt(5),
   855  				},
   856  				Offset: 3,
   857  			},
   858  		},
   859  	}
   860  
   861  	results := []*sqltypes.Result{sqltypes.MakeTestResult(
   862  		sqltypes.MakeTestFields(
   863  			"cola|colb|colc|colc=5",
   864  			"int64|int64|int64|int64",
   865  		),
   866  		"1|3|4|0",
   867  		"2|3|5|1",
   868  		"1|3|6|0",
   869  		"2|3|7|0",
   870  	)}
   871  	vc := newDMLTestVCursor("-20", "20-")
   872  	vc.results = results
   873  
   874  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   875  	require.NoError(t, err)
   876  	vc.ExpectLog(t, []string{
   877  		`ResolveDestinationsMultiCol sharded [[INT64(1) INT64(3)] [INT64(2) INT64(3)]] Destinations:DestinationKeyspaceID(014eb190c9a2fa169c),DestinationKeyspaceID(024eb190c9a2fa169c)`,
   878  		// ResolveDestinations is hard-coded to return -20.
   879  		// It gets used to perform the subquery to fetch the changing column values.
   880  		`ExecuteMultiShard sharded.-20: dummy_subquery {} false false`,
   881  		// Those values are returned as 4,5,6 and 7 for colc, but 5 is unchanged so only 3 rows will be updated.
   882  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"4" toc: type:VARBINARY value:"\x01N\xb1\x90ɢ\xfa\x16\x9c" true`,
   883  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x01N\xb1\x90ɢ\xfa\x16\x9c" true`,
   884  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"6" toc: type:VARBINARY value:"\x01N\xb1\x90ɢ\xfa\x16\x9c" true`,
   885  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x01N\xb1\x90ɢ\xfa\x16\x9c" true`,
   886  		`Execute delete from lkp_rg_tbl where from = :from and toc = :toc from: type:INT64 value:"7" toc: type:VARBINARY value:"\x02N\xb1\x90ɢ\xfa\x16\x9c" true`,
   887  		`Execute insert into lkp_rg_tbl(from, toc) values(:from_0, :toc_0) from_0: type:INT64 value:"5" toc_0: type:VARBINARY value:"\x02N\xb1\x90ɢ\xfa\x16\x9c" true`,
   888  		// Finally, the actual update, which is also sent to -20, same route as the subquery.
   889  		`ExecuteMultiShard sharded.-20: dummy_update {} true true`,
   890  	})
   891  }
   892  
   893  func TestUpdateEqualSubshard(t *testing.T) {
   894  	vindex, _ := vindexes.NewRegionExperimental("", map[string]string{"region_bytes": "1"})
   895  	upd := &Update{
   896  		DML: &DML{
   897  			RoutingParameters: &RoutingParameters{
   898  				Opcode: SubShard,
   899  				Keyspace: &vindexes.Keyspace{
   900  					Name:    "ks",
   901  					Sharded: true,
   902  				},
   903  				Vindex: vindex,
   904  				Values: []evalengine.Expr{evalengine.NewLiteralInt(1)},
   905  			},
   906  			Query: "dummy_update",
   907  		},
   908  	}
   909  
   910  	vc := newDMLTestVCursor("-20", "20-")
   911  	vc.shardForKsid = []string{"-20", "20-"}
   912  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   913  	require.NoError(t, err)
   914  	vc.ExpectLog(t, []string{
   915  		`ResolveDestinationsMultiCol ks [[INT64(1)]] Destinations:DestinationKeyRange(01-02)`,
   916  		`ExecuteMultiShard ks.-20: dummy_update {} ks.20-: dummy_update {} true false`,
   917  	})
   918  
   919  	vc.Rewind()
   920  	// as it is single shard so autocommit should be allowed.
   921  	vc.shardForKsid = []string{"-20"}
   922  	_, err = upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   923  	require.NoError(t, err)
   924  	vc.ExpectLog(t, []string{
   925  		`ResolveDestinationsMultiCol ks [[INT64(1)]] Destinations:DestinationKeyRange(01-02)`,
   926  		`ExecuteMultiShard ks.-20: dummy_update {} true true`,
   927  	})
   928  }
   929  
   930  func TestUpdateMultiEqual(t *testing.T) {
   931  	ks := buildTestVSchema().Keyspaces["sharded"]
   932  	upd := &Update{
   933  		DML: &DML{
   934  			RoutingParameters: &RoutingParameters{
   935  				Opcode:   MultiEqual,
   936  				Keyspace: ks.Keyspace,
   937  				Vindex:   ks.Vindexes["hash"],
   938  				Values: []evalengine.Expr{evalengine.NewTupleExpr(
   939  					evalengine.NewLiteralInt(1),
   940  					evalengine.NewLiteralInt(5),
   941  				)},
   942  			},
   943  			Query: "dummy_update",
   944  		},
   945  	}
   946  
   947  	vc := newDMLTestVCursor("-20", "20-")
   948  	vc.shardForKsid = []string{"-20", "20-"}
   949  	_, err := upd.TryExecute(context.Background(), vc, map[string]*querypb.BindVariable{}, false)
   950  	require.NoError(t, err)
   951  	vc.ExpectLog(t, []string{
   952  		`ResolveDestinations sharded [type:INT64 value:"1" type:INT64 value:"5"] Destinations:DestinationKeyspaceID(166b40b44aba4bd6),DestinationKeyspaceID(70bb023c810ca87a)`,
   953  		`ExecuteMultiShard sharded.-20: dummy_update {} sharded.20-: dummy_update {} true false`,
   954  	})
   955  }
   956  
   957  func buildTestVSchema() *vindexes.VSchema {
   958  	invschema := &vschemapb.SrvVSchema{
   959  		Keyspaces: map[string]*vschemapb.Keyspace{
   960  			"sharded": {
   961  				Sharded: true,
   962  				Vindexes: map[string]*vschemapb.Vindex{
   963  					"hash": {
   964  						Type: "hash",
   965  					},
   966  					"rg_vdx": {
   967  						Type: "region_experimental",
   968  						Params: map[string]string{
   969  							"region_bytes": "1",
   970  						},
   971  					},
   972  					"twocol": {
   973  						Type: "lookup",
   974  						Params: map[string]string{
   975  							"table": "lkp2",
   976  							"from":  "from1,from2",
   977  							"to":    "toc",
   978  						},
   979  						Owner: "t1",
   980  					},
   981  					"onecol": {
   982  						Type: "lookup",
   983  						Params: map[string]string{
   984  							"table": "lkp1",
   985  							"from":  "from",
   986  							"to":    "toc",
   987  						},
   988  						Owner: "t1",
   989  					},
   990  					"lkp_rg": {
   991  						Type: "lookup",
   992  						Params: map[string]string{
   993  							"table": "lkp_rg_tbl",
   994  							"from":  "from",
   995  							"to":    "toc",
   996  						},
   997  						Owner: "rg_tbl",
   998  					},
   999  				},
  1000  				Tables: map[string]*vschemapb.Table{
  1001  					"t1": {
  1002  						ColumnVindexes: []*vschemapb.ColumnVindex{{
  1003  							Name:    "hash",
  1004  							Columns: []string{"id"},
  1005  						}, {
  1006  							Name:    "twocol",
  1007  							Columns: []string{"c1", "c2"},
  1008  						}, {
  1009  							Name:    "onecol",
  1010  							Columns: []string{"c3"},
  1011  						}},
  1012  					},
  1013  					"t2": {
  1014  						ColumnVindexes: []*vschemapb.ColumnVindex{{
  1015  							Name:    "hash",
  1016  							Columns: []string{"id"},
  1017  						}},
  1018  					},
  1019  					"rg_tbl": {
  1020  						ColumnVindexes: []*vschemapb.ColumnVindex{{
  1021  							Name:    "rg_vdx",
  1022  							Columns: []string{"cola", "colb"},
  1023  						}, {
  1024  							Name:    "lkp_rg",
  1025  							Columns: []string{"colc"},
  1026  						}},
  1027  					},
  1028  				},
  1029  			},
  1030  		},
  1031  	}
  1032  	vs := vindexes.BuildVSchema(invschema)
  1033  	return vs
  1034  }
  1035  
  1036  func newDMLTestVCursor(shards ...string) *loggingVCursor {
  1037  	return &loggingVCursor{shards: shards, resolvedTargetTabletType: topodatapb.TabletType_PRIMARY}
  1038  }