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 }