github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/xform/memo_format.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 xform
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"sort"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
    21  	"github.com/cockroachdb/cockroach/pkg/util/treeprinter"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  // FmtFlags controls how the memo output is formatted.
    26  type FmtFlags int
    27  
    28  const (
    29  	// FmtPretty performs a breadth-first topological sort on the memo groups,
    30  	// and shows the root group at the top of the memo.
    31  	FmtPretty FmtFlags = iota
    32  )
    33  
    34  type group struct {
    35  	first  opt.Expr
    36  	states []*groupState
    37  }
    38  
    39  type memoFormatter struct {
    40  	buf   *bytes.Buffer
    41  	flags FmtFlags
    42  
    43  	o *Optimizer
    44  
    45  	// groups reachable from the root, generated in breadth-first order.
    46  	groups []group
    47  
    48  	// groupIdx remembers the group index (in the groups slice) for the first
    49  	// expression in each group.
    50  	groupIdx map[opt.Expr]int
    51  }
    52  
    53  func makeMemoFormatter(o *Optimizer, flags FmtFlags) memoFormatter {
    54  	return memoFormatter{buf: &bytes.Buffer{}, flags: flags, o: o}
    55  }
    56  
    57  func (mf *memoFormatter) format() string {
    58  	m := mf.o.mem
    59  
    60  	// Assign group numbers to every expression in the memo.
    61  	mf.groupIdx = make(map[opt.Expr]int)
    62  	mf.numberMemo(m.RootExpr())
    63  
    64  	// Populate the group states.
    65  	mf.populateStates()
    66  
    67  	// Format the memo using treeprinter.
    68  	tp := treeprinter.New()
    69  	desc := "not optimized"
    70  	if mf.o.mem.IsOptimized() {
    71  		desc = "optimized"
    72  	}
    73  	tpRoot := tp.Childf(
    74  		"memo (%s, ~%dKB, required=%s)",
    75  		desc, mf.o.mem.MemoryEstimate()/1024, m.RootProps(),
    76  	)
    77  
    78  	for i, e := range mf.groups {
    79  		mf.buf.Reset()
    80  		rel, ok := e.first.(memo.RelExpr)
    81  		if !ok {
    82  			mf.formatExpr(e.first)
    83  			tpRoot.Childf("G%d: %s", i+1, mf.buf.String())
    84  			continue
    85  		}
    86  		mf.formatGroup(rel)
    87  		tpChild := tpRoot.Childf("G%d: %s", i+1, mf.buf.String())
    88  		for _, s := range e.states {
    89  			mf.buf.Reset()
    90  			c := tpChild.Childf("%s", s.required)
    91  			mf.formatBest(s.best, s.required)
    92  			c.Childf("best: %s", mf.buf.String())
    93  			c.Childf("cost: %.2f", s.cost)
    94  		}
    95  	}
    96  
    97  	return tp.String()
    98  }
    99  
   100  func (mf *memoFormatter) group(expr opt.Expr) int {
   101  	res, ok := mf.groupIdx[firstExpr(expr)]
   102  	if !ok {
   103  		panic(errors.AssertionFailedf("unknown group for %s", expr))
   104  	}
   105  	return res
   106  }
   107  
   108  // numberMemo does a breadth-first search of the memo (starting at the root of
   109  // the expression tree), creates the groups and sets groupIdx for all
   110  // expressions.
   111  func (mf *memoFormatter) numberMemo(root opt.Expr) {
   112  	mf.numberExpr(root)
   113  
   114  	// Do a breadth-first search (groups acts as our queue).
   115  	for i := 0; i < len(mf.groups); i++ {
   116  		// next returns the next expression in the group.
   117  		next := func(e opt.Expr) opt.Expr {
   118  			rel, ok := e.(memo.RelExpr)
   119  			if !ok {
   120  				return nil
   121  			}
   122  			return rel.NextExpr()
   123  		}
   124  
   125  		for e := mf.groups[i].first; e != nil; e = next(e) {
   126  			for i := 0; i < e.ChildCount(); i++ {
   127  				mf.numberExpr(e.Child(i))
   128  			}
   129  		}
   130  	}
   131  }
   132  
   133  // numberExpr sets groupIdx for the expression, creating a new group if necessary.
   134  func (mf *memoFormatter) numberExpr(expr opt.Expr) {
   135  	// Don't include list item expressions, as they don't communicate useful
   136  	// information.
   137  	skipItemOp := func(expr opt.Expr) opt.Expr {
   138  		if opt.IsListItemOp(expr) {
   139  			return expr.Child(0)
   140  		}
   141  		return expr
   142  	}
   143  
   144  	expr = firstExpr(skipItemOp(expr))
   145  
   146  	// Handle special case of list expressions, which are not interned and stored
   147  	// as byval slices in parent expressions: search the existing groups to detect
   148  	// duplicate lists.
   149  	if opt.IsListOp(expr) {
   150  		for i := range mf.groups {
   151  			g := mf.groups[i].first
   152  			if g.Op() == expr.Op() && g.ChildCount() == expr.ChildCount() {
   153  				eq := true
   154  				for j := 0; j < g.ChildCount(); j++ {
   155  					if firstExpr(skipItemOp(g.Child(j))) != firstExpr(skipItemOp(expr.Child(j))) {
   156  						eq = false
   157  						break
   158  					}
   159  				}
   160  				if eq {
   161  					mf.groupIdx[expr] = i
   162  					return
   163  				}
   164  			}
   165  		}
   166  		// Not a duplicate; fall through to add a new group.
   167  	}
   168  
   169  	if _, ok := mf.groupIdx[expr]; !ok {
   170  		mf.groupIdx[expr] = len(mf.groups)
   171  		mf.groups = append(mf.groups, group{first: expr})
   172  	}
   173  }
   174  
   175  func (mf *memoFormatter) populateStates() {
   176  	for groupStateKey, groupState := range mf.o.stateMap {
   177  		if !groupState.fullyOptimized {
   178  			continue
   179  		}
   180  		groupIdx, ok := mf.groupIdx[groupStateKey.group]
   181  		if !ok {
   182  			// This group was not reachable from the root; ignore.
   183  			continue
   184  		}
   185  		mf.groups[groupIdx].states = append(mf.groups[groupIdx].states, groupState)
   186  	}
   187  
   188  	// Sort the states to get deterministic results.
   189  	for groupIdx := range mf.groups {
   190  		s := mf.groups[groupIdx].states
   191  		sort.Slice(s, func(i, j int) bool {
   192  			p1, p2 := s[i].required, s[j].required
   193  			// Sort no required props last.
   194  			if !p1.Defined() {
   195  				return false
   196  			}
   197  			if !p2.Defined() {
   198  				return true
   199  			}
   200  			// Sort props with presentations before those without.
   201  			if !p1.Presentation.Any() && p2.Presentation.Any() {
   202  				return true
   203  			}
   204  			if p1.Presentation.Any() && !p2.Presentation.Any() {
   205  				return false
   206  			}
   207  			// Finally, just sort by string.
   208  			return p1.String() < p2.String()
   209  		})
   210  	}
   211  
   212  }
   213  
   214  // formatGroup prints out (to mf.buf) all members of the group); e.g:
   215  //    (limit G2 G3 ordering=-1) (scan a,rev,cols=(1,3),lim=10(rev))
   216  func (mf *memoFormatter) formatGroup(first memo.RelExpr) {
   217  	for member := first; member != nil; member = member.NextExpr() {
   218  		if member != first {
   219  			mf.buf.WriteByte(' ')
   220  		}
   221  		mf.formatExpr(member)
   222  	}
   223  }
   224  
   225  // formatExpr prints out (to mf.buf) a single expression; e.g:
   226  //    (filters G6 G7)
   227  func (mf *memoFormatter) formatExpr(e opt.Expr) {
   228  	fmt.Fprintf(mf.buf, "(%s", e.Op())
   229  	for i := 0; i < e.ChildCount(); i++ {
   230  		child := e.Child(i)
   231  		if opt.IsListItemOp(child) {
   232  			child = child.Child(0)
   233  		}
   234  		fmt.Fprintf(mf.buf, " G%d", mf.group(child)+1)
   235  	}
   236  	mf.formatPrivate(e, &physical.Required{})
   237  	mf.buf.WriteString(")")
   238  }
   239  
   240  func (mf *memoFormatter) formatBest(best memo.RelExpr, required *physical.Required) {
   241  	fmt.Fprintf(mf.buf, "(%s", best.Op())
   242  
   243  	for i := 0; i < best.ChildCount(); i++ {
   244  		fmt.Fprintf(mf.buf, " G%d", mf.group(best.Child(i))+1)
   245  
   246  		// Print properties required of the child if they are interesting.
   247  		childReq := BuildChildPhysicalProps(mf.o.mem, best, i, required)
   248  		if childReq.Defined() {
   249  			fmt.Fprintf(mf.buf, "=\"%s\"", childReq)
   250  		}
   251  	}
   252  
   253  	mf.formatPrivate(best, required)
   254  	mf.buf.WriteString(")")
   255  }
   256  
   257  func (mf *memoFormatter) formatPrivate(e opt.Expr, physProps *physical.Required) {
   258  	private := e.Private()
   259  	if private == nil {
   260  		return
   261  	}
   262  
   263  	// Remap special-case privates.
   264  	switch t := e.(type) {
   265  	case *memo.CastExpr:
   266  		private = t.Typ.SQLString()
   267  	}
   268  
   269  	// Start by using private expression formatting.
   270  	m := mf.o.mem
   271  	nf := memo.MakeExprFmtCtxBuffer(mf.buf, memo.ExprFmtHideAll, m, nil /* catalog */)
   272  	memo.FormatPrivate(&nf, private, physProps)
   273  
   274  	// Now append additional information that's useful in the memo case.
   275  	switch t := e.(type) {
   276  	case *memo.ScanExpr:
   277  		tab := m.Metadata().Table(t.Table)
   278  		if tab.ColumnCount() != t.Cols.Len() {
   279  			fmt.Fprintf(mf.buf, ",cols=%s", t.Cols)
   280  		}
   281  		if t.Constraint != nil {
   282  			fmt.Fprintf(mf.buf, ",constrained")
   283  		}
   284  		if t.HardLimit.IsSet() {
   285  			fmt.Fprintf(mf.buf, ",lim=%s", t.HardLimit)
   286  		}
   287  
   288  	case *memo.IndexJoinExpr:
   289  		fmt.Fprintf(mf.buf, ",cols=%s", t.Cols)
   290  
   291  	case *memo.LookupJoinExpr:
   292  		fmt.Fprintf(mf.buf, ",keyCols=%v,outCols=%s", t.KeyCols, t.Cols)
   293  
   294  	case *memo.ExplainExpr:
   295  		propsStr := t.Props.String()
   296  		if propsStr != "" {
   297  			fmt.Fprintf(mf.buf, " %s", propsStr)
   298  		}
   299  
   300  	case *memo.ProjectExpr:
   301  		t.Passthrough.ForEach(func(i opt.ColumnID) {
   302  			fmt.Fprintf(mf.buf, " %s", m.Metadata().ColumnMeta(i).Alias)
   303  		})
   304  	}
   305  }
   306  
   307  func firstExpr(expr opt.Expr) opt.Expr {
   308  	if rel, ok := expr.(memo.RelExpr); ok {
   309  		return rel.FirstExpr()
   310  	}
   311  	return expr
   312  }