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 }