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  }