github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/optbuilder/with.go (about) 1 // Copyright 2019 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/memo" 15 "github.com/cockroachdb/cockroach/pkg/sql/opt/props" 16 "github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical" 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 20 "github.com/cockroachdb/errors" 21 ) 22 23 func (b *Builder) processWiths( 24 with *tree.With, inScope *scope, buildStmt func(inScope *scope) *scope, 25 ) *scope { 26 inScope = b.buildCTEs(with, inScope) 27 prevAtRoot := inScope.atRoot 28 inScope.atRoot = false 29 outScope := buildStmt(inScope) 30 inScope.atRoot = prevAtRoot 31 return outScope 32 } 33 34 func (b *Builder) buildCTE( 35 cte *tree.CTE, inScope *scope, isRecursive bool, 36 ) (memo.RelExpr, physical.Presentation) { 37 if !isRecursive { 38 cteScope := b.buildStmt(cte.Stmt, nil /* desiredTypes */, inScope) 39 cteScope.removeHiddenCols() 40 b.dropOrderingAndExtraCols(cteScope) 41 return cteScope.expr, b.getCTECols(cteScope, cte.Name) 42 } 43 44 // WITH RECURSIVE queries are always of the form: 45 // 46 // WITH RECURSIVE name(cols) AS ( 47 // initial_query 48 // UNION ALL 49 // recursive_query 50 // ) 51 // 52 // Recursive CTE evaluation (paraphrased from postgres docs): 53 // 1. Evaluate the initial query; emit the results and also save them in 54 // a "working" table. 55 // 2. So long as the working table is not empty: 56 // * evaluate the recursive query, substituting the current contents of 57 // the working table for the recursive self-reference. 58 // * emit all resulting rows, and save them as the next iteration's 59 // working table. 60 // 61 // Note however, that a non-recursive CTE can be used even when RECURSIVE is 62 // specified (particularly useful when there are multiple CTEs defined). 63 // Handling this while having decent error messages is tricky. 64 65 // Generate an id for the recursive CTE reference. This is the id through 66 // which the recursive expression refers to the current working table 67 // (via WithScan). 68 withID := b.factory.Memo().NextWithID() 69 70 // cteScope allows recursive references to this CTE. 71 cteScope := inScope.push() 72 cteSrc := &cteSource{ 73 id: withID, 74 name: cte.Name, 75 } 76 cteScope.ctes = map[string]*cteSource{cte.Name.Alias.String(): cteSrc} 77 78 initial, recursive, isUnionAll, ok := b.splitRecursiveCTE(cte.Stmt) 79 // We don't currently support the UNION form (only UNION ALL). 80 if !ok || !isUnionAll { 81 // Build this as a non-recursive CTE, but throw a proper error message if it 82 // does have a recursive reference. 83 cteSrc.onRef = func() { 84 if !ok { 85 panic(pgerror.Newf( 86 pgcode.Syntax, 87 "recursive query %q does not have the form non-recursive-term UNION ALL recursive-term", 88 cte.Name.Alias, 89 )) 90 } else { 91 panic(unimplementedWithIssueDetailf( 92 46642, "", 93 "recursive query %q uses UNION which is not implemented (only UNION ALL is supported)", 94 cte.Name.Alias, 95 )) 96 } 97 } 98 return b.buildCTE(cte, cteScope, false /* recursive */) 99 } 100 101 // Set up an error if the initial part has a recursive reference. 102 cteSrc.onRef = func() { 103 panic(pgerror.Newf( 104 pgcode.Syntax, 105 "recursive reference to query %q must not appear within its non-recursive term", 106 cte.Name.Alias, 107 )) 108 } 109 // If the initial statement contains CTEs, we don't want the Withs hoisted 110 // above the recursive CTE. 111 b.pushWithFrame() 112 initialScope := b.buildStmt(initial, nil /* desiredTypes */, cteScope) 113 b.popWithFrame(initialScope) 114 115 initialScope.removeHiddenCols() 116 b.dropOrderingAndExtraCols(initialScope) 117 118 // The properties of the binding are tricky: the recursive expression is 119 // invoked repeatedly and these must hold each time. We can't use the initial 120 // expression's properties directly, as those only hold the first time the 121 // recursive query is executed. We can't really say too much about what the 122 // working table contains, except that it has at least one row (the recursive 123 // query is never invoked with an empty working table). 124 bindingProps := &props.Relational{} 125 bindingProps.OutputCols = initialScope.colSet() 126 bindingProps.Cardinality = props.AnyCardinality.AtLeast(props.OneCardinality) 127 // We don't really know the input row count, except for the first time we run 128 // the recursive query. We don't have anything better though. 129 bindingProps.Stats.RowCount = initialScope.expr.Relational().Stats.RowCount 130 cteSrc.bindingProps = bindingProps 131 132 cteSrc.cols = b.getCTECols(initialScope, cte.Name) 133 134 outScope := inScope.push() 135 136 initialTypes := initialScope.makeColumnTypes() 137 138 // Synthesize new output columns (because they contain values from both the 139 // initial and the recursive relations). These columns will also be used to 140 // refer to the working table (from the recursive query); we can't use the 141 // initial columns directly because they might contain duplicate IDs (e.g. 142 // consider initial query SELECT 0, 0). 143 for i, c := range cteSrc.cols { 144 newCol := b.synthesizeColumn(outScope, c.Alias, initialTypes[i], nil /* expr */, nil /* scalar */) 145 cteSrc.cols[i].ID = newCol.id 146 } 147 148 // We want to check if the recursive query is actually recursive. This is for 149 // annoying cases like `SELECT 1 UNION ALL SELECT 2`. 150 numRefs := 0 151 cteSrc.onRef = func() { 152 numRefs++ 153 } 154 155 // If the recursive statement contains CTEs, we don't want the Withs hoisted 156 // above the recursive CTE. 157 b.pushWithFrame() 158 recursiveScope := b.buildStmt(recursive, initialTypes /* desiredTypes */, cteScope) 159 b.popWithFrame(recursiveScope) 160 161 if numRefs == 0 { 162 // Build this as a non-recursive CTE. 163 cteScope := b.buildSetOp(tree.UnionOp, false /* all */, inScope, initialScope, recursiveScope) 164 return cteScope.expr, b.getCTECols(cteScope, cte.Name) 165 } 166 167 if numRefs != 1 { 168 // We disallow multiple recursive references for consistency with postgres. 169 panic(pgerror.Newf( 170 pgcode.Syntax, 171 "recursive reference to query %q must not appear more than once", 172 cte.Name.Alias, 173 )) 174 } 175 176 recursiveScope.removeHiddenCols() 177 b.dropOrderingAndExtraCols(recursiveScope) 178 179 // We allow propagation of types from the initial query to the recursive 180 // query. 181 _, propagateToRight := b.checkTypesMatch(initialScope, recursiveScope, 182 false, /* tolerateUnknownLeft */ 183 true, /* tolerateUnknownRight */ 184 "UNION", 185 ) 186 if propagateToRight { 187 recursiveScope = b.propagateTypes(recursiveScope /* dst */, initialScope /* src */) 188 } 189 190 private := memo.RecursiveCTEPrivate{ 191 Name: string(cte.Name.Alias), 192 WithID: withID, 193 InitialCols: colsToColList(initialScope.cols), 194 RecursiveCols: colsToColList(recursiveScope.cols), 195 OutCols: colsToColList(outScope.cols), 196 } 197 198 expr := b.factory.ConstructRecursiveCTE(initialScope.expr, recursiveScope.expr, &private) 199 return expr, cteSrc.cols 200 } 201 202 // getCTECols returns a presentation for the scope, renaming the columns to 203 // those provided in the AliasClause (if any). Throws an error if there is a 204 // mismatch in the number of columns. 205 func (b *Builder) getCTECols(cteScope *scope, name tree.AliasClause) physical.Presentation { 206 presentation := cteScope.makePresentation() 207 208 if len(presentation) == 0 { 209 err := pgerror.Newf( 210 pgcode.FeatureNotSupported, 211 "WITH clause %q does not return any columns", 212 tree.ErrString(&name), 213 ) 214 panic(errors.WithHint(err, "missing RETURNING clause?")) 215 } 216 217 if name.Cols == nil { 218 return presentation 219 } 220 221 if len(presentation) != len(name.Cols) { 222 panic(pgerror.Newf( 223 pgcode.InvalidColumnReference, 224 "source %q has %d columns available but %d columns specified", 225 name.Alias, len(presentation), len(name.Cols), 226 )) 227 } 228 for i := range presentation { 229 presentation[i].Alias = string(name.Cols[i]) 230 } 231 return presentation 232 } 233 234 // splitRecursiveCTE splits a CTE statement of the form 235 // initial_query UNION ALL recursive_query 236 // into the initial and recursive parts. If the statement is not of this form, 237 // returns ok=false. 238 func (b *Builder) splitRecursiveCTE( 239 stmt tree.Statement, 240 ) (initial, recursive *tree.Select, isUnionAll bool, ok bool) { 241 sel, ok := stmt.(*tree.Select) 242 // The form above doesn't allow for "outer" WITH, ORDER BY, or LIMIT 243 // clauses. 244 if !ok || sel.With != nil || sel.OrderBy != nil || sel.Limit != nil { 245 return nil, nil, false, false 246 } 247 union, ok := sel.Select.(*tree.UnionClause) 248 if !ok || union.Type != tree.UnionOp { 249 return nil, nil, false, false 250 } 251 return union.Left, union.Right, union.All, true 252 }