github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/optbuilder/project.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package optbuilder
    12  
    13  import (
    14  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    15  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    16  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    20  )
    21  
    22  // constructProjectForScope constructs a projection if it will result in a
    23  // different set of columns than its input. Either way, it updates
    24  // projectionsScope.group with the output memo group ID.
    25  func (b *Builder) constructProjectForScope(inScope, projectionsScope *scope) {
    26  	// Don't add an unnecessary "pass through" project.
    27  	if projectionsScope.hasSameColumns(inScope) {
    28  		projectionsScope.expr = inScope.expr
    29  	} else {
    30  		projectionsScope.expr = b.constructProject(
    31  			inScope.expr.(memo.RelExpr),
    32  			append(projectionsScope.cols, projectionsScope.extraCols...),
    33  		)
    34  	}
    35  }
    36  
    37  func (b *Builder) constructProject(input memo.RelExpr, cols []scopeColumn) memo.RelExpr {
    38  	var passthrough opt.ColSet
    39  	projections := make(memo.ProjectionsExpr, 0, len(cols))
    40  
    41  	// Deduplicate the columns; we only need to project each column once.
    42  	colSet := opt.ColSet{}
    43  	for i := range cols {
    44  		id, scalar := cols[i].id, cols[i].scalar
    45  		if !colSet.Contains(id) {
    46  			if scalar == nil {
    47  				passthrough.Add(id)
    48  			} else {
    49  				projections = append(projections, b.factory.ConstructProjectionsItem(scalar, id))
    50  			}
    51  			colSet.Add(id)
    52  		}
    53  	}
    54  
    55  	return b.factory.ConstructProject(input, projections, passthrough)
    56  }
    57  
    58  // dropOrderingAndExtraCols removes the ordering in the scope and projects away
    59  // any extra columns.
    60  func (b *Builder) dropOrderingAndExtraCols(s *scope) {
    61  	s.ordering = nil
    62  	if len(s.extraCols) > 0 {
    63  		s.extraCols = nil
    64  		s.expr = b.constructProject(s.expr, s.cols)
    65  	}
    66  }
    67  
    68  // analyzeProjectionList analyzes the given list of SELECT clause expressions,
    69  // and adds the resulting aliases and typed expressions to outScope. See the
    70  // header comment for analyzeSelectList.
    71  func (b *Builder) analyzeProjectionList(
    72  	selects tree.SelectExprs, desiredTypes []*types.T, inScope, outScope *scope,
    73  ) {
    74  	// We need to save and restore the previous values of the replaceSRFs field
    75  	// and the field in semaCtx in case we are recursively called within a
    76  	// subquery context.
    77  	defer b.semaCtx.Properties.Restore(b.semaCtx.Properties)
    78  	defer func(replaceSRFs bool) { inScope.replaceSRFs = replaceSRFs }(inScope.replaceSRFs)
    79  
    80  	b.semaCtx.Properties.Require(exprKindSelect.String(), tree.RejectNestedGenerators)
    81  	inScope.context = exprKindSelect
    82  	inScope.replaceSRFs = true
    83  
    84  	b.analyzeSelectList(selects, desiredTypes, inScope, outScope)
    85  }
    86  
    87  // analyzeReturningList analyzes the given list of RETURNING clause expressions,
    88  // and adds the resulting aliases and typed expressions to outScope. See the
    89  // header comment for analyzeSelectList.
    90  func (b *Builder) analyzeReturningList(
    91  	returning tree.ReturningExprs, desiredTypes []*types.T, inScope, outScope *scope,
    92  ) {
    93  	// We need to save and restore the previous value of the field in
    94  	// semaCtx in case we are recursively called within a subquery
    95  	// context.
    96  	defer b.semaCtx.Properties.Restore(b.semaCtx.Properties)
    97  
    98  	// Ensure there are no special functions in the RETURNING clause.
    99  	b.semaCtx.Properties.Require(exprKindReturning.String(), tree.RejectSpecial)
   100  	inScope.context = exprKindReturning
   101  
   102  	b.analyzeSelectList(tree.SelectExprs(returning), desiredTypes, inScope, outScope)
   103  }
   104  
   105  // analyzeSelectList is a helper function used by analyzeProjectionList and
   106  // analyzeReturningList. It normalizes names, expands wildcards, resolves types,
   107  // and adds resulting columns to outScope. The desiredTypes slice contains
   108  // target type hints for the resulting expressions.
   109  //
   110  // As a side-effect, the appropriate scopes are updated with aggregations
   111  // (scope.groupby.aggs)
   112  func (b *Builder) analyzeSelectList(
   113  	selects tree.SelectExprs, desiredTypes []*types.T, inScope, outScope *scope,
   114  ) {
   115  	for i, e := range selects {
   116  		// Start with fast path, looking for simple column reference.
   117  		texpr := b.resolveColRef(e.Expr, inScope)
   118  		if texpr == nil {
   119  			// Fall back to slow path. Pre-normalize any VarName so the work is
   120  			// not done twice below.
   121  			if err := e.NormalizeTopLevelVarName(); err != nil {
   122  				panic(err)
   123  			}
   124  
   125  			// Special handling for "*", "<table>.*" and "(Expr).*".
   126  			if v, ok := e.Expr.(tree.VarName); ok {
   127  				switch v.(type) {
   128  				case tree.UnqualifiedStar, *tree.AllColumnsSelector, *tree.TupleStar:
   129  					if e.As != "" {
   130  						panic(pgerror.Newf(pgcode.Syntax,
   131  							"%q cannot be aliased", tree.ErrString(v)))
   132  					}
   133  
   134  					aliases, exprs := b.expandStar(e.Expr, inScope)
   135  					if outScope.cols == nil {
   136  						outScope.cols = make([]scopeColumn, 0, len(selects)+len(exprs)-1)
   137  					}
   138  					for j, e := range exprs {
   139  						b.addColumn(outScope, aliases[j], e)
   140  					}
   141  					continue
   142  				}
   143  			}
   144  
   145  			desired := types.Any
   146  			if i < len(desiredTypes) {
   147  				desired = desiredTypes[i]
   148  			}
   149  
   150  			texpr = inScope.resolveType(e.Expr, desired)
   151  		}
   152  
   153  		// Output column names should exactly match the original expression, so we
   154  		// have to determine the output column name before we perform type
   155  		// checking.
   156  		if outScope.cols == nil {
   157  			outScope.cols = make([]scopeColumn, 0, len(selects))
   158  		}
   159  		alias := b.getColName(e)
   160  		b.addColumn(outScope, alias, texpr)
   161  	}
   162  }
   163  
   164  // buildProjectionList builds a set of memo groups that represent the given
   165  // expressions in projectionsScope.
   166  //
   167  // See Builder.buildStmt for a description of the remaining input values.
   168  func (b *Builder) buildProjectionList(inScope *scope, projectionsScope *scope) {
   169  	for i := range projectionsScope.cols {
   170  		col := &projectionsScope.cols[i]
   171  		b.buildScalar(col.getExpr(), inScope, projectionsScope, col, nil)
   172  	}
   173  }
   174  
   175  // resolveColRef looks for the common case of a standalone column reference
   176  // expression, like this:
   177  //
   178  //   SELECT ..., c, ... FROM ...
   179  //
   180  // It resolves the column name to a scopeColumn and returns it as a TypedExpr.
   181  func (b *Builder) resolveColRef(e tree.Expr, inScope *scope) tree.TypedExpr {
   182  	unresolved, ok := e.(*tree.UnresolvedName)
   183  	if ok && !unresolved.Star && unresolved.NumParts == 1 {
   184  		colName := unresolved.Parts[0]
   185  		_, srcMeta, _, err := inScope.FindSourceProvidingColumn(b.ctx, tree.Name(colName))
   186  		if err != nil {
   187  			panic(err)
   188  		}
   189  		return srcMeta.(tree.TypedExpr)
   190  	}
   191  	return nil
   192  }
   193  
   194  // getColName returns the output column name for a projection expression.
   195  func (b *Builder) getColName(expr tree.SelectExpr) string {
   196  	s, err := tree.GetRenderColName(b.semaCtx.SearchPath, expr)
   197  	if err != nil {
   198  		panic(err)
   199  	}
   200  	return s
   201  }
   202  
   203  // finishBuildScalar completes construction of a new scalar expression. If
   204  // outScope is nil, then finishBuildScalar returns the result memo group, which
   205  // can be nested within the larger expression being built. If outScope is not
   206  // nil, then finishBuildScalar synthesizes a new output column in outScope with
   207  // the expression as its value.
   208  //
   209  // texpr     The given scalar expression. The expression is any scalar
   210  //           expression except for a bare variable or aggregate (those are
   211  //           handled separately in buildVariableProjection and
   212  //           buildFunction).
   213  // scalar    The memo expression that has already been built for the given
   214  //           typed expression.
   215  // outCol    The output column of the scalar which is being built. It can be
   216  //           nil if outScope is nil.
   217  //
   218  // See Builder.buildStmt for a description of the remaining input and return
   219  // values.
   220  func (b *Builder) finishBuildScalar(
   221  	texpr tree.TypedExpr, scalar opt.ScalarExpr, inScope, outScope *scope, outCol *scopeColumn,
   222  ) (out opt.ScalarExpr) {
   223  	if outScope == nil {
   224  		return scalar
   225  	}
   226  
   227  	// Avoid synthesizing a new column if possible.
   228  	if col := outScope.findExistingCol(texpr, false /* allowSideEffects */); col != nil && col != outCol {
   229  		outCol.id = col.id
   230  		outCol.scalar = scalar
   231  		return scalar
   232  	}
   233  
   234  	b.populateSynthesizedColumn(outCol, scalar)
   235  	return scalar
   236  }
   237  
   238  // finishBuildScalarRef constructs a reference to the given column. If outScope
   239  // is nil, then finishBuildScalarRef returns a Variable expression that refers
   240  // to the column. This expression can be nested within the larger expression
   241  // being constructed. If outScope is not nil, then finishBuildScalarRef adds the
   242  // column to outScope, either as a passthrough column (if it already exists in
   243  // the input scope), or a variable expression.
   244  //
   245  // col      Column containing the scalar expression that's been referenced.
   246  // outCol   The output column which is being built. It can be nil if outScope is
   247  //          nil.
   248  // colRefs  The set of columns referenced so far by the scalar expression being
   249  //          built. If not nil, it is updated with the ID of this column.
   250  //
   251  // See Builder.buildStmt for a description of the remaining input and return
   252  // values.
   253  func (b *Builder) finishBuildScalarRef(
   254  	col *scopeColumn, inScope, outScope *scope, outCol *scopeColumn, colRefs *opt.ColSet,
   255  ) (out opt.ScalarExpr) {
   256  	// Update the sets of column references and outer columns if needed.
   257  	if colRefs != nil {
   258  		colRefs.Add(col.id)
   259  	}
   260  
   261  	// Collect the outer columns of the current subquery, if any.
   262  	isOuterColumn := inScope == nil || inScope.isOuterColumn(col.id)
   263  	if isOuterColumn && b.subquery != nil {
   264  		b.subquery.outerCols.Add(col.id)
   265  	}
   266  
   267  	// If this is not a projection context, then wrap the column reference with
   268  	// a Variable expression that can be embedded in outer expression(s).
   269  	if outScope == nil {
   270  		return b.factory.ConstructVariable(col.id)
   271  	}
   272  
   273  	// Outer columns must be wrapped in a variable expression and assigned a new
   274  	// column id before projection.
   275  	if isOuterColumn {
   276  		// Avoid synthesizing a new column if possible.
   277  		existing := outScope.findExistingCol(col, false /* allowSideEffects */)
   278  		if existing == nil || existing == outCol {
   279  			if outCol.name == "" {
   280  				outCol.name = col.name
   281  			}
   282  			group := b.factory.ConstructVariable(col.id)
   283  			b.populateSynthesizedColumn(outCol, group)
   284  			return group
   285  		}
   286  
   287  		col = existing
   288  	}
   289  
   290  	// Project the column.
   291  	b.projectColumn(outCol, col)
   292  	return outCol.scalar
   293  }