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  }