vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/operator_transformers.go (about) 1 /* 2 Copyright 2021 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 planbuilder 18 19 import ( 20 "fmt" 21 "sort" 22 "strconv" 23 "strings" 24 25 "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/rewrite" 26 27 "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/ops" 28 29 "vitess.io/vitess/go/sqltypes" 30 31 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 32 33 "vitess.io/vitess/go/mysql/collations" 34 35 "vitess.io/vitess/go/vt/vtgate/evalengine" 36 37 "vitess.io/vitess/go/vt/vtgate/planbuilder/operators" 38 39 "vitess.io/vitess/go/vt/sqlparser" 40 "vitess.io/vitess/go/vt/vtgate/engine" 41 "vitess.io/vitess/go/vt/vtgate/vindexes" 42 43 "vitess.io/vitess/go/vt/vterrors" 44 ) 45 46 func transformToLogicalPlan(ctx *plancontext.PlanningContext, op ops.Operator, isRoot bool) (logicalPlan, error) { 47 switch op := op.(type) { 48 case *operators.Route: 49 return transformRoutePlan(ctx, op) 50 case *operators.ApplyJoin: 51 return transformApplyJoinPlan(ctx, op) 52 case *operators.Union: 53 return transformUnionPlan(ctx, op, isRoot) 54 case *operators.Vindex: 55 return transformVindexPlan(ctx, op) 56 case *operators.SubQueryOp: 57 return transformSubQueryPlan(ctx, op) 58 case *operators.CorrelatedSubQueryOp: 59 return transformCorrelatedSubQueryPlan(ctx, op) 60 case *operators.Derived: 61 return transformDerivedPlan(ctx, op) 62 case *operators.Filter: 63 plan, err := transformToLogicalPlan(ctx, op.Source, false) 64 if err != nil { 65 return nil, err 66 } 67 scl := &simpleConverterLookup{ 68 canPushProjection: true, 69 ctx: ctx, 70 plan: plan, 71 } 72 ast := ctx.SemTable.AndExpressions(op.Predicates...) 73 predicate, err := evalengine.Translate(ast, scl) 74 if err != nil { 75 return nil, err 76 } 77 78 return &filter{ 79 logicalPlanCommon: newBuilderCommon(plan), 80 efilter: &engine.Filter{ 81 Predicate: predicate, 82 ASTPredicate: ast, 83 }, 84 }, nil 85 case *operators.Horizon: 86 return transformHorizon(ctx, op, isRoot) 87 } 88 89 return nil, vterrors.VT13001(fmt.Sprintf("unknown type encountered: %T (transformToLogicalPlan)", op)) 90 } 91 92 func transformHorizon(ctx *plancontext.PlanningContext, op *operators.Horizon, isRoot bool) (logicalPlan, error) { 93 source, err := transformToLogicalPlan(ctx, op.Source, isRoot) 94 if err != nil { 95 return nil, err 96 } 97 switch node := op.Select.(type) { 98 case *sqlparser.Select: 99 hp := horizonPlanning{ 100 sel: node, 101 } 102 103 replaceSubQuery(ctx, node) 104 plan, err := hp.planHorizon(ctx, source, true) 105 if err != nil { 106 return nil, err 107 } 108 return planLimit(node.Limit, plan) 109 case *sqlparser.Union: 110 var err error 111 rb, isRoute := source.(*routeGen4) 112 if !isRoute && ctx.SemTable.NotSingleRouteErr != nil { 113 return nil, ctx.SemTable.NotSingleRouteErr 114 } 115 var plan logicalPlan 116 if isRoute && rb.isSingleShard() { 117 err = planSingleShardRoutePlan(node, rb) 118 plan = rb 119 } else { 120 plan, err = planOrderByOnUnion(ctx, source, node) 121 } 122 if err != nil { 123 return nil, err 124 } 125 126 return planLimit(node.Limit, plan) 127 default: 128 panic("only SELECT and UNION implement the SelectStatement interface") 129 } 130 } 131 132 func transformApplyJoinPlan(ctx *plancontext.PlanningContext, n *operators.ApplyJoin) (logicalPlan, error) { 133 lhs, err := transformToLogicalPlan(ctx, n.LHS, false) 134 if err != nil { 135 return nil, err 136 } 137 rhs, err := transformToLogicalPlan(ctx, n.RHS, false) 138 if err != nil { 139 return nil, err 140 } 141 opCode := engine.InnerJoin 142 if n.LeftJoin { 143 opCode = engine.LeftJoin 144 } 145 146 return &joinGen4{ 147 Left: lhs, 148 Right: rhs, 149 Cols: n.Columns, 150 Vars: n.Vars, 151 LHSColumns: n.LHSColumns, 152 Opcode: opCode, 153 }, nil 154 } 155 156 func routeToEngineRoute(ctx *plancontext.PlanningContext, op *operators.Route) (*engine.Route, error) { 157 tableNames, err := getAllTableNames(op) 158 if err != nil { 159 return nil, err 160 } 161 var vindex vindexes.Vindex 162 var values []evalengine.Expr 163 if op.SelectedVindex() != nil { 164 vindex = op.Selected.FoundVindex 165 values = op.Selected.Values 166 } 167 return &engine.Route{ 168 TableName: strings.Join(tableNames, ", "), 169 RoutingParameters: &engine.RoutingParameters{ 170 Opcode: op.RouteOpCode, 171 Keyspace: op.Keyspace, 172 Vindex: vindex, 173 Values: values, 174 SysTableTableName: op.SysTableTableName, 175 SysTableTableSchema: op.SysTableTableSchema, 176 }, 177 }, nil 178 } 179 180 func transformRoutePlan(ctx *plancontext.PlanningContext, op *operators.Route) (logicalPlan, error) { 181 switch src := op.Source.(type) { 182 case *operators.Update: 183 return transformUpdatePlan(ctx, op, src) 184 case *operators.Delete: 185 return transformDeletePlan(ctx, op, src) 186 } 187 condition := getVindexPredicate(ctx, op) 188 sel, err := operators.ToSQL(ctx, op.Source) 189 if err != nil { 190 return nil, err 191 } 192 replaceSubQuery(ctx, sel) 193 eroute, err := routeToEngineRoute(ctx, op) 194 if err != nil { 195 return nil, err 196 } 197 return &routeGen4{ 198 eroute: eroute, 199 Select: sel, 200 tables: operators.TableID(op), 201 condition: condition, 202 }, nil 203 204 } 205 206 func transformUpdatePlan(ctx *plancontext.PlanningContext, op *operators.Route, upd *operators.Update) (logicalPlan, error) { 207 var vindex vindexes.Vindex 208 var values []evalengine.Expr 209 if op.Selected != nil { 210 vindex = op.Selected.FoundVindex 211 values = op.Selected.Values 212 } 213 ast := upd.AST 214 replaceSubQuery(ctx, ast) 215 edml := &engine.DML{ 216 Query: generateQuery(ast), 217 Table: []*vindexes.Table{ 218 upd.VTable, 219 }, 220 OwnedVindexQuery: upd.OwnedVindexQuery, 221 RoutingParameters: &engine.RoutingParameters{ 222 Opcode: op.RouteOpCode, 223 Keyspace: op.Keyspace, 224 Vindex: vindex, 225 Values: values, 226 TargetDestination: op.TargetDestination, 227 }, 228 } 229 230 directives := upd.AST.GetParsedComments().Directives() 231 if directives.IsSet(sqlparser.DirectiveMultiShardAutocommit) { 232 edml.MultiShardAutocommit = true 233 } 234 edml.QueryTimeout = queryTimeout(directives) 235 236 e := &engine.Update{ 237 ChangedVindexValues: upd.ChangedVindexValues, 238 } 239 e.DML = edml 240 241 if op.RouteOpCode != engine.Unsharded && len(upd.ChangedVindexValues) > 0 { 242 primary := upd.VTable.ColumnVindexes[0] 243 e.DML.KsidVindex = primary.Vindex 244 e.DML.KsidLength = len(primary.Columns) 245 } 246 247 return &primitiveWrapper{prim: e}, nil 248 } 249 250 func transformDeletePlan(ctx *plancontext.PlanningContext, op *operators.Route, del *operators.Delete) (logicalPlan, error) { 251 var vindex vindexes.Vindex 252 var values []evalengine.Expr 253 if op.Selected != nil { 254 vindex = op.Selected.FoundVindex 255 values = op.Selected.Values 256 } 257 ast := del.AST 258 replaceSubQuery(ctx, ast) 259 edml := &engine.DML{ 260 Query: generateQuery(ast), 261 Table: []*vindexes.Table{ 262 del.VTable, 263 }, 264 OwnedVindexQuery: del.OwnedVindexQuery, 265 RoutingParameters: &engine.RoutingParameters{ 266 Opcode: op.RouteOpCode, 267 Keyspace: op.Keyspace, 268 Vindex: vindex, 269 Values: values, 270 TargetDestination: op.TargetDestination, 271 }, 272 } 273 274 directives := del.AST.GetParsedComments().Directives() 275 if directives.IsSet(sqlparser.DirectiveMultiShardAutocommit) { 276 edml.MultiShardAutocommit = true 277 } 278 edml.QueryTimeout = queryTimeout(directives) 279 280 e := &engine.Delete{} 281 e.DML = edml 282 283 if op.RouteOpCode != engine.Unsharded && del.OwnedVindexQuery != "" { 284 primary := del.VTable.ColumnVindexes[0] 285 e.DML.KsidVindex = primary.Vindex 286 e.DML.KsidLength = len(primary.Columns) 287 } 288 289 return &primitiveWrapper{prim: e}, nil 290 } 291 292 func replaceSubQuery(ctx *plancontext.PlanningContext, sel sqlparser.Statement) { 293 extractedSubqueries := ctx.SemTable.GetSubqueryNeedingRewrite() 294 if len(extractedSubqueries) == 0 { 295 return 296 } 297 sqr := &subQReplacer{subqueryToReplace: extractedSubqueries} 298 sqlparser.SafeRewrite(sel, nil, sqr.replacer) 299 for sqr.replaced { 300 // to handle subqueries inside subqueries, we need to do this again and again until no replacements are left 301 sqr.replaced = false 302 sqlparser.SafeRewrite(sel, nil, sqr.replacer) 303 } 304 } 305 306 func getVindexPredicate(ctx *plancontext.PlanningContext, op *operators.Route) sqlparser.Expr { 307 var condition sqlparser.Expr 308 if op.Selected != nil { 309 if len(op.Selected.ValueExprs) > 0 { 310 condition = op.Selected.ValueExprs[0] 311 } 312 _, isMultiColumn := op.Selected.FoundVindex.(vindexes.MultiColumn) 313 for idx, predicate := range op.Selected.Predicates { 314 switch predicate := predicate.(type) { 315 case *sqlparser.ComparisonExpr: 316 if predicate.Operator == sqlparser.InOp { 317 switch predicate.Left.(type) { 318 case *sqlparser.ColName: 319 if subq, isSubq := predicate.Right.(*sqlparser.Subquery); isSubq { 320 extractedSubquery := ctx.SemTable.FindSubqueryReference(subq) 321 if extractedSubquery != nil { 322 extractedSubquery.SetArgName(engine.ListVarName) 323 } 324 } 325 if isMultiColumn { 326 predicate.Right = sqlparser.ListArg(engine.ListVarName + strconv.Itoa(idx)) 327 continue 328 } 329 predicate.Right = sqlparser.ListArg(engine.ListVarName) 330 } 331 } 332 } 333 } 334 335 } 336 return condition 337 } 338 339 func getAllTableNames(op *operators.Route) ([]string, error) { 340 tableNameMap := map[string]any{} 341 err := rewrite.Visit(op, func(op ops.Operator) error { 342 tbl, isTbl := op.(*operators.Table) 343 var name string 344 if isTbl { 345 if tbl.QTable.IsInfSchema { 346 name = sqlparser.String(tbl.QTable.Table) 347 } else { 348 name = sqlparser.String(tbl.QTable.Table.Name) 349 } 350 tableNameMap[name] = nil 351 } 352 return nil 353 }) 354 if err != nil { 355 return nil, err 356 } 357 var tableNames []string 358 for name := range tableNameMap { 359 tableNames = append(tableNames, name) 360 } 361 sort.Strings(tableNames) 362 return tableNames, nil 363 } 364 365 func transformUnionPlan(ctx *plancontext.PlanningContext, op *operators.Union, isRoot bool) (logicalPlan, error) { 366 var sources []logicalPlan 367 var err error 368 if op.Distinct { 369 sources, err = transformAndMerge(ctx, op) 370 if err != nil { 371 return nil, err 372 } 373 for _, source := range sources { 374 pushDistinct(source) 375 } 376 } else { 377 sources, err = transformAndMergeInOrder(ctx, op) 378 if err != nil { 379 return nil, err 380 } 381 } 382 var result logicalPlan 383 if len(sources) == 1 { 384 src := sources[0] 385 if rb, isRoute := src.(*routeGen4); isRoute && rb.isSingleShard() { 386 // if we have a single shard route, we don't need to do anything to make it distinct 387 // TODO 388 // rb.Select.SetLimit(op.limit) 389 // rb.Select.SetOrderBy(op.ordering) 390 return src, nil 391 } 392 result = src 393 } else { 394 if len(op.Ordering) > 0 { 395 return nil, vterrors.VT12001("ORDER BY on top of UNION") 396 } 397 result = &concatenateGen4{sources: sources} 398 } 399 if op.Distinct { 400 colls := getCollationsFor(ctx, op) 401 checkCols, err := getCheckColsForUnion(ctx, result, colls) 402 if err != nil { 403 return nil, err 404 } 405 return newDistinct(result, checkCols, isRoot), nil 406 } 407 return result, nil 408 409 } 410 411 func getWeightStringForSelectExpr(selectExpr sqlparser.SelectExpr) (*sqlparser.AliasedExpr, error) { 412 expr, isAliased := selectExpr.(*sqlparser.AliasedExpr) 413 if !isAliased { 414 return nil, vterrors.VT12001("get weight string expression for non-aliased expression") 415 } 416 return &sqlparser.AliasedExpr{Expr: weightStringFor(expr.Expr)}, nil 417 } 418 419 func getCheckColsForUnion(ctx *plancontext.PlanningContext, result logicalPlan, colls []collations.ID) ([]engine.CheckCol, error) { 420 checkCols := make([]engine.CheckCol, 0, len(colls)) 421 for i, coll := range colls { 422 checkCol := engine.CheckCol{Col: i, Collation: coll} 423 if coll != collations.Unknown { 424 checkCols = append(checkCols, checkCol) 425 continue 426 } 427 // We might need a weight string - let's push one 428 // `might` because we just don't know what type we are dealing with. 429 // If we encounter a numerical value, we don't need any weight_string values 430 newOffset, err := pushWeightStringForDistinct(ctx, result, i) 431 if err != nil { 432 return nil, err 433 } 434 checkCol.WsCol = &newOffset 435 checkCols = append(checkCols, checkCol) 436 } 437 return checkCols, nil 438 } 439 440 // pushWeightStringForDistinct adds a weight_string projection 441 func pushWeightStringForDistinct(ctx *plancontext.PlanningContext, plan logicalPlan, offset int) (newOffset int, err error) { 442 switch node := plan.(type) { 443 case *routeGen4: 444 allSelects := sqlparser.GetAllSelects(node.Select) 445 for _, sel := range allSelects { 446 expr, err := getWeightStringForSelectExpr(sel.SelectExprs[offset]) 447 if err != nil { 448 return 0, err 449 } 450 if i := checkIfAlreadyExists(expr, sel, ctx.SemTable); i != -1 { 451 return i, nil 452 } 453 sel.SelectExprs = append(sel.SelectExprs, expr) 454 newOffset = len(sel.SelectExprs) - 1 455 } 456 // we leave the responsibility of truncating to distinct 457 node.eroute.TruncateColumnCount = 0 458 case *concatenateGen4: 459 for _, source := range node.sources { 460 newOffset, err = pushWeightStringForDistinct(ctx, source, offset) 461 if err != nil { 462 return 0, err 463 } 464 } 465 node.noNeedToTypeCheck = append(node.noNeedToTypeCheck, newOffset) 466 case *joinGen4: 467 joinOffset := node.Cols[offset] 468 switch { 469 case joinOffset < 0: 470 offset, err = pushWeightStringForDistinct(ctx, node.Left, -(joinOffset + 1)) 471 offset = -(offset + 1) 472 case joinOffset > 0: 473 offset, err = pushWeightStringForDistinct(ctx, node.Right, joinOffset-1) 474 offset = offset + 1 475 default: 476 return 0, vterrors.VT13001("wrong column offset in join plan to push DISTINCT WEIGHT_STRING") 477 } 478 if err != nil { 479 return 0, err 480 } 481 newOffset = len(node.Cols) 482 node.Cols = append(node.Cols, offset) 483 default: 484 return 0, vterrors.VT13001(fmt.Sprintf("pushWeightStringForDistinct on %T", plan)) 485 } 486 return 487 } 488 489 func transformAndMerge(ctx *plancontext.PlanningContext, op *operators.Union) (sources []logicalPlan, err error) { 490 for _, source := range op.Sources { 491 // first we go over all the operator inputs and turn them into logical plans, 492 // including horizon planning 493 plan, err := transformToLogicalPlan(ctx, source, false) 494 if err != nil { 495 return nil, err 496 } 497 sources = append(sources, plan) 498 } 499 500 // next we'll go over all the plans from and check if any two can be merged. if they can, they are merged, 501 // and we continue checking for pairs of plans that can be merged into a single route 502 idx := 0 503 for idx < len(sources) { 504 keep := make([]bool, len(sources)) 505 srcA := sources[idx] 506 merged := false 507 for j, srcB := range sources { 508 if j <= idx { 509 continue 510 } 511 newPlan := mergeUnionLogicalPlans(ctx, srcA, srcB) 512 if newPlan != nil { 513 sources[idx] = newPlan 514 srcA = newPlan 515 merged = true 516 } else { 517 keep[j] = true 518 } 519 } 520 if !merged { 521 return sources, nil 522 } 523 var phase []logicalPlan 524 for i, source := range sources { 525 if keep[i] || i <= idx { 526 phase = append(phase, source) 527 } 528 } 529 idx++ 530 sources = phase 531 } 532 return sources, nil 533 } 534 535 func transformAndMergeInOrder(ctx *plancontext.PlanningContext, op *operators.Union) (sources []logicalPlan, err error) { 536 // We go over all the input operators and turn them into logical plans 537 for i, source := range op.Sources { 538 plan, err := transformToLogicalPlan(ctx, source, false) 539 if err != nil { 540 return nil, err 541 } 542 if i == 0 { 543 sources = append(sources, plan) 544 continue 545 } 546 547 // next we check if the last plan we produced can be merged with this new plan 548 last := sources[len(sources)-1] 549 newPlan := mergeUnionLogicalPlans(ctx, last, plan) 550 if newPlan != nil { 551 // if we could merge them, let's replace the last plan with this new merged one 552 sources[len(sources)-1] = newPlan 553 continue 554 } 555 // else we just add the new plan to the end of list 556 sources = append(sources, plan) 557 } 558 return sources, nil 559 } 560 561 func getCollationsFor(ctx *plancontext.PlanningContext, n *operators.Union) []collations.ID { 562 // TODO: coerce selects' select expressions' collations 563 var colls []collations.ID 564 565 sel, err := n.GetSelectFor(0) 566 if err != nil { 567 return nil 568 } 569 for _, expr := range sel.SelectExprs { 570 aliasedE, ok := expr.(*sqlparser.AliasedExpr) 571 if !ok { 572 return nil 573 } 574 typ := ctx.SemTable.CollationForExpr(aliasedE.Expr) 575 if typ == collations.Unknown { 576 if t, hasT := ctx.SemTable.ExprTypes[aliasedE.Expr]; hasT && sqltypes.IsNumber(t.Type) { 577 typ = collations.CollationBinaryID 578 } 579 } 580 colls = append(colls, typ) 581 } 582 return colls 583 } 584 585 func transformDerivedPlan(ctx *plancontext.PlanningContext, op *operators.Derived) (logicalPlan, error) { 586 // transforming the inner part of the derived table into a logical plan 587 // so that we can do horizon planning on the inner. If the logical plan 588 // we've produced is a Route, we set its Select.From field to be an aliased 589 // expression containing our derived table's inner select and the derived 590 // table's alias. 591 592 plan, err := transformToLogicalPlan(ctx, op.Source, false) 593 if err != nil { 594 return nil, err 595 } 596 597 plan, err = planHorizon(ctx, plan, op.Query, false) 598 if err != nil { 599 return nil, err 600 } 601 602 rb, isRoute := plan.(*routeGen4) 603 if !isRoute { 604 return &simpleProjection{ 605 logicalPlanCommon: newBuilderCommon(plan), 606 eSimpleProj: &engine.SimpleProjection{ 607 Cols: op.ColumnsOffset, 608 }, 609 }, nil 610 } 611 innerSelect := rb.Select 612 derivedTable := &sqlparser.DerivedTable{Select: innerSelect} 613 tblExpr := &sqlparser.AliasedTableExpr{ 614 Expr: derivedTable, 615 As: sqlparser.NewIdentifierCS(op.Alias), 616 Columns: op.ColumnAliases, 617 } 618 selectExprs := sqlparser.SelectExprs{} 619 for _, colName := range op.Columns { 620 selectExprs = append(selectExprs, &sqlparser.AliasedExpr{ 621 Expr: colName, 622 }) 623 } 624 rb.Select = &sqlparser.Select{ 625 From: []sqlparser.TableExpr{tblExpr}, 626 SelectExprs: selectExprs, 627 } 628 return plan, nil 629 } 630 631 type subQReplacer struct { 632 subqueryToReplace []*sqlparser.ExtractedSubquery 633 replaced bool 634 } 635 636 func (sqr *subQReplacer) replacer(cursor *sqlparser.Cursor) bool { 637 ext, ok := cursor.Node().(*sqlparser.ExtractedSubquery) 638 if !ok { 639 return true 640 } 641 for _, replaceByExpr := range sqr.subqueryToReplace { 642 // we are comparing the ArgNames in case the expressions have been cloned 643 if ext.GetArgName() == replaceByExpr.GetArgName() { 644 cursor.Replace(ext.Original) 645 sqr.replaced = true 646 return true 647 } 648 } 649 return true 650 } 651 652 func pushDistinct(plan logicalPlan) { 653 switch n := plan.(type) { 654 case *routeGen4: 655 n.Select.MakeDistinct() 656 case *concatenateGen4: 657 for _, source := range n.sources { 658 pushDistinct(source) 659 } 660 } 661 } 662 663 func mergeUnionLogicalPlans(ctx *plancontext.PlanningContext, left logicalPlan, right logicalPlan) logicalPlan { 664 lroute, ok := left.(*routeGen4) 665 if !ok { 666 return nil 667 } 668 rroute, ok := right.(*routeGen4) 669 if !ok { 670 return nil 671 } 672 673 if canMergeUnionPlans(ctx, lroute, rroute) { 674 lroute.Select = &sqlparser.Union{Left: lroute.Select, Distinct: false, Right: rroute.Select} 675 return mergeSystemTableInformation(lroute, rroute) 676 } 677 return nil 678 } 679 680 func canMergeUnionPlans(ctx *plancontext.PlanningContext, a, b *routeGen4) bool { 681 // this method should be close to tryMerge below. it does the same thing, but on logicalPlans instead of queryTrees 682 if a.eroute.Keyspace.Name != b.eroute.Keyspace.Name { 683 return false 684 } 685 switch a.eroute.Opcode { 686 case engine.Unsharded, engine.Reference: 687 return a.eroute.Opcode == b.eroute.Opcode 688 case engine.DBA: 689 return canSelectDBAMerge(a, b) 690 case engine.EqualUnique: 691 // Check if they target the same shard. 692 if b.eroute.Opcode == engine.EqualUnique && 693 a.eroute.Vindex == b.eroute.Vindex && 694 a.condition != nil && 695 b.condition != nil && 696 gen4ValuesEqual(ctx, []sqlparser.Expr{a.condition}, []sqlparser.Expr{b.condition}) { 697 return true 698 } 699 case engine.Scatter: 700 return b.eroute.Opcode == engine.Scatter 701 case engine.Next: 702 return false 703 } 704 return false 705 } 706 707 func canSelectDBAMerge(a, b *routeGen4) bool { 708 if a.eroute.Opcode != engine.DBA { 709 return false 710 } 711 if b.eroute.Opcode != engine.DBA { 712 return false 713 } 714 715 // safe to merge when any 1 table name or schema matches, since either the routing will match or either side would be throwing an error 716 // during run-time which we want to preserve. For example outer side has User in sys table schema and inner side has User and Main in sys table schema 717 // Inner might end up throwing an error at runtime, but if it doesn't then it is safe to merge. 718 for _, aExpr := range a.eroute.SysTableTableSchema { 719 for _, bExpr := range b.eroute.SysTableTableSchema { 720 if evalengine.FormatExpr(aExpr) == evalengine.FormatExpr(bExpr) { 721 return true 722 } 723 } 724 } 725 for _, aExpr := range a.eroute.SysTableTableName { 726 for _, bExpr := range b.eroute.SysTableTableName { 727 if evalengine.FormatExpr(aExpr) == evalengine.FormatExpr(bExpr) { 728 return true 729 } 730 } 731 } 732 733 // if either/both of the side does not have any routing information, then they can be merged. 734 return (len(a.eroute.SysTableTableSchema) == 0 && len(a.eroute.SysTableTableName) == 0) || 735 (len(b.eroute.SysTableTableSchema) == 0 && len(b.eroute.SysTableTableName) == 0) 736 } 737 738 func gen4ValuesEqual(ctx *plancontext.PlanningContext, a, b []sqlparser.Expr) bool { 739 if len(a) != len(b) { 740 return false 741 } 742 743 // TODO: check SemTable's columnEqualities for better plan 744 745 for i, aExpr := range a { 746 bExpr := b[i] 747 if !gen4ValEqual(ctx, aExpr, bExpr) { 748 return false 749 } 750 } 751 return true 752 } 753 754 func gen4ValEqual(ctx *plancontext.PlanningContext, a, b sqlparser.Expr) bool { 755 switch a := a.(type) { 756 case *sqlparser.ColName: 757 if b, ok := b.(*sqlparser.ColName); ok { 758 if !a.Name.Equal(b.Name) { 759 return false 760 } 761 762 return ctx.SemTable.DirectDeps(a) == ctx.SemTable.DirectDeps(b) 763 } 764 case sqlparser.Argument: 765 b, ok := b.(sqlparser.Argument) 766 if !ok { 767 return false 768 } 769 return a == b 770 case *sqlparser.Literal: 771 b, ok := b.(*sqlparser.Literal) 772 if !ok { 773 return false 774 } 775 switch a.Type { 776 case sqlparser.StrVal: 777 switch b.Type { 778 case sqlparser.StrVal: 779 return a.Val == b.Val 780 case sqlparser.HexVal: 781 return hexEqual(b, a) 782 } 783 case sqlparser.HexVal: 784 return hexEqual(a, b) 785 case sqlparser.IntVal: 786 if b.Type == (sqlparser.IntVal) { 787 return a.Val == b.Val 788 } 789 } 790 } 791 return false 792 }