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 }