vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/projection_pushing.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 planbuilder
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"vitess.io/vitess/go/vt/sqlparser"
    23  	"vitess.io/vitess/go/vt/vterrors"
    24  	"vitess.io/vitess/go/vt/vtgate/engine"
    25  	"vitess.io/vitess/go/vt/vtgate/planbuilder/operators"
    26  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    27  	"vitess.io/vitess/go/vt/vtgate/semantics"
    28  )
    29  
    30  // pushProjection pushes a projection to the plan.
    31  func pushProjection(
    32  	ctx *plancontext.PlanningContext,
    33  	expr *sqlparser.AliasedExpr,
    34  	plan logicalPlan,
    35  	inner, reuseCol, hasAggregation bool,
    36  ) (offset int, added bool, err error) {
    37  	switch node := plan.(type) {
    38  	case *limit, *projection, *pulloutSubquery, *distinct, *filter:
    39  		// All of these either push to the single source, or push to the LHS
    40  		src := node.Inputs()[0]
    41  		return pushProjection(ctx, expr, src, inner, reuseCol, hasAggregation)
    42  	case *routeGen4:
    43  		return addExpressionToRoute(ctx, node, expr, reuseCol)
    44  	case *hashJoin:
    45  		return pushProjectionIntoHashJoin(ctx, expr, node, reuseCol, inner, hasAggregation)
    46  	case *joinGen4:
    47  		return pushProjectionIntoJoin(ctx, expr, node, reuseCol, inner, hasAggregation)
    48  	case *simpleProjection:
    49  		return pushProjectionIntoSimpleProj(ctx, expr, node, inner, hasAggregation, reuseCol)
    50  	case *orderedAggregate:
    51  		return pushProjectionIntoOA(ctx, expr, node, inner, hasAggregation)
    52  	case *vindexFunc:
    53  		return pushProjectionIntoVindexFunc(node, expr, reuseCol)
    54  	case *semiJoin:
    55  		return pushProjectionIntoSemiJoin(ctx, expr, reuseCol, node, inner, hasAggregation)
    56  	case *concatenateGen4:
    57  		return pushProjectionIntoConcatenate(ctx, expr, hasAggregation, node, inner, reuseCol)
    58  	default:
    59  		return 0, false, vterrors.VT13001(fmt.Sprintf("push projection does not yet support: %T", node))
    60  	}
    61  }
    62  
    63  func pushProjectionIntoVindexFunc(node *vindexFunc, expr *sqlparser.AliasedExpr, reuseCol bool) (int, bool, error) {
    64  	colsBefore := len(node.eVindexFunc.Cols)
    65  	i, err := node.SupplyProjection(expr, reuseCol)
    66  	if err != nil {
    67  		return 0, false, err
    68  	}
    69  	return i /* col added */, len(node.eVindexFunc.Cols) > colsBefore, nil
    70  }
    71  
    72  func pushProjectionIntoConcatenate(ctx *plancontext.PlanningContext, expr *sqlparser.AliasedExpr, hasAggregation bool, node *concatenateGen4, inner bool, reuseCol bool) (int, bool, error) {
    73  	if hasAggregation {
    74  		return 0, false, vterrors.VT12001("aggregation on UNIONs")
    75  	}
    76  	offset, added, err := pushProjection(ctx, expr, node.sources[0], inner, reuseCol, hasAggregation)
    77  	if err != nil {
    78  		return 0, false, err
    79  	}
    80  	if added && ctx.SemTable.DirectDeps(expr.Expr).NonEmpty() {
    81  		return 0, false, vterrors.VT13001(fmt.Sprintf("pushing projection %v on concatenate should reference an existing column", sqlparser.String(expr)))
    82  	}
    83  	if added {
    84  		for _, source := range node.sources[1:] {
    85  			_, _, err := pushProjection(ctx, expr, source, inner, reuseCol, hasAggregation)
    86  			if err != nil {
    87  				return 0, false, err
    88  			}
    89  		}
    90  	}
    91  	return offset, added, nil
    92  }
    93  
    94  func pushProjectionIntoSemiJoin(
    95  	ctx *plancontext.PlanningContext,
    96  	expr *sqlparser.AliasedExpr,
    97  	reuseCol bool,
    98  	node *semiJoin,
    99  	inner, hasAggregation bool,
   100  ) (int, bool, error) {
   101  	passDownReuseCol := reuseCol
   102  	if !reuseCol {
   103  		passDownReuseCol = expr.As.IsEmpty()
   104  	}
   105  	offset, added, err := pushProjection(ctx, expr, node.lhs, inner, passDownReuseCol, hasAggregation)
   106  	if err != nil {
   107  		return 0, false, err
   108  	}
   109  	column := -(offset + 1)
   110  	if reuseCol && !added {
   111  		for idx, col := range node.cols {
   112  			if column == col {
   113  				return idx, false, nil
   114  			}
   115  		}
   116  	}
   117  	node.cols = append(node.cols, column)
   118  	return len(node.cols) - 1, true, nil
   119  }
   120  
   121  func pushProjectionIntoOA(ctx *plancontext.PlanningContext, expr *sqlparser.AliasedExpr, node *orderedAggregate, inner, hasAggregation bool) (int, bool, error) {
   122  	colName, isColName := expr.Expr.(*sqlparser.ColName)
   123  	for _, aggregate := range node.aggregates {
   124  		if ctx.SemTable.EqualsExpr(aggregate.Expr, expr.Expr) {
   125  			return aggregate.Col, false, nil
   126  		}
   127  		if isColName && colName.Name.EqualString(aggregate.Alias) {
   128  			return aggregate.Col, false, nil
   129  		}
   130  	}
   131  	for _, key := range node.groupByKeys {
   132  		if ctx.SemTable.EqualsExpr(key.Expr, expr.Expr) {
   133  			return key.KeyCol, false, nil
   134  		}
   135  	}
   136  	offset, _, err := pushProjection(ctx, expr, node.input, inner, true, hasAggregation)
   137  	if err != nil {
   138  		return 0, false, err
   139  	}
   140  	node.aggregates = append(node.aggregates, &engine.AggregateParams{
   141  		Opcode:   engine.AggregateRandom,
   142  		Col:      offset,
   143  		Alias:    expr.ColumnName(),
   144  		Expr:     expr.Expr,
   145  		Original: expr,
   146  	})
   147  	return offset, true, nil
   148  }
   149  
   150  func pushProjectionIntoSimpleProj(
   151  	ctx *plancontext.PlanningContext,
   152  	expr *sqlparser.AliasedExpr,
   153  	node *simpleProjection,
   154  	inner, hasAggregation, reuseCol bool,
   155  ) (int, bool, error) {
   156  	offset, _, err := pushProjection(ctx, expr, node.input, inner, true, hasAggregation)
   157  	if err != nil {
   158  		return 0, false, err
   159  	}
   160  	for i, value := range node.eSimpleProj.Cols {
   161  		// we return early if we already have the column in the simple projection's
   162  		// output list so we do not add it again.
   163  		if reuseCol && value == offset {
   164  			return i, false, nil
   165  		}
   166  	}
   167  	node.eSimpleProj.Cols = append(node.eSimpleProj.Cols, offset)
   168  	return len(node.eSimpleProj.Cols) - 1, true, nil
   169  }
   170  
   171  func pushProjectionIntoJoin(
   172  	ctx *plancontext.PlanningContext,
   173  	expr *sqlparser.AliasedExpr,
   174  	node *joinGen4,
   175  	reuseCol, inner, hasAggregation bool,
   176  ) (int, bool, error) {
   177  	lhsSolves := node.Left.ContainsTables()
   178  	rhsSolves := node.Right.ContainsTables()
   179  	deps := ctx.SemTable.RecursiveDeps(expr.Expr)
   180  	var column int
   181  	var appended bool
   182  	passDownReuseCol := reuseCol
   183  	if !reuseCol {
   184  		passDownReuseCol = expr.As.IsEmpty()
   185  	}
   186  	switch {
   187  	case deps.IsSolvedBy(lhsSolves):
   188  		offset, added, err := pushProjection(ctx, expr, node.Left, inner, passDownReuseCol, hasAggregation)
   189  		if err != nil {
   190  			return 0, false, err
   191  		}
   192  		column = -(offset + 1)
   193  		appended = added
   194  	case deps.IsSolvedBy(rhsSolves):
   195  		offset, added, err := pushProjection(ctx, expr, node.Right, inner && node.Opcode != engine.LeftJoin, passDownReuseCol, hasAggregation)
   196  		if err != nil {
   197  			return 0, false, err
   198  		}
   199  		column = offset + 1
   200  		appended = added
   201  	default:
   202  		// if an expression has aggregation, then it should not be split up and pushed to both sides,
   203  		// for example an expression like count(*) will have dependencies on both sides, but we should not push it
   204  		// instead we should return an error
   205  		if hasAggregation {
   206  			return 0, false, vterrors.VT12001("cross-shard query with aggregates")
   207  		}
   208  		// now we break the expression into left and right side dependencies and rewrite the left ones to bind variables
   209  		bvName, cols, rewrittenExpr, err := operators.BreakExpressionInLHSandRHS(ctx, expr.Expr, lhsSolves)
   210  		if err != nil {
   211  			return 0, false, err
   212  		}
   213  		// go over all the columns coming from the left side of the tree and push them down. While at it, also update the bind variable map.
   214  		// It is okay to reuse the columns on the left side since
   215  		// the final expression which will be selected will be pushed into the right side.
   216  		for i, col := range cols {
   217  			colOffset, _, err := pushProjection(ctx, &sqlparser.AliasedExpr{Expr: col}, node.Left, inner, true, false)
   218  			if err != nil {
   219  				return 0, false, err
   220  			}
   221  			node.Vars[bvName[i]] = colOffset
   222  		}
   223  		// push the rewritten expression on the right side of the tree. Here we should take care whether we want to reuse the expression or not.
   224  		expr.Expr = rewrittenExpr
   225  		offset, added, err := pushProjection(ctx, expr, node.Right, inner && node.Opcode != engine.LeftJoin, passDownReuseCol, false)
   226  		if err != nil {
   227  			return 0, false, err
   228  		}
   229  		column = offset + 1
   230  		appended = added
   231  	}
   232  	if reuseCol && !appended {
   233  		for idx, col := range node.Cols {
   234  			if column == col {
   235  				return idx, false, nil
   236  			}
   237  		}
   238  		// the column was not appended to either child, but we could not find it in out cols list,
   239  		// so we'll still add it
   240  	}
   241  	node.Cols = append(node.Cols, column)
   242  	return len(node.Cols) - 1, true, nil
   243  }
   244  
   245  func pushProjectionIntoHashJoin(
   246  	ctx *plancontext.PlanningContext,
   247  	expr *sqlparser.AliasedExpr,
   248  	node *hashJoin,
   249  	reuseCol, inner, hasAggregation bool,
   250  ) (int, bool, error) {
   251  	lhsSolves := node.Left.ContainsTables()
   252  	rhsSolves := node.Right.ContainsTables()
   253  	deps := ctx.SemTable.RecursiveDeps(expr.Expr)
   254  	var column int
   255  	var appended bool
   256  	passDownReuseCol := reuseCol
   257  	if !reuseCol {
   258  		passDownReuseCol = expr.As.IsEmpty()
   259  	}
   260  	switch {
   261  	case deps.IsSolvedBy(lhsSolves):
   262  		offset, added, err := pushProjection(ctx, expr, node.Left, inner, passDownReuseCol, hasAggregation)
   263  		if err != nil {
   264  			return 0, false, err
   265  		}
   266  		column = -(offset + 1)
   267  		appended = added
   268  	case deps.IsSolvedBy(rhsSolves):
   269  		offset, added, err := pushProjection(ctx, expr, node.Right, inner && node.Opcode != engine.LeftJoin, passDownReuseCol, hasAggregation)
   270  		if err != nil {
   271  			return 0, false, err
   272  		}
   273  		column = offset + 1
   274  		appended = added
   275  	default:
   276  		// if an expression has aggregation, then it should not be split up and pushed to both sides,
   277  		// for example an expression like count(*) will have dependencies on both sides, but we should not push it
   278  		// instead we should return an error
   279  		if hasAggregation {
   280  			return 0, false, vterrors.VT12001("cross-shard query with aggregates")
   281  		}
   282  		return 0, false, vterrors.VT12001("hash join with projection from both sides of the join")
   283  	}
   284  	if reuseCol && !appended {
   285  		for idx, col := range node.Cols {
   286  			if column == col {
   287  				return idx, false, nil
   288  			}
   289  		}
   290  		// the column was not appended to either child, but we could not find it in out cols list,
   291  		// so we'll still add it
   292  	}
   293  	node.Cols = append(node.Cols, column)
   294  	return len(node.Cols) - 1, true, nil
   295  }
   296  
   297  func addExpressionToRoute(ctx *plancontext.PlanningContext, rb *routeGen4, expr *sqlparser.AliasedExpr, reuseCol bool) (int, bool, error) {
   298  	if reuseCol {
   299  		if i := checkIfAlreadyExists(expr, rb.Select, ctx.SemTable); i != -1 {
   300  			return i, false, nil
   301  		}
   302  	}
   303  	sqlparser.RemoveKeyspaceFromColName(expr.Expr)
   304  	sel, isSel := rb.Select.(*sqlparser.Select)
   305  	if !isSel {
   306  		return 0, false, vterrors.VT12001(fmt.Sprintf("pushing projection '%s' on %T", sqlparser.String(expr), rb.Select))
   307  	}
   308  
   309  	if ctx.RewriteDerivedExpr {
   310  		// if we are trying to push a projection that belongs to a DerivedTable
   311  		// we rewrite that expression, so it matches the column name used inside
   312  		// that derived table.
   313  		err := rewriteProjectionOfDerivedTable(expr, ctx.SemTable)
   314  		if err != nil {
   315  			return 0, false, err
   316  		}
   317  	}
   318  
   319  	offset := len(sel.SelectExprs)
   320  	sel.SelectExprs = append(sel.SelectExprs, expr)
   321  	return offset, true, nil
   322  }
   323  
   324  func rewriteProjectionOfDerivedTable(expr *sqlparser.AliasedExpr, semTable *semantics.SemTable) error {
   325  	ti, err := semTable.TableInfoForExpr(expr.Expr)
   326  	if err != nil && err != semantics.ErrNotSingleTable {
   327  		return err
   328  	}
   329  	_, isDerivedTable := ti.(*semantics.DerivedTable)
   330  	if isDerivedTable {
   331  		expr.Expr = semantics.RewriteDerivedTableExpression(expr.Expr, ti)
   332  	}
   333  	return nil
   334  }