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 }