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  }