github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/testutils/opttester/memo_groups.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 opttester
    12  
    13  import (
    14  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    15  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    16  	"github.com/cockroachdb/errors"
    17  )
    18  
    19  // groupID identifies a memo group.
    20  type groupID int
    21  
    22  // memberOrd identifies an expression within its memo group (0 is the first
    23  // expression). It is always 0 for scalar expressions.
    24  type memberOrd int
    25  
    26  // memoLoc identifies an expression by its location in the memo.
    27  type memoLoc struct {
    28  	group  groupID
    29  	member memberOrd
    30  }
    31  
    32  // memoGroups is used to map expressions to memo locations (see MemoLoc).
    33  // To populate this information, AddGroup must be called with the first
    34  // expression in each memo group.
    35  type memoGroups struct {
    36  	// exprMap maps the first expression in each group to a unique group ID; it
    37  	// includes scalar expressions.
    38  	exprMap map[opt.Expr]groupID
    39  	// lastID is used to generate the next ID when AddGroup is called.
    40  	lastID groupID
    41  }
    42  
    43  // AddGroup is called when a new memo group is created.
    44  func (g *memoGroups) AddGroup(firstExpr opt.Expr) {
    45  	g.lastID++
    46  	if g.exprMap == nil {
    47  		g.exprMap = make(map[opt.Expr]groupID)
    48  	}
    49  	g.exprMap[firstExpr] = g.lastID
    50  }
    51  
    52  // MemoLoc finds the memo location of the given expression. Panics if the
    53  // expression isn't in the memo. Note that scalar leaf singletons (like True),
    54  // lists and list items are not considered to be part of the memo.
    55  func (g *memoGroups) MemoLoc(expr opt.Expr) memoLoc {
    56  	var member memberOrd
    57  
    58  	for opt.IsEnforcerOp(expr) {
    59  		// Enforcers aren't actually part of the memo group.
    60  		expr = expr.Child(0)
    61  	}
    62  
    63  	if rel, ok := expr.(memo.RelExpr); ok {
    64  		for e := rel.FirstExpr(); e != rel; e = e.NextExpr() {
    65  			if e == nil {
    66  				panic(errors.AssertionFailedf("could not reach expr (%s) from FirstExpr", expr.Op()))
    67  			}
    68  			member++
    69  		}
    70  		expr = rel.FirstExpr()
    71  	}
    72  	// exprMap contains both scalar and relational groups.
    73  	gID := g.exprMap[expr]
    74  	if gID == 0 {
    75  		panic(errors.AssertionFailedf("could not find group for expr (%s)", expr.Op()))
    76  	}
    77  	return memoLoc{gID, member}
    78  }
    79  
    80  // FindPath finds a path from the root memo group to the given target
    81  // expression. The returned path contains the location for each expression on
    82  // this path, including the target expression (in the last entry).
    83  func (g *memoGroups) FindPath(root opt.Expr, target opt.Expr) []memoLoc {
    84  	for opt.IsEnforcerOp(target) {
    85  		// Enforcers aren't actually part of the memo group.
    86  		target = target.Child(0)
    87  	}
    88  	path := g.depthFirstSearch(root, target, make(map[groupID]struct{}), nil /* path */)
    89  	if path == nil {
    90  		panic(errors.AssertionFailedf("could not find path to expr (%s)", target.Op()))
    91  	}
    92  	return path
    93  }
    94  
    95  // depthFirstSearch is used to find a path from any expression in the group that
    96  // contains `start` to a `target` expression. If found, returns the path as a
    97  // list of memo locations (starting with the given path and ending with the
    98  // location that corresponds to `target`).
    99  func (g *memoGroups) depthFirstSearch(
   100  	start opt.Expr, target opt.Expr, visited map[groupID]struct{}, path []memoLoc,
   101  ) []memoLoc {
   102  	// Lists aren't actually part of the memo; we treat them separately.
   103  	if opt.IsListOp(start) || opt.IsListItemOp(start) {
   104  		for i, n := 0, start.ChildCount(); i < n; i++ {
   105  			if r := g.depthFirstSearch(start.Child(i), target, visited, path); r != nil {
   106  				return r
   107  			}
   108  		}
   109  		return nil
   110  	}
   111  
   112  	// There are various scalar leaf singletons that won't be registered as
   113  	// groups; ignore them.
   114  	if scalar, ok := start.(opt.ScalarExpr); ok && scalar.ChildCount() == 0 {
   115  		if _, found := g.exprMap[scalar]; !found {
   116  			return nil
   117  		}
   118  	}
   119  
   120  	loc, expr := g.firstInGroup(start)
   121  	if _, ok := visited[loc.group]; ok {
   122  		// We already visited this group as part of this DFS.
   123  		return nil
   124  	}
   125  	for ; expr != nil; loc, expr = g.nextInGroup(loc, expr) {
   126  		nextPath := append(path, loc)
   127  		if expr == target {
   128  			return nextPath
   129  		}
   130  		for i, n := 0, expr.ChildCount(); i < n; i++ {
   131  			if r := g.depthFirstSearch(expr.Child(i), target, visited, nextPath); r != nil {
   132  				return r
   133  			}
   134  		}
   135  		// Special hack for scalar expressions that hang off the table meta - we
   136  		// treat these as children of Scan expressions.
   137  		if scan, ok := expr.(*memo.ScanExpr); ok {
   138  			md := scan.Memo().Metadata()
   139  			meta := md.TableMeta(scan.Table)
   140  			if meta.Constraints != nil {
   141  				if r := g.depthFirstSearch(meta.Constraints, target, visited, nextPath); r != nil {
   142  					return r
   143  				}
   144  			}
   145  			for _, expr := range meta.ComputedCols {
   146  				if r := g.depthFirstSearch(expr, target, visited, nextPath); r != nil {
   147  					return r
   148  				}
   149  			}
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  // firstInGroup returns the first expression in the given group, along with its
   156  // location. Panics if the expression isn't in the memo.
   157  func (g *memoGroups) firstInGroup(expr opt.Expr) (memoLoc, opt.Expr) {
   158  	if rel, ok := expr.(memo.RelExpr); ok {
   159  		expr = rel.FirstExpr()
   160  	}
   161  	return g.MemoLoc(expr), expr
   162  }
   163  
   164  // nextInGroup returns the next memo location and corresponding expression,
   165  // given the current location and expression. If this is the last expression,
   166  // returns a nil expression.
   167  func (g *memoGroups) nextInGroup(loc memoLoc, expr opt.Expr) (memoLoc, opt.Expr) {
   168  	// Only relational groups have more than one expression.
   169  	if rel, ok := expr.(memo.RelExpr); ok {
   170  		if next := rel.NextExpr(); next != nil {
   171  			return memoLoc{group: loc.group, member: loc.member + 1}, next
   172  		}
   173  	}
   174  	return memoLoc{}, nil
   175  }