vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/routing.go (about)

     1  /*
     2  Copyright 2022 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  	"encoding/json"
    22  	"strconv"
    23  
    24  	"vitess.io/vitess/go/sqltypes"
    25  	"vitess.io/vitess/go/vt/key"
    26  	"vitess.io/vitess/go/vt/log"
    27  	querypb "vitess.io/vitess/go/vt/proto/query"
    28  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    29  	"vitess.io/vitess/go/vt/sqlparser"
    30  	"vitess.io/vitess/go/vt/srvtopo"
    31  	"vitess.io/vitess/go/vt/vterrors"
    32  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    33  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    34  )
    35  
    36  // Opcode is a number representing the opcode
    37  // for any engine primitve.
    38  type Opcode int
    39  
    40  // This is the list of Opcode values.
    41  const (
    42  	// Unsharded is for routing a statement
    43  	// to an unsharded keyspace.
    44  	Unsharded = Opcode(iota)
    45  	// EqualUnique is for routing a query to a single shard.
    46  	// Requires: A Unique Vindex, and a single Value.
    47  	EqualUnique
    48  	// Equal is for routing a query using a non-unique vindex.
    49  	// Requires: A Vindex, and a single Value.
    50  	Equal
    51  	// IN is for routing a statement to a multi shard.
    52  	// Requires: A Vindex, and a multi Values.
    53  	IN
    54  	// MultiEqual is used for routing queries with IN with tuple clause
    55  	// Requires: A Vindex, and a multi Tuple Values.
    56  	MultiEqual
    57  	// SubShard is for when we are missing one or more columns from a composite vindex
    58  	SubShard
    59  	// Scatter is for routing a scattered statement.
    60  	Scatter
    61  	// Next is for fetching from a sequence.
    62  	Next
    63  	// DBA is used for routing DBA queries
    64  	// e.g: Select * from information_schema.tables where schema_name = "a"
    65  	DBA
    66  	// Reference is for fetching from a reference table.
    67  	Reference
    68  	// None is used for queries which do not need routing
    69  	None
    70  	// ByDestination is to route explicitly to a given target destination.
    71  	// Is used when the query explicitly sets a target destination:
    72  	// in the clause e.g: UPDATE `keyspace[-]`.x1 SET foo=1
    73  	ByDestination
    74  )
    75  
    76  var opName = map[Opcode]string{
    77  	Unsharded:     "Unsharded",
    78  	EqualUnique:   "EqualUnique",
    79  	Equal:         "Equal",
    80  	IN:            "IN",
    81  	MultiEqual:    "MultiEqual",
    82  	Scatter:       "Scatter",
    83  	DBA:           "DBA",
    84  	Next:          "Next",
    85  	Reference:     "Reference",
    86  	None:          "None",
    87  	ByDestination: "ByDestination",
    88  	SubShard:      "SubShard",
    89  }
    90  
    91  // MarshalJSON serializes the Opcode as a JSON string.
    92  // It's used for testing and diagnostics.
    93  func (code Opcode) MarshalJSON() ([]byte, error) {
    94  	return json.Marshal(opName[code])
    95  }
    96  
    97  // String returns a string presentation of this opcode
    98  func (code Opcode) String() string {
    99  	return opName[code]
   100  }
   101  
   102  type RoutingParameters struct {
   103  	// Opcode is the execution opcode.
   104  	Opcode Opcode
   105  
   106  	// Keyspace specifies the keyspace to send the query to.
   107  	Keyspace *vindexes.Keyspace
   108  
   109  	// The following two fields are used when routing information_schema queries
   110  	SysTableTableSchema []evalengine.Expr
   111  	SysTableTableName   map[string]evalengine.Expr
   112  
   113  	// TargetDestination specifies an explicit target destination to send the query to.
   114  	// This will bypass the routing logic.
   115  	TargetDestination key.Destination // update `user[-]@replica`.user set ....
   116  
   117  	// Vindex specifies the vindex to be used.
   118  	Vindex vindexes.Vindex
   119  
   120  	// Values specifies the vindex values to use for routing.
   121  	Values []evalengine.Expr
   122  }
   123  
   124  func (code Opcode) IsSingleShard() bool {
   125  	switch code {
   126  	case Unsharded, DBA, Next, EqualUnique, Reference:
   127  		return true
   128  	}
   129  	return false
   130  }
   131  
   132  func (rp *RoutingParameters) findRoute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   133  	switch rp.Opcode {
   134  	case None:
   135  		return nil, nil, nil
   136  	case DBA:
   137  		return rp.systemQuery(ctx, vcursor, bindVars)
   138  	case Unsharded, Next:
   139  		return rp.unsharded(ctx, vcursor, bindVars)
   140  	case Reference:
   141  		return rp.anyShard(ctx, vcursor, bindVars)
   142  	case Scatter:
   143  		return rp.byDestination(ctx, vcursor, bindVars, key.DestinationAllShards{})
   144  	case ByDestination:
   145  		return rp.byDestination(ctx, vcursor, bindVars, rp.TargetDestination)
   146  	case Equal, EqualUnique, SubShard:
   147  		switch rp.Vindex.(type) {
   148  		case vindexes.MultiColumn:
   149  			return rp.equalMultiCol(ctx, vcursor, bindVars)
   150  		default:
   151  			return rp.equal(ctx, vcursor, bindVars)
   152  		}
   153  	case IN:
   154  		switch rp.Vindex.(type) {
   155  		case vindexes.MultiColumn:
   156  			return rp.inMultiCol(ctx, vcursor, bindVars)
   157  		default:
   158  			return rp.in(ctx, vcursor, bindVars)
   159  		}
   160  	case MultiEqual:
   161  		switch rp.Vindex.(type) {
   162  		case vindexes.MultiColumn:
   163  			return rp.multiEqualMultiCol(ctx, vcursor, bindVars)
   164  		default:
   165  			return rp.multiEqual(ctx, vcursor, bindVars)
   166  		}
   167  	default:
   168  		// Unreachable.
   169  		return nil, nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "unsupported opcode: %v", rp.Opcode)
   170  	}
   171  }
   172  
   173  func (rp *RoutingParameters) systemQuery(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   174  	destinations, err := rp.routeInfoSchemaQuery(ctx, vcursor, bindVars)
   175  	if err != nil {
   176  		return nil, nil, err
   177  	}
   178  
   179  	return destinations, []map[string]*querypb.BindVariable{bindVars}, nil
   180  }
   181  
   182  func (rp *RoutingParameters) routeInfoSchemaQuery(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, error) {
   183  	defaultRoute := func() ([]*srvtopo.ResolvedShard, error) {
   184  		ks := rp.Keyspace.Name
   185  		destinations, _, err := vcursor.ResolveDestinations(ctx, ks, nil, []key.Destination{key.DestinationAnyShard{}})
   186  		return destinations, vterrors.Wrapf(err, "failed to find information about keyspace `%s`", ks)
   187  	}
   188  
   189  	if len(rp.SysTableTableName) == 0 && len(rp.SysTableTableSchema) == 0 {
   190  		return defaultRoute()
   191  	}
   192  
   193  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   194  	var specifiedKS string
   195  	for _, tableSchema := range rp.SysTableTableSchema {
   196  		result, err := env.Evaluate(tableSchema)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  		ks := result.Value().ToString()
   201  		if specifiedKS == "" {
   202  			specifiedKS = ks
   203  		}
   204  		if specifiedKS != ks {
   205  			return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "specifying two different database in the query is not supported")
   206  		}
   207  	}
   208  	if specifiedKS != "" {
   209  		bindVars[sqltypes.BvSchemaName] = sqltypes.StringBindVariable(specifiedKS)
   210  	}
   211  
   212  	tableNames := map[string]string{}
   213  	for tblBvName, sysTableName := range rp.SysTableTableName {
   214  		val, err := env.Evaluate(sysTableName)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  		tabName := val.Value().ToString()
   219  		tableNames[tblBvName] = tabName
   220  		bindVars[tblBvName] = sqltypes.StringBindVariable(tabName)
   221  	}
   222  
   223  	// if the table_schema is system schema, route to default keyspace.
   224  	if sqlparser.SystemSchema(specifiedKS) {
   225  		return defaultRoute()
   226  	}
   227  
   228  	// the use has specified a table_name - let's check if it's a routed table
   229  	if len(tableNames) > 0 {
   230  		rss, err := rp.routedTable(ctx, vcursor, bindVars, specifiedKS, tableNames)
   231  		if err != nil {
   232  			// Only if keyspace is not found in vschema, we try with default keyspace.
   233  			// As the in the table_schema predicates for a keyspace 'ks' it can contain 'vt_ks'.
   234  			if vterrors.ErrState(err) == vterrors.BadDb {
   235  				return defaultRoute()
   236  			}
   237  			return nil, err
   238  		}
   239  		if rss != nil {
   240  			return rss, nil
   241  		}
   242  	}
   243  
   244  	// it was not a routed table, and we dont have a schema name to look up. give up
   245  	if specifiedKS == "" {
   246  		return defaultRoute()
   247  	}
   248  
   249  	// we only have table_schema to work with
   250  	destinations, _, err := vcursor.ResolveDestinations(ctx, specifiedKS, nil, []key.Destination{key.DestinationAnyShard{}})
   251  	if err != nil {
   252  		log.Errorf("failed to route information_schema query to keyspace [%s]", specifiedKS)
   253  		bindVars[sqltypes.BvSchemaName] = sqltypes.StringBindVariable(specifiedKS)
   254  		return defaultRoute()
   255  	}
   256  	setReplaceSchemaName(bindVars)
   257  	return destinations, nil
   258  }
   259  
   260  func (rp *RoutingParameters) routedTable(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, tableSchema string, tableNames map[string]string) ([]*srvtopo.ResolvedShard, error) {
   261  	var routedKs *vindexes.Keyspace
   262  	for tblBvName, tableName := range tableNames {
   263  		tbl := sqlparser.TableName{
   264  			Name:      sqlparser.NewIdentifierCS(tableName),
   265  			Qualifier: sqlparser.NewIdentifierCS(tableSchema),
   266  		}
   267  		routedTable, err := vcursor.FindRoutedTable(tbl)
   268  		if err != nil {
   269  			return nil, err
   270  		}
   271  
   272  		if routedTable != nil {
   273  			// if we were able to find information about this table, let's use it
   274  
   275  			// check if the query is send to single keyspace.
   276  			if routedKs == nil {
   277  				routedKs = routedTable.Keyspace
   278  			}
   279  			if routedKs.Name != routedTable.Keyspace.Name {
   280  				return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "cannot send the query to multiple keyspace due to different table_name: %s, %s", routedKs.Name, routedTable.Keyspace.Name)
   281  			}
   282  
   283  			shards, _, err := vcursor.ResolveDestinations(ctx, routedTable.Keyspace.Name, nil, []key.Destination{key.DestinationAnyShard{}})
   284  			bindVars[tblBvName] = sqltypes.StringBindVariable(routedTable.Name.String())
   285  			if tableSchema != "" {
   286  				setReplaceSchemaName(bindVars)
   287  			}
   288  			return shards, err
   289  		}
   290  		// no routed table info found. we'll return nil and check on the outside if we can find the table_schema
   291  		bindVars[tblBvName] = sqltypes.StringBindVariable(tableName)
   292  	}
   293  	return nil, nil
   294  }
   295  
   296  func (rp *RoutingParameters) anyShard(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   297  	rss, _, err := vcursor.ResolveDestinations(ctx, rp.Keyspace.Name, nil, []key.Destination{key.DestinationAnyShard{}})
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   302  	for i := range multiBindVars {
   303  		multiBindVars[i] = bindVars
   304  	}
   305  	return rss, multiBindVars, nil
   306  }
   307  
   308  func (rp *RoutingParameters) unsharded(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   309  	rss, _, err := vcursor.ResolveDestinations(ctx, rp.Keyspace.Name, nil, []key.Destination{key.DestinationAllShards{}})
   310  	if err != nil {
   311  		return nil, nil, err
   312  	}
   313  	if len(rss) != 1 {
   314  		return nil, nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "Keyspace '%s' does not have exactly one shard: %v", rp.Keyspace.Name, rss)
   315  	}
   316  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   317  	for i := range multiBindVars {
   318  		multiBindVars[i] = bindVars
   319  	}
   320  	return rss, multiBindVars, nil
   321  }
   322  
   323  func (rp *RoutingParameters) byDestination(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, destination key.Destination) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   324  	rss, _, err := vcursor.ResolveDestinations(ctx, rp.Keyspace.Name, nil, []key.Destination{destination})
   325  	if err != nil {
   326  		return nil, nil, err
   327  	}
   328  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   329  	for i := range multiBindVars {
   330  		multiBindVars[i] = bindVars
   331  	}
   332  	return rss, multiBindVars, err
   333  }
   334  
   335  func (rp *RoutingParameters) equal(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   336  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   337  	value, err := env.Evaluate(rp.Values[0])
   338  	if err != nil {
   339  		return nil, nil, err
   340  	}
   341  	rss, _, err := resolveShards(ctx, vcursor, rp.Vindex.(vindexes.SingleColumn), rp.Keyspace, []sqltypes.Value{value.Value()})
   342  	if err != nil {
   343  		return nil, nil, err
   344  	}
   345  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   346  	for i := range multiBindVars {
   347  		multiBindVars[i] = bindVars
   348  	}
   349  	return rss, multiBindVars, nil
   350  }
   351  
   352  func (rp *RoutingParameters) equalMultiCol(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   353  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   354  	var rowValue []sqltypes.Value
   355  	for _, rvalue := range rp.Values {
   356  		v, err := env.Evaluate(rvalue)
   357  		if err != nil {
   358  			return nil, nil, err
   359  		}
   360  		rowValue = append(rowValue, v.Value())
   361  	}
   362  
   363  	rss, _, err := resolveShardsMultiCol(ctx, vcursor, rp.Vindex.(vindexes.MultiColumn), rp.Keyspace, [][]sqltypes.Value{rowValue}, false /* shardIdsNeeded */)
   364  	if err != nil {
   365  		return nil, nil, err
   366  	}
   367  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   368  	for i := range multiBindVars {
   369  		multiBindVars[i] = bindVars
   370  	}
   371  	return rss, multiBindVars, nil
   372  }
   373  
   374  func (rp *RoutingParameters) in(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   375  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   376  	value, err := env.Evaluate(rp.Values[0])
   377  	if err != nil {
   378  		return nil, nil, err
   379  	}
   380  	rss, values, err := resolveShards(ctx, vcursor, rp.Vindex.(vindexes.SingleColumn), rp.Keyspace, value.TupleValues())
   381  	if err != nil {
   382  		return nil, nil, err
   383  	}
   384  	return rss, shardVars(bindVars, values), nil
   385  }
   386  
   387  func (rp *RoutingParameters) inMultiCol(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   388  	rowColValues, isSingleVal, err := generateRowColValues(vcursor, bindVars, rp.Values)
   389  	if err != nil {
   390  		return nil, nil, err
   391  	}
   392  
   393  	rss, mapVals, err := resolveShardsMultiCol(ctx, vcursor, rp.Vindex.(vindexes.MultiColumn), rp.Keyspace, rowColValues, true /* shardIdsNeeded */)
   394  	if err != nil {
   395  		return nil, nil, err
   396  	}
   397  	return rss, shardVarsMultiCol(bindVars, mapVals, isSingleVal), nil
   398  }
   399  
   400  func (rp *RoutingParameters) multiEqual(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   401  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   402  	value, err := env.Evaluate(rp.Values[0])
   403  	if err != nil {
   404  		return nil, nil, err
   405  	}
   406  	rss, _, err := resolveShards(ctx, vcursor, rp.Vindex.(vindexes.SingleColumn), rp.Keyspace, value.TupleValues())
   407  	if err != nil {
   408  		return nil, nil, err
   409  	}
   410  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   411  	for i := range multiBindVars {
   412  		multiBindVars[i] = bindVars
   413  	}
   414  	return rss, multiBindVars, nil
   415  }
   416  
   417  func (rp *RoutingParameters) multiEqualMultiCol(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]*srvtopo.ResolvedShard, []map[string]*querypb.BindVariable, error) {
   418  	var multiColValues [][]sqltypes.Value
   419  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   420  	for _, rvalue := range rp.Values {
   421  		v, err := env.Evaluate(rvalue)
   422  		if err != nil {
   423  			return nil, nil, err
   424  		}
   425  		multiColValues = append(multiColValues, v.TupleValues())
   426  	}
   427  
   428  	// transpose from multi col value to vindex keys with one value from each multi column values.
   429  	// [1,3]
   430  	// [2,4]
   431  	// [5,6]
   432  	// change
   433  	// [1,2,5]
   434  	// [3,4,6]
   435  
   436  	rowColValues := make([][]sqltypes.Value, len(multiColValues[0]))
   437  	for _, colValues := range multiColValues {
   438  		for row, colVal := range colValues {
   439  			rowColValues[row] = append(rowColValues[row], colVal)
   440  		}
   441  	}
   442  
   443  	rss, _, err := resolveShardsMultiCol(ctx, vcursor, rp.Vindex.(vindexes.MultiColumn), rp.Keyspace, rowColValues, false /* shardIdsNotNeeded */)
   444  	if err != nil {
   445  		return nil, nil, err
   446  	}
   447  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   448  	for i := range multiBindVars {
   449  		multiBindVars[i] = bindVars
   450  	}
   451  	return rss, multiBindVars, nil
   452  }
   453  
   454  func setReplaceSchemaName(bindVars map[string]*querypb.BindVariable) {
   455  	delete(bindVars, sqltypes.BvSchemaName)
   456  	bindVars[sqltypes.BvReplaceSchemaName] = sqltypes.Int64BindVariable(1)
   457  }
   458  
   459  func resolveShards(ctx context.Context, vcursor VCursor, vindex vindexes.SingleColumn, keyspace *vindexes.Keyspace, vindexKeys []sqltypes.Value) ([]*srvtopo.ResolvedShard, [][]*querypb.Value, error) {
   460  	// Convert vindexKeys to []*querypb.Value
   461  	ids := make([]*querypb.Value, len(vindexKeys))
   462  	for i, vik := range vindexKeys {
   463  		ids[i] = sqltypes.ValueToProto(vik)
   464  	}
   465  
   466  	// Map using the Vindex
   467  	destinations, err := vindex.Map(ctx, vcursor, vindexKeys)
   468  	if err != nil {
   469  		return nil, nil, err
   470  
   471  	}
   472  
   473  	// And use the Resolver to map to ResolvedShards.
   474  	return vcursor.ResolveDestinations(ctx, keyspace.Name, ids, destinations)
   475  }
   476  
   477  func resolveShardsMultiCol(ctx context.Context, vcursor VCursor, vindex vindexes.MultiColumn, keyspace *vindexes.Keyspace, rowColValues [][]sqltypes.Value, shardIdsNeeded bool) ([]*srvtopo.ResolvedShard, [][][]*querypb.Value, error) {
   478  	destinations, err := vindex.Map(ctx, vcursor, rowColValues)
   479  	if err != nil {
   480  		return nil, nil, err
   481  	}
   482  
   483  	// And use the Resolver to map to ResolvedShards.
   484  	rss, shardsValues, err := vcursor.ResolveDestinationsMultiCol(ctx, keyspace.Name, rowColValues, destinations)
   485  	if err != nil {
   486  		return nil, nil, err
   487  	}
   488  
   489  	if shardIdsNeeded {
   490  		return rss, buildMultiColumnVindexValues(shardsValues), nil
   491  	}
   492  	return rss, nil, nil
   493  }
   494  
   495  // buildMultiColumnVindexValues takes in the values resolved for each shard and transposes them
   496  // and eliminates duplicates, returning the values to be used for each column for a multi column
   497  // vindex in each shard.
   498  func buildMultiColumnVindexValues(shardsValues [][][]sqltypes.Value) [][][]*querypb.Value {
   499  	var shardsIds [][][]*querypb.Value
   500  	for _, shardValues := range shardsValues {
   501  		// shardValues -> [[0,1], [0,2], [0,3]]
   502  		// shardIds -> [[0,0,0], [1,2,3]]
   503  		// cols = 2
   504  		cols := len(shardValues[0])
   505  		shardIds := make([][]*querypb.Value, cols)
   506  		colValSeen := make([]map[string]any, cols)
   507  		for _, values := range shardValues {
   508  			for colIdx, value := range values {
   509  				if colValSeen[colIdx] == nil {
   510  					colValSeen[colIdx] = map[string]any{}
   511  				}
   512  				if _, found := colValSeen[colIdx][value.String()]; found {
   513  					continue
   514  				}
   515  				shardIds[colIdx] = append(shardIds[colIdx], sqltypes.ValueToProto(value))
   516  				colValSeen[colIdx][value.String()] = nil
   517  			}
   518  		}
   519  		shardsIds = append(shardsIds, shardIds)
   520  	}
   521  	return shardsIds
   522  }
   523  
   524  func shardVars(bv map[string]*querypb.BindVariable, mapVals [][]*querypb.Value) []map[string]*querypb.BindVariable {
   525  	shardVars := make([]map[string]*querypb.BindVariable, len(mapVals))
   526  	for i, vals := range mapVals {
   527  		newbv := make(map[string]*querypb.BindVariable, len(bv)+1)
   528  		for k, v := range bv {
   529  			newbv[k] = v
   530  		}
   531  		newbv[ListVarName] = &querypb.BindVariable{
   532  			Type:   querypb.Type_TUPLE,
   533  			Values: vals,
   534  		}
   535  		shardVars[i] = newbv
   536  	}
   537  	return shardVars
   538  }
   539  
   540  func shardVarsMultiCol(bv map[string]*querypb.BindVariable, mapVals [][][]*querypb.Value, isSingleVal map[int]any) []map[string]*querypb.BindVariable {
   541  	shardVars := make([]map[string]*querypb.BindVariable, len(mapVals))
   542  	for i, shardVals := range mapVals {
   543  		newbv := make(map[string]*querypb.BindVariable, len(bv)+len(shardVals)-len(isSingleVal))
   544  		for k, v := range bv {
   545  			newbv[k] = v
   546  		}
   547  		for j, vals := range shardVals {
   548  			if _, found := isSingleVal[j]; found {
   549  				// this vindex column is non-tuple column hence listVal bind variable is not required to be set.
   550  				continue
   551  			}
   552  			newbv[ListVarName+strconv.Itoa(j)] = &querypb.BindVariable{
   553  				Type:   querypb.Type_TUPLE,
   554  				Values: vals,
   555  			}
   556  		}
   557  		shardVars[i] = newbv
   558  	}
   559  	return shardVars
   560  }
   561  
   562  func generateRowColValues(vcursor VCursor, bindVars map[string]*querypb.BindVariable, values []evalengine.Expr) ([][]sqltypes.Value, map[int]any, error) {
   563  	// gather values from all the column in the vindex
   564  	var multiColValues [][]sqltypes.Value
   565  	var lv []sqltypes.Value
   566  	isSingleVal := map[int]any{}
   567  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   568  	for colIdx, rvalue := range values {
   569  		result, err := env.Evaluate(rvalue)
   570  		if err != nil {
   571  			return nil, nil, err
   572  		}
   573  		lv = result.TupleValues()
   574  		if lv == nil {
   575  			v, err := env.Evaluate(rvalue)
   576  			if err != nil {
   577  				return nil, nil, err
   578  			}
   579  			isSingleVal[colIdx] = nil
   580  			lv = []sqltypes.Value{v.Value()}
   581  		}
   582  		multiColValues = append(multiColValues, lv)
   583  	}
   584  
   585  	/*
   586  		need to convert them into vindex keys
   587  		from: cola (1,2) colb (3,4,5)
   588  		to: keys (1,3) (1,4) (1,5) (2,3) (2,4) (2,5)
   589  
   590  		so that the vindex can map them into correct destination.
   591  	*/
   592  
   593  	var rowColValues [][]sqltypes.Value
   594  	for _, firstCol := range multiColValues[0] {
   595  		rowColValues = append(rowColValues, []sqltypes.Value{firstCol})
   596  	}
   597  	for idx := 1; idx < len(multiColValues); idx++ {
   598  		rowColValues = buildRowColValues(rowColValues, multiColValues[idx])
   599  	}
   600  	return rowColValues, isSingleVal, nil
   601  }
   602  
   603  // buildRowColValues will take [1,2][1,3] as left input and [4,5] as right input
   604  // convert it into [1,2,4][1,2,5][1,3,4][1,3,5]
   605  // all combination of left and right side.
   606  func buildRowColValues(left [][]sqltypes.Value, right []sqltypes.Value) [][]sqltypes.Value {
   607  	var allCombinations [][]sqltypes.Value
   608  	for _, firstPart := range left {
   609  		for _, secondPart := range right {
   610  			allCombinations = append(allCombinations, append(firstPart, secondPart))
   611  		}
   612  	}
   613  	return allCombinations
   614  }