github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/explain_tree.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 sql 12 13 import ( 14 "context" 15 "fmt" 16 17 "github.com/cockroachdb/cockroach/pkg/roachpb" 18 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 19 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 20 "github.com/cockroachdb/cockroach/pkg/util" 21 ) 22 23 // planToTree uses a stack to "parse" the planObserver's sequence of calls 24 // into a tree which can be easily serialized as JSON or Protobuf. 25 // 26 // e.g. for the plan 27 // 28 // join [cond: t1.a = t2.b] 29 // scan [table: t1] 30 // scan [table: t2] 31 // 32 // the observer would call 33 // 34 // enterNode join // push onto stack 35 // enterNode scan // push onto stack 36 // attr table: t1 // add attribute 37 // leaveNode // pop scan node; add it as a child of join node 38 // enterNode scan // push onto stack 39 // attr table: t2 // add attribute 40 // leaveNode // pop scan node; add it as a child of join node 41 // expr cond: t1.a = t2.b // add attribute 42 // leaveNode // keep root node on stack (base case because it's the root). 43 // 44 // and planToTree would return the join node. 45 func planToTree(ctx context.Context, top *planTop) *roachpb.ExplainTreePlanNode { 46 var nodeStack planNodeStack 47 observer := planObserver{ 48 // We set followRowSourceToPlanNode to true, to instruct the plan observer 49 // to follow the edges from rowSourceToPlanNodes (indicating that the prior 50 // node was not plannable by DistSQL) to the original planNodes that were 51 // replaced by DistSQL nodes. This prevents the walk from ending at these 52 // special replacement nodes. 53 // TODO(jordan): this is pretty hacky. We should modify DistSQL physical 54 // planning to avoid mutating its input planNode tree instead. 55 followRowSourceToPlanNode: true, 56 enterNode: func(ctx context.Context, nodeName string, plan planNode) (bool, error) { 57 nodeStack.push(&roachpb.ExplainTreePlanNode{ 58 Name: nodeName, 59 }) 60 return true, nil 61 }, 62 expr: func(_ observeVerbosity, nodeName, fieldName string, n int, expr tree.Expr) { 63 if expr == nil { 64 return 65 } 66 stackTop := nodeStack.peek() 67 stackTop.Attrs = append(stackTop.Attrs, &roachpb.ExplainTreePlanNode_Attr{ 68 Key: fieldName, 69 Value: tree.AsStringWithFlags(expr, sampledLogicalPlanFmtFlags), 70 }) 71 }, 72 spans: func(nodeName, fieldName string, index *sqlbase.IndexDescriptor, spans []roachpb.Span, hardLimitSet bool) { 73 // TODO(jordan): it's expensive to serialize long span 74 // strings. It's unfortunate that we're still calling 75 // PrettySpans, just to check to see whether the output is - or 76 // not. Unfortunately it's not so clear yet how to write a 77 // shorter function. Suggestions welcome. 78 spanss := sqlbase.PrettySpans(index, spans, 2) 79 if spanss != "" { 80 if spanss == "-" { 81 spanss = getAttrForSpansAll(hardLimitSet) 82 } else { 83 // Spans contain literal values from the query and thus 84 // cannot be spelled out in the collected plan. 85 spanss = fmt.Sprintf("%d span%s", len(spans), util.Pluralize(int64(len(spans)))) 86 } 87 stackTop := nodeStack.peek() 88 stackTop.Attrs = append(stackTop.Attrs, &roachpb.ExplainTreePlanNode_Attr{ 89 Key: fieldName, 90 Value: spanss, 91 }) 92 } 93 }, 94 attr: func(nodeName, fieldName, attr string) { 95 stackTop := nodeStack.peek() 96 stackTop.Attrs = append(stackTop.Attrs, &roachpb.ExplainTreePlanNode_Attr{ 97 Key: fieldName, 98 Value: attr, 99 }) 100 }, 101 leaveNode: func(nodeName string, plan planNode) error { 102 if nodeStack.len() == 1 { 103 return nil 104 } 105 poppedNode := nodeStack.pop() 106 newStackTop := nodeStack.peek() 107 newStackTop.Children = append(newStackTop.Children, poppedNode) 108 return nil 109 }, 110 } 111 112 if err := observePlan( 113 ctx, &top.planComponents, observer, true /* returnError */, sampledLogicalPlanFmtFlags, 114 ); err != nil { 115 panic(fmt.Sprintf("error while walking plan to save it to statement stats: %s", err.Error())) 116 } 117 return nodeStack.peek() 118 } 119 120 type planNodeStack struct { 121 stack []*roachpb.ExplainTreePlanNode 122 } 123 124 func (ns *planNodeStack) push(node *roachpb.ExplainTreePlanNode) { 125 ns.stack = append(ns.stack, node) 126 } 127 128 func (ns *planNodeStack) pop() *roachpb.ExplainTreePlanNode { 129 if len(ns.stack) == 0 { 130 return nil 131 } 132 stackTop := ns.stack[len(ns.stack)-1] 133 ns.stack = ns.stack[0 : len(ns.stack)-1] 134 return stackTop 135 } 136 137 func (ns *planNodeStack) peek() *roachpb.ExplainTreePlanNode { 138 if len(ns.stack) == 0 { 139 return nil 140 } 141 return ns.stack[len(ns.stack)-1] 142 } 143 144 func (ns *planNodeStack) len() int { 145 return len(ns.stack) 146 }