github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/optbuilder/srfs.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  	"context"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    20  	"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
    21  	"github.com/cockroachdb/errors"
    22  )
    23  
    24  // srf represents an srf expression in an expression tree
    25  // after it has been type-checked and added to the memo.
    26  type srf struct {
    27  	// The resolved function expression.
    28  	*tree.FuncExpr
    29  
    30  	// cols contains the output columns of the srf.
    31  	cols []scopeColumn
    32  
    33  	// fn is the top level function expression of the srf.
    34  	fn opt.ScalarExpr
    35  }
    36  
    37  // Walk is part of the tree.Expr interface.
    38  func (s *srf) Walk(v tree.Visitor) tree.Expr {
    39  	return s
    40  }
    41  
    42  // TypeCheck is part of the tree.Expr interface.
    43  func (s *srf) TypeCheck(
    44  	_ context.Context, ctx *tree.SemaContext, desired *types.T,
    45  ) (tree.TypedExpr, error) {
    46  	if ctx.Properties.Derived.SeenGenerator {
    47  		// This error happens if this srf struct is nested inside a raw srf that
    48  		// has not yet been replaced. This is possible since scope.replaceSRF first
    49  		// calls f.Walk(s) on the external raw srf, which replaces any internal
    50  		// raw srfs with srf structs. The next call to TypeCheck on the external
    51  		// raw srf triggers this error.
    52  		return nil, unimplemented.NewWithIssuef(26234, "nested set-returning functions")
    53  	}
    54  
    55  	return s, nil
    56  }
    57  
    58  // Eval is part of the tree.TypedExpr interface.
    59  func (s *srf) Eval(_ *tree.EvalContext) (tree.Datum, error) {
    60  	panic(errors.AssertionFailedf("srf must be replaced before evaluation"))
    61  }
    62  
    63  var _ tree.Expr = &srf{}
    64  var _ tree.TypedExpr = &srf{}
    65  
    66  // buildZip builds a set of memo groups which represent a functional zip over
    67  // the given expressions.
    68  //
    69  // Reminder, for context: the functional zip over iterators a,b,c
    70  // returns tuples of values from a,b,c picked "simultaneously". NULLs
    71  // are used when an iterator is "shorter" than another. For example:
    72  //
    73  //    zip([1,2,3], ['a','b']) = [(1,'a'), (2,'b'), (3, null)]
    74  //
    75  func (b *Builder) buildZip(exprs tree.Exprs, inScope *scope) (outScope *scope) {
    76  	outScope = inScope.push()
    77  
    78  	// We need to save and restore the previous value of the field in
    79  	// semaCtx in case we are recursively called within a subquery
    80  	// context.
    81  	defer b.semaCtx.Properties.Restore(b.semaCtx.Properties)
    82  	b.semaCtx.Properties.Require(exprKindFrom.String(),
    83  		tree.RejectAggregates|tree.RejectWindowApplications|tree.RejectNestedGenerators)
    84  	inScope.context = exprKindFrom
    85  
    86  	// Build each of the provided expressions.
    87  	zip := make(memo.ZipExpr, len(exprs))
    88  	for i, expr := range exprs {
    89  		// Output column names should exactly match the original expression, so we
    90  		// have to determine the output column name before we perform type
    91  		// checking. However, the alias may be overridden later below if the expression
    92  		// is a function and specifically defines a return label.
    93  		_, alias, err := tree.ComputeColNameInternal(b.semaCtx.SearchPath, expr)
    94  		if err != nil {
    95  			panic(err)
    96  		}
    97  		texpr := inScope.resolveType(expr, types.Any)
    98  
    99  		var def *tree.FunctionDefinition
   100  		if funcExpr, ok := texpr.(*tree.FuncExpr); ok {
   101  			if def, err = funcExpr.Func.Resolve(b.semaCtx.SearchPath); err != nil {
   102  				panic(err)
   103  			}
   104  		}
   105  
   106  		var outCol *scopeColumn
   107  		startCols := len(outScope.cols)
   108  		if def == nil || def.Class != tree.GeneratorClass || b.shouldCreateDefaultColumn(texpr) {
   109  
   110  			if def != nil && len(def.ReturnLabels) > 0 {
   111  				// Override the computed alias with the one defined in the ReturnLabels. This
   112  				// satisfies a Postgres quirk where some json functions use different labels
   113  				// when used in a from clause.
   114  				alias = def.ReturnLabels[0]
   115  			}
   116  			outCol = b.addColumn(outScope, alias, texpr)
   117  		}
   118  
   119  		scalar := b.buildScalar(texpr, inScope, outScope, outCol, nil)
   120  		cols := make(opt.ColList, len(outScope.cols)-startCols)
   121  		for j := startCols; j < len(outScope.cols); j++ {
   122  			cols[j-startCols] = outScope.cols[j].id
   123  		}
   124  		zip[i] = b.factory.ConstructZipItem(scalar, cols)
   125  	}
   126  
   127  	// Construct the zip as a ProjectSet with empty input.
   128  	input := b.factory.ConstructValues(memo.ScalarListWithEmptyTuple, &memo.ValuesPrivate{
   129  		Cols: opt.ColList{},
   130  		ID:   b.factory.Metadata().NextUniqueID(),
   131  	})
   132  	outScope.expr = b.factory.ConstructProjectSet(input, zip)
   133  	if len(outScope.cols) == 1 {
   134  		outScope.singleSRFColumn = true
   135  	}
   136  	return outScope
   137  }
   138  
   139  // finishBuildGeneratorFunction finishes building a set-generating function
   140  // (SRF) such as generate_series() or unnest(). It synthesizes new columns in
   141  // outScope for each of the SRF's output columns.
   142  func (b *Builder) finishBuildGeneratorFunction(
   143  	f *tree.FuncExpr, fn opt.ScalarExpr, inScope, outScope *scope, outCol *scopeColumn,
   144  ) (out opt.ScalarExpr) {
   145  	// Add scope columns.
   146  	if outCol != nil {
   147  		// Single-column return type.
   148  		b.populateSynthesizedColumn(outCol, fn)
   149  	} else {
   150  		// Multi-column return type. Use the tuple labels in the SRF's return type
   151  		// as column aliases.
   152  		typ := f.ResolvedType()
   153  		for i := range typ.TupleContents() {
   154  			b.synthesizeColumn(outScope, typ.TupleLabels()[i], typ.TupleContents()[i], nil, fn)
   155  		}
   156  	}
   157  
   158  	return fn
   159  }
   160  
   161  // buildProjectSet builds a ProjectSet, which is a lateral cross join
   162  // between the given input expression and a functional zip constructed from the
   163  // given srfs.
   164  //
   165  // This function is called at most once per SELECT clause, and updates
   166  // inScope.expr if at least one SRF was discovered in the SELECT list. The
   167  // ProjectSet is necessary in case some of the SRFs depend on the input.
   168  // For example, consider this query:
   169  //
   170  //   SELECT generate_series(t.a, t.a + 1) FROM t
   171  //
   172  // In this case, the inputs to generate_series depend on table t, so during
   173  // execution, generate_series will be called once for each row of t.
   174  func (b *Builder) buildProjectSet(inScope *scope) {
   175  	if len(inScope.srfs) == 0 {
   176  		return
   177  	}
   178  
   179  	// Get the output columns and function expressions of the zip.
   180  	zip := make(memo.ZipExpr, len(inScope.srfs))
   181  	for i, srf := range inScope.srfs {
   182  		cols := make(opt.ColList, len(srf.cols))
   183  		for j := range srf.cols {
   184  			cols[j] = srf.cols[j].id
   185  		}
   186  		zip[i] = b.factory.ConstructZipItem(srf.fn, cols)
   187  	}
   188  
   189  	inScope.expr = b.factory.ConstructProjectSet(inScope.expr, zip)
   190  }