github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/scope.go (about) 1 // Copyright 2020-2021 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package plan 16 17 import ( 18 "github.com/dolthub/go-mysql-server/sql" 19 "github.com/dolthub/go-mysql-server/sql/transform" 20 ) 21 22 // Scope of the analysis being performed, used when analyzing subqueries to give such analysis access to outer scope. 23 type Scope struct { 24 // Stack of nested node scopes, with innermost scope first. A scope node is the node in which the subquery is 25 // defined, or an appropriate sibling, NOT the child node of the Subquery node. 26 nodes []sql.Node 27 // Memo nodes are nodes in the execution context that shouldn't be considered for name resolution, but are still 28 // important for analysis. 29 Memos []sql.Node 30 // recursionDepth tracks how many times we've recursed with analysis, to avoid stack overflows from infinite recursion 31 recursionDepth int 32 // CurrentNodeIsFromSubqueryExpression is true when the last scope (i.e. the most inner of the outer scope levels) has been 33 // created by a subquery expression. This is needed in order to calculate outer scope visibility for derived tables. 34 CurrentNodeIsFromSubqueryExpression bool 35 // EnforceReadOnly causes analysis to block all modification operations, as though a database is read only. 36 EnforceReadOnly bool 37 38 Procedures *ProcedureCache 39 40 // corr is the aggregated set of correlated columns tracked by the subquery 41 // chain that produced this scope. 42 corr sql.ColSet 43 inJoin bool 44 inLateralJoin bool 45 joinSiblings []sql.Node 46 } 47 48 func (s *Scope) SetJoin(b bool) { 49 if s == nil { 50 return 51 } 52 s.inJoin = b 53 } 54 55 func (s *Scope) SetLateralJoin(b bool) { 56 if s == nil { 57 return 58 } 59 s.inLateralJoin = b 60 } 61 62 func (s *Scope) IsEmpty() bool { 63 return s == nil || len(s.nodes) == 0 64 } 65 66 func (s *Scope) EnforcesReadOnly() bool { 67 return s != nil && s.EnforceReadOnly 68 } 69 70 // OuterRelUnresolved returns true if the relations in the 71 // outer scope are not qualified and resolved. 72 // note: a subquery in the outer scope is itself a scope, 73 // and by definition not an outer relation 74 func (s *Scope) OuterRelUnresolved() bool { 75 return !s.IsEmpty() && s.Schema() == nil && len(s.nodes[0].Children()) > 0 76 } 77 78 // NewScope creates a new Scope object with the additional innermost Node context. When constructing with a subquery, 79 // the Node given should be the sibling Node of the subquery. 80 func (s *Scope) NewScope(node sql.Node) *Scope { 81 if s == nil { 82 return &Scope{nodes: []sql.Node{node}} 83 } 84 var newNodes []sql.Node 85 newNodes = append(newNodes, node) 86 newNodes = append(newNodes, s.nodes...) 87 return &Scope{ 88 nodes: newNodes, 89 Memos: s.Memos, 90 recursionDepth: s.recursionDepth + 1, 91 Procedures: s.Procedures, 92 joinSiblings: s.joinSiblings, 93 } 94 } 95 96 // NewScopeFromSubqueryExpression returns a new subscope created from a 97 // subquery expression contained by the specified node. |corr| is the 98 // set of correlated columns referenced in this subquery, which is only 99 // implicit here because the subquery is distanced from its parent |node|. 100 func (s *Scope) NewScopeFromSubqueryExpression(node sql.Node, corr sql.ColSet) *Scope { 101 subScope := s.NewScope(node) 102 subScope.CurrentNodeIsFromSubqueryExpression = true 103 subScope.corr = corr 104 if s != nil { 105 subScope.corr = s.corr.Union(corr) 106 } 107 return subScope 108 } 109 110 // NewScopeFromSubqueryExpression returns a new subscope created from a subquery expression contained by the specified 111 // node. 112 func (s *Scope) NewScopeInJoin(node sql.Node) *Scope { 113 for { 114 var done bool 115 switch n := node.(type) { 116 case *StripRowNode: 117 node = n.Child 118 default: 119 done = true 120 } 121 if done { 122 break 123 } 124 } 125 if s == nil { 126 return &Scope{joinSiblings: []sql.Node{node}} 127 } 128 129 var newNodes []sql.Node 130 newNodes = append(newNodes, node) 131 newNodes = append(newNodes, s.joinSiblings...) 132 return &Scope{ 133 nodes: s.nodes, 134 Memos: s.Memos, 135 recursionDepth: s.recursionDepth + 1, 136 Procedures: s.Procedures, 137 joinSiblings: newNodes, 138 corr: s.corr, 139 } 140 } 141 142 // newScopeFromSubqueryExpression returns a new subscope created from a subquery expression contained by the specified 143 // node. 144 func (s *Scope) NewScopeNoJoin() *Scope { 145 return &Scope{ 146 nodes: s.nodes, 147 Memos: s.Memos, 148 recursionDepth: s.recursionDepth + 1, 149 Procedures: s.Procedures, 150 EnforceReadOnly: s.EnforceReadOnly, 151 corr: s.corr, 152 } 153 } 154 155 // NewScopeFromSubqueryAlias returns a new subscope created from the specified SubqueryAlias. Subquery aliases, or 156 // derived tables, generally do NOT have any visibility to outer scopes, but when they are nested inside a subquery 157 // expression, they may reference tables from the scopes outside the subquery expression's scope. 158 func (s *Scope) NewScopeFromSubqueryAlias(sqa *SubqueryAlias) *Scope { 159 subScope := newScopeWithDepth(s.RecursionDepth() + 1) 160 subScope.corr = sqa.Correlated 161 if s != nil { 162 if len(s.nodes) > 0 { 163 // As of MySQL 8.0.14, MySQL provides OUTER scope visibility to derived tables. Unlike LATERAL scope visibility, which 164 // gives a derived table visibility to the adjacent expressions where the subquery is defined, OUTER scope visibility 165 // gives a derived table visibility to the OUTER scope where the subquery is defined. 166 // https://dev.mysql.com/blog-archive/supporting-all-kinds-of-outer-references-in-derived-tables-lateral-or-not/ 167 // We don't include the current inner node so that the outer scope nodes are still present, but not the lateral nodes 168 if s.CurrentNodeIsFromSubqueryExpression { // TODO: probably copy this for lateral 169 sqa.OuterScopeVisibility = true 170 subScope.nodes = append(subScope.nodes, s.InnerToOuter()...) 171 } 172 } 173 if len(s.joinSiblings) > 0 { 174 subScope.joinSiblings = append(subScope.joinSiblings, s.joinSiblings...) 175 } 176 subScope.inJoin = s.inJoin 177 subScope.inLateralJoin = s.inLateralJoin 178 subScope.corr = s.corr.Union(sqa.Correlated) 179 } 180 181 return subScope 182 } 183 184 // newScopeWithDepth returns a new scope object with the recursion depth given 185 func newScopeWithDepth(depth int) *Scope { 186 return &Scope{recursionDepth: depth} 187 } 188 189 // Memo creates a new Scope object with the Memo node given. Memo nodes don't affect name resolution, but are used in 190 // other parts of analysis, such as error handling for trigger / procedure execution. 191 func (s *Scope) Memo(node sql.Node) *Scope { 192 if s == nil { 193 return &Scope{Memos: []sql.Node{node}} 194 } 195 var newNodes []sql.Node 196 newNodes = append(newNodes, node) 197 newNodes = append(newNodes, s.Memos...) 198 return &Scope{ 199 Memos: newNodes, 200 nodes: s.nodes, 201 Procedures: s.Procedures, 202 } 203 } 204 205 // WithMemos returns a new scope object identical to the receiver, but with its memos replaced with the ones given. 206 func (s *Scope) WithMemos(memoNodes []sql.Node) *Scope { 207 if s == nil { 208 return &Scope{Memos: memoNodes} 209 } 210 return &Scope{ 211 Memos: memoNodes, 212 nodes: s.nodes, 213 Procedures: s.Procedures, 214 } 215 } 216 217 func (s *Scope) MemoNodes() []sql.Node { 218 if s == nil { 219 return nil 220 } 221 return s.Memos 222 } 223 224 func (s *Scope) RecursionDepth() int { 225 if s == nil { 226 return 0 227 } 228 return s.recursionDepth 229 } 230 231 func (s *Scope) ProcedureCache() *ProcedureCache { 232 if s == nil { 233 return nil 234 } 235 return s.Procedures 236 } 237 238 func (s *Scope) WithProcedureCache(cache *ProcedureCache) *Scope { 239 if s == nil { 240 return &Scope{Procedures: cache} 241 } 242 return &Scope{ 243 Memos: s.Memos, 244 nodes: s.nodes, 245 Procedures: cache, 246 } 247 } 248 249 func (s *Scope) ProceduresPopulating() bool { 250 return s != nil && s.Procedures != nil && s.Procedures.IsPopulating 251 } 252 253 // InnerToOuter returns the scope Nodes in order of innermost scope to outermost scope. When using these nodes for 254 // analysis, always inspect the children of the nodes, rather than the nodes themselves. The children define the schema 255 // of the rows being processed by the scope node itself. 256 func (s *Scope) InnerToOuter() []sql.Node { 257 if s == nil { 258 return nil 259 } 260 return s.nodes 261 } 262 263 // OuterToInner returns the scope nodes in order of outermost scope to innermost scope. When using these nodes for 264 // analysis, always inspect the children of the nodes, rather than the nodes themselves. The children define the schema 265 // of the rows being processed by the scope node itself. 266 func (s *Scope) OuterToInner() []sql.Node { 267 if s == nil { 268 return nil 269 } 270 reversed := make([]sql.Node, len(s.nodes)) 271 for i := range s.nodes { 272 reversed[i] = s.nodes[len(s.nodes)-i-1] 273 } 274 return reversed 275 } 276 277 // Schema returns the equivalent schema of this scope, which consists of the schemas of all constituent scope nodes 278 // concatenated from outer to inner. Because we can only calculate the Schema() of nodes that are Resolved(), this 279 // method fills in place holder columns as necessary. 280 func (s *Scope) Schema() sql.Schema { 281 var schema sql.Schema 282 for _, n := range s.OuterToInner() { 283 for _, n := range n.Children() { 284 if n.Resolved() { 285 schema = append(schema, n.Schema()...) 286 continue 287 } 288 289 // If this scope node isn't resolved, we can't use Schema() on it. Instead, assemble an equivalent Schema, with 290 // placeholder columns where necessary, for the purpose of analysis. 291 switch n := n.(type) { 292 case *Project: 293 for _, expr := range n.Projections { 294 var col *sql.Column 295 if expr.Resolved() { 296 col = transform.ExpressionToColumn(expr, AliasSubqueryString(expr)) 297 } else { 298 // TODO: a new type here? 299 col = &sql.Column{ 300 Name: "", 301 Source: "", 302 } 303 } 304 schema = append(schema, col) 305 } 306 default: 307 // TODO: log this 308 // panic(fmt.Sprintf("Unsupported scope node %T", n)) 309 } 310 } 311 } 312 if s != nil && s.inJoin { 313 for _, n := range s.joinSiblings { 314 schema = append(schema, n.Schema()...) 315 } 316 } 317 return schema 318 } 319 320 func (s *Scope) InJoin() bool { 321 if s == nil { 322 return false 323 } 324 return s.inJoin 325 } 326 327 func (s *Scope) InLateralJoin() bool { 328 if s == nil { 329 return false 330 } 331 return s.inLateralJoin 332 } 333 334 func (s *Scope) JoinSiblings() []sql.Node { 335 return s.joinSiblings 336 } 337 338 func (s *Scope) Correlated() sql.ColSet { 339 return s.corr 340 }