github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/distsql_plan_window.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 sql
    12  
    13  import (
    14  	"strings"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/rowexec"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    20  	"github.com/cockroachdb/errors"
    21  )
    22  
    23  type windowPlanState struct {
    24  	// infos contains information about windowFuncHolders in the same order as
    25  	// they appear in n.funcs.
    26  	infos   []*windowFuncInfo
    27  	n       *windowNode
    28  	planCtx *PlanningCtx
    29  	plan    *PhysicalPlan
    30  }
    31  
    32  func createWindowPlanState(
    33  	n *windowNode, planCtx *PlanningCtx, plan *PhysicalPlan,
    34  ) *windowPlanState {
    35  	infos := make([]*windowFuncInfo, 0, len(n.funcs))
    36  	for _, holder := range n.funcs {
    37  		infos = append(infos, &windowFuncInfo{holder: holder})
    38  	}
    39  	return &windowPlanState{
    40  		infos:   infos,
    41  		n:       n,
    42  		planCtx: planCtx,
    43  		plan:    plan,
    44  	}
    45  }
    46  
    47  // windowFuncInfo contains runtime information about a window function.
    48  type windowFuncInfo struct {
    49  	holder *windowFuncHolder
    50  	// isProcessed indicates whether holder has already been processed. It is set
    51  	// to true when holder is included in the set of window functions to be
    52  	// processed by findUnprocessedWindowFnsWithSamePartition.
    53  	isProcessed bool
    54  }
    55  
    56  // findUnprocessedWindowFnsWithSamePartition finds a set of unprocessed window
    57  // functions that use the same partitioning and updates their isProcessed flag
    58  // accordingly. It returns the set of unprocessed window functions and indices
    59  // of the columns in their PARTITION BY clause.
    60  func (s *windowPlanState) findUnprocessedWindowFnsWithSamePartition() (
    61  	samePartitionFuncs []*windowFuncHolder,
    62  	partitionIdxs []uint32,
    63  ) {
    64  	windowFnToProcessIdx := -1
    65  	for windowFnIdx, windowFn := range s.infos {
    66  		if !windowFn.isProcessed {
    67  			windowFnToProcessIdx = windowFnIdx
    68  			break
    69  		}
    70  	}
    71  	if windowFnToProcessIdx == -1 {
    72  		panic("unexpected: no unprocessed window function")
    73  	}
    74  
    75  	windowFnToProcess := s.infos[windowFnToProcessIdx].holder
    76  	partitionIdxs = make([]uint32, len(windowFnToProcess.partitionIdxs))
    77  	for i, idx := range windowFnToProcess.partitionIdxs {
    78  		partitionIdxs[i] = uint32(idx)
    79  	}
    80  
    81  	samePartitionFuncs = make([]*windowFuncHolder, 0, len(s.infos)-windowFnToProcessIdx)
    82  	samePartitionFuncs = append(samePartitionFuncs, windowFnToProcess)
    83  	s.infos[windowFnToProcessIdx].isProcessed = true
    84  	for _, windowFn := range s.infos[windowFnToProcessIdx+1:] {
    85  		if windowFn.isProcessed {
    86  			continue
    87  		}
    88  		if windowFnToProcess.samePartition(windowFn.holder) {
    89  			samePartitionFuncs = append(samePartitionFuncs, windowFn.holder)
    90  			windowFn.isProcessed = true
    91  		}
    92  	}
    93  
    94  	return samePartitionFuncs, partitionIdxs
    95  }
    96  
    97  func (s *windowPlanState) createWindowFnSpec(
    98  	funcInProgress *windowFuncHolder,
    99  ) (execinfrapb.WindowerSpec_WindowFn, *types.T, error) {
   100  	for _, argIdx := range funcInProgress.argsIdxs {
   101  		if argIdx >= uint32(len(s.plan.ResultTypes)) {
   102  			return execinfrapb.WindowerSpec_WindowFn{}, nil, errors.Errorf("ColIdx out of range (%d)", argIdx)
   103  		}
   104  	}
   105  	// Figure out which built-in to compute.
   106  	funcStr := strings.ToUpper(funcInProgress.expr.Func.String())
   107  	funcSpec, err := rowexec.CreateWindowerSpecFunc(funcStr)
   108  	if err != nil {
   109  		return execinfrapb.WindowerSpec_WindowFn{}, nil, err
   110  	}
   111  	argTypes := make([]*types.T, len(funcInProgress.argsIdxs))
   112  	for i, argIdx := range funcInProgress.argsIdxs {
   113  		argTypes[i] = s.plan.ResultTypes[argIdx]
   114  	}
   115  	_, outputType, err := execinfrapb.GetWindowFunctionInfo(funcSpec, argTypes...)
   116  	if err != nil {
   117  		return execinfrapb.WindowerSpec_WindowFn{}, outputType, err
   118  	}
   119  	// Populating column ordering from ORDER BY clause of funcInProgress.
   120  	ordCols := make([]execinfrapb.Ordering_Column, 0, len(funcInProgress.columnOrdering))
   121  	for _, column := range funcInProgress.columnOrdering {
   122  		ordCols = append(ordCols, execinfrapb.Ordering_Column{
   123  			ColIdx: uint32(column.ColIdx),
   124  			// We need this -1 because encoding.Direction has extra value "_"
   125  			// as zeroth "entry" which its proto equivalent doesn't have.
   126  			Direction: execinfrapb.Ordering_Column_Direction(column.Direction - 1),
   127  		})
   128  	}
   129  	funcInProgressSpec := execinfrapb.WindowerSpec_WindowFn{
   130  		Func:         funcSpec,
   131  		ArgsIdxs:     funcInProgress.argsIdxs,
   132  		Ordering:     execinfrapb.Ordering{Columns: ordCols},
   133  		FilterColIdx: int32(funcInProgress.filterColIdx),
   134  		OutputColIdx: uint32(funcInProgress.outputColIdx),
   135  	}
   136  	if funcInProgress.frame != nil {
   137  		// funcInProgress has a custom window frame.
   138  		frameSpec := execinfrapb.WindowerSpec_Frame{}
   139  		if err := frameSpec.InitFromAST(funcInProgress.frame, s.planCtx.EvalContext()); err != nil {
   140  			return execinfrapb.WindowerSpec_WindowFn{}, outputType, err
   141  		}
   142  		funcInProgressSpec.Frame = &frameSpec
   143  	}
   144  
   145  	return funcInProgressSpec, outputType, nil
   146  }
   147  
   148  // addRenderingOrProjection checks whether any of the window functions' outputs
   149  // are used in another expression and, if they are, adds rendering to the plan.
   150  // If no rendering is required, it adds a projection to remove all columns that
   151  // were arguments to window functions or were used within OVER clauses.
   152  func (s *windowPlanState) addRenderingOrProjection() error {
   153  	// numWindowFuncsAsIs is the number of window functions output of which is
   154  	// used directly (i.e. simply as an output column). Note: the same window
   155  	// function might appear multiple times in the query, but its every
   156  	// occurrence is replaced by a different windowFuncHolder. For example, on
   157  	// query like 'SELECT avg(a) OVER (), avg(a) OVER () + 1 FROM t', only the
   158  	// first window function is used "as is."
   159  	numWindowFuncsAsIs := 0
   160  	for _, render := range s.n.windowRender {
   161  		if _, ok := render.(*windowFuncHolder); ok {
   162  			numWindowFuncsAsIs++
   163  		}
   164  	}
   165  	if numWindowFuncsAsIs == len(s.infos) {
   166  		// All window functions' outputs are used directly, so there is no
   167  		// rendering to do and simple projection is sufficient.
   168  		columns := make([]uint32, len(s.n.windowRender))
   169  		passedThruColIdx := uint32(0)
   170  		for i, render := range s.n.windowRender {
   171  			if render == nil {
   172  				columns[i] = passedThruColIdx
   173  				passedThruColIdx++
   174  			} else {
   175  				// We have done the type introspection above, so all non-nil renders
   176  				// are windowFuncHolders.
   177  				holder := render.(*windowFuncHolder)
   178  				columns[i] = uint32(holder.outputColIdx)
   179  			}
   180  		}
   181  		s.plan.AddProjection(columns)
   182  		return nil
   183  	}
   184  
   185  	// windowNode contains render expressions that might contain:
   186  	// 1) IndexedVars that refer to columns by their indices in the full table,
   187  	// 2) IndexedVars that replaced regular aggregates that are above
   188  	//    "windowing level."
   189  	// The mapping of both types IndexedVars is stored in s.n.colAndAggContainer.
   190  	renderExprs := make([]tree.TypedExpr, len(s.n.windowRender))
   191  	visitor := replaceWindowFuncsVisitor{
   192  		columnsMap: s.n.colAndAggContainer.idxMap,
   193  	}
   194  
   195  	// All passed through columns are contiguous and at the beginning of the
   196  	// output schema.
   197  	passedThruColIdx := 0
   198  	renderTypes := make([]*types.T, 0, len(s.n.windowRender))
   199  	for i, render := range s.n.windowRender {
   200  		if render != nil {
   201  			// render contains at least one reference to windowFuncHolder, so we need
   202  			// to walk over the render and replace all windowFuncHolders and (if found)
   203  			// IndexedVars using columnsMap and outputColIdx of windowFuncHolders.
   204  			renderExprs[i] = visitor.replace(render)
   205  		} else {
   206  			// render is nil meaning that a column is being passed through.
   207  			renderExprs[i] = tree.NewTypedOrdinalReference(passedThruColIdx, s.plan.ResultTypes[passedThruColIdx])
   208  			passedThruColIdx++
   209  		}
   210  		outputType := renderExprs[i].ResolvedType()
   211  		renderTypes = append(renderTypes, outputType)
   212  	}
   213  	return s.plan.AddRendering(renderExprs, s.planCtx, s.plan.PlanToStreamColMap, renderTypes)
   214  }
   215  
   216  // replaceWindowFuncsVisitor is used to populate render expressions containing
   217  // the results of window functions. It recurses into all expressions except for
   218  // windowFuncHolders (which are replaced by the indices to the corresponding
   219  // output columns) and IndexedVars (which are replaced using columnsMap).
   220  type replaceWindowFuncsVisitor struct {
   221  	columnsMap map[int]int
   222  }
   223  
   224  var _ tree.Visitor = &replaceWindowFuncsVisitor{}
   225  
   226  // VisitPre satisfies the Visitor interface.
   227  func (v *replaceWindowFuncsVisitor) VisitPre(expr tree.Expr) (recurse bool, newExpr tree.Expr) {
   228  	switch t := expr.(type) {
   229  	case *windowFuncHolder:
   230  		return false, tree.NewTypedOrdinalReference(t.outputColIdx, t.ResolvedType())
   231  	case *tree.IndexedVar:
   232  		return false, tree.NewTypedOrdinalReference(v.columnsMap[t.Idx], t.ResolvedType())
   233  	}
   234  	return true, expr
   235  }
   236  
   237  // VisitPost satisfies the Visitor interface.
   238  func (v *replaceWindowFuncsVisitor) VisitPost(expr tree.Expr) tree.Expr {
   239  	return expr
   240  }
   241  
   242  func (v *replaceWindowFuncsVisitor) replace(typedExpr tree.TypedExpr) tree.TypedExpr {
   243  	expr, _ := tree.WalkExpr(v, typedExpr)
   244  	return expr.(tree.TypedExpr)
   245  }