github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/explain_plan.go (about)

     1  // Copyright 2016 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  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"strings"
    18  	"text/tabwriter"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/colflow"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/flowinfra"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/rowexec"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    27  	"github.com/cockroachdb/cockroach/pkg/util/treeprinter"
    28  	"github.com/cockroachdb/errors"
    29  )
    30  
    31  const (
    32  	// explainSubqueryFmtFlags is the format for subqueries within `EXPLAIN SQL` statements.
    33  	// Since these are individually run, we don't need to scrub any data from subqueries.
    34  	explainSubqueryFmtFlags = tree.FmtSimple
    35  
    36  	// sampledLogicalPlanFmtFlags is the format for sampled logical plans. Because these exposed
    37  	// in the Admin UI, sampled plans should be scrubbed of sensitive information.
    38  	sampledLogicalPlanFmtFlags = tree.FmtHideConstants
    39  )
    40  
    41  // explainPlanNode wraps the logic for EXPLAIN as a planNode.
    42  type explainPlanNode struct {
    43  	explainer explainer
    44  
    45  	plan planComponents
    46  
    47  	stmtType tree.StatementType
    48  
    49  	run explainPlanRun
    50  }
    51  
    52  // makeExplainPlanNodeWithPlan instantiates a planNode that EXPLAINs an
    53  // underlying plan.
    54  func (p *planner) makeExplainPlanNodeWithPlan(
    55  	ctx context.Context, opts *tree.ExplainOptions, plan *planComponents, stmtType tree.StatementType,
    56  ) (planNode, error) {
    57  	flags := explainFlags{
    58  		symbolicVars: opts.Flags[tree.ExplainFlagSymVars],
    59  	}
    60  	if opts.Flags[tree.ExplainFlagVerbose] {
    61  		flags.showMetadata = true
    62  		flags.qualifyNames = true
    63  	}
    64  	if opts.Flags[tree.ExplainFlagTypes] {
    65  		flags.showMetadata = true
    66  		flags.showTypes = true
    67  	}
    68  
    69  	columns := sqlbase.ExplainPlanColumns
    70  	if flags.showMetadata {
    71  		columns = sqlbase.ExplainPlanVerboseColumns
    72  	}
    73  	// Make a copy (to allow changes through planMutableColumns).
    74  	columns = append(sqlbase.ResultColumns(nil), columns...)
    75  
    76  	e := explainer{explainFlags: flags}
    77  
    78  	noPlaceholderFlags := tree.FmtExpr(
    79  		tree.FmtSymbolicSubqueries, flags.showTypes, flags.symbolicVars, flags.qualifyNames,
    80  	)
    81  	e.fmtFlags = noPlaceholderFlags
    82  	e.showPlaceholderValues = func(ctx *tree.FmtCtx, placeholder *tree.Placeholder) {
    83  		d, err := placeholder.Eval(p.EvalContext())
    84  		if err != nil {
    85  			// Disable the placeholder formatter so that
    86  			// we don't recurse infinitely trying to evaluate.
    87  			//
    88  			// We also avoid calling ctx.FormatNode because when
    89  			// types are visible, this would cause the type information
    90  			// to be printed twice.
    91  			ctx.WithPlaceholderFormat(nil, func() {
    92  				placeholder.Format(ctx)
    93  			})
    94  			return
    95  		}
    96  		ctx.FormatNode(d)
    97  	}
    98  
    99  	node := &explainPlanNode{
   100  		explainer: e,
   101  		plan:      *plan,
   102  		stmtType:  stmtType,
   103  		run: explainPlanRun{
   104  			results: p.newContainerValuesNode(columns, 0),
   105  		},
   106  	}
   107  	return node, nil
   108  }
   109  
   110  // explainPlanRun is the run-time state of explainPlanNode during local execution.
   111  type explainPlanRun struct {
   112  	// results is the container for EXPLAIN's output.
   113  	results *valuesNode
   114  }
   115  
   116  func (e *explainPlanNode) startExec(params runParams) error {
   117  	return populateExplain(params, &e.explainer, e.run.results, &e.plan, e.stmtType)
   118  }
   119  
   120  func (e *explainPlanNode) Next(params runParams) (bool, error) { return e.run.results.Next(params) }
   121  func (e *explainPlanNode) Values() tree.Datums                 { return e.run.results.Values() }
   122  
   123  func (e *explainPlanNode) Close(ctx context.Context) {
   124  	e.plan.main.Close(ctx)
   125  	for i := range e.plan.subqueryPlans {
   126  		e.plan.subqueryPlans[i].plan.Close(ctx)
   127  	}
   128  	for i := range e.plan.checkPlans {
   129  		e.plan.checkPlans[i].plan.Close(ctx)
   130  	}
   131  	e.run.results.Close(ctx)
   132  }
   133  
   134  // explainEntry is a representation of the info that makes it into an output row
   135  // of an EXPLAIN statement.
   136  type explainEntry struct {
   137  	isNode                bool
   138  	level                 int
   139  	node, field, fieldVal string
   140  	plan                  planNode
   141  }
   142  
   143  // explainFlags contains parameters for the EXPLAIN logic.
   144  type explainFlags struct {
   145  	// showMetadata indicates whether the output has separate columns for the
   146  	// schema signature and ordering information of the intermediate
   147  	// nodes.
   148  	showMetadata bool
   149  
   150  	// qualifyNames determines whether column names in expressions
   151  	// should be fully qualified during pretty-printing.
   152  	qualifyNames bool
   153  
   154  	// symbolicVars determines whether ordinal column references
   155  	// should be printed numerically.
   156  	symbolicVars bool
   157  
   158  	// showTypes indicates whether to print the type of embedded
   159  	// expressions and result columns.
   160  	showTypes bool
   161  }
   162  
   163  // explainFlags represents the run-time state of the EXPLAIN logic.
   164  type explainer struct {
   165  	explainFlags
   166  
   167  	// fmtFlags is the formatter to use for pretty-printing expressions.
   168  	// This can change during the execution of EXPLAIN.
   169  	fmtFlags tree.FmtFlags
   170  
   171  	// showPlaceholderValues is a formatting overload function
   172  	// that will try to evaluate the placeholders if possible.
   173  	// Meant for use with FmtCtx.WithPlaceholderFormat().
   174  	showPlaceholderValues func(ctx *tree.FmtCtx, placeholder *tree.Placeholder)
   175  
   176  	// level is the current depth in the tree of planNodes.
   177  	level int
   178  
   179  	// explainEntry accumulates entries (nodes or attributes).
   180  	entries []explainEntry
   181  }
   182  
   183  // populateExplain walks the plan and generates rows in a valuesNode.
   184  // The subquery plans, if any are known to the planner, are printed
   185  // at the bottom.
   186  func populateExplain(
   187  	params runParams, e *explainer, v *valuesNode, plan *planComponents, stmtType tree.StatementType,
   188  ) error {
   189  	// Determine the "distributed" and "vectorized" values, which we will emit as
   190  	// special rows.
   191  	var willDistribute, willVectorize bool
   192  	distSQLPlanner := params.extendedEvalCtx.DistSQLPlanner
   193  	willDistribute = willDistributePlanForExplainPurposes(
   194  		params.ctx, params.extendedEvalCtx.ExecCfg.NodeID,
   195  		params.extendedEvalCtx.SessionData.DistSQLMode, plan.main,
   196  	)
   197  	outerSubqueries := params.p.curPlan.subqueryPlans
   198  	planCtx := newPlanningCtxForExplainPurposes(distSQLPlanner, params, stmtType, plan.subqueryPlans, willDistribute)
   199  	defer func() {
   200  		planCtx.planner.curPlan.subqueryPlans = outerSubqueries
   201  	}()
   202  	physicalPlan, err := newPhysPlanForExplainPurposes(planCtx, distSQLPlanner, plan.main)
   203  	if err == nil {
   204  		// There might be an issue making the physical plan, but that should not
   205  		// cause an error or panic, so swallow the error. See #40677 for example.
   206  		distSQLPlanner.FinalizePlan(planCtx, physicalPlan)
   207  		// TODO(asubiotto): This cast from SQLInstanceID to NodeID is temporary:
   208  		//  https://github.com/cockroachdb/cockroach/issues/49596
   209  		flows := physicalPlan.GenerateFlowSpecs(roachpb.NodeID(params.extendedEvalCtx.NodeID.SQLInstanceID()))
   210  		flowCtx := newFlowCtxForExplainPurposes(planCtx, params)
   211  		flowCtx.Cfg.ClusterID = &distSQLPlanner.rpcCtx.ClusterID
   212  
   213  		ctxSessionData := flowCtx.EvalCtx.SessionData
   214  		vectorizedThresholdMet := physicalPlan.MaxEstimatedRowCount >= ctxSessionData.VectorizeRowCountThreshold
   215  		willVectorize = true
   216  		if ctxSessionData.VectorizeMode == sessiondata.VectorizeOff {
   217  			willVectorize = false
   218  		} else if !vectorizedThresholdMet && (ctxSessionData.VectorizeMode == sessiondata.Vectorize201Auto || ctxSessionData.VectorizeMode == sessiondata.VectorizeOn) {
   219  			willVectorize = false
   220  		} else {
   221  			thisNodeID := distSQLPlanner.nodeDesc.NodeID
   222  			for nodeID, flow := range flows {
   223  				fuseOpt := flowinfra.FuseNormally
   224  				if nodeID == thisNodeID && !willDistribute {
   225  					fuseOpt = flowinfra.FuseAggressively
   226  				}
   227  				_, err := colflow.SupportsVectorized(params.ctx, flowCtx, flow.Processors, fuseOpt, nil /* output */)
   228  				willVectorize = willVectorize && (err == nil)
   229  				if !willVectorize {
   230  					break
   231  				}
   232  			}
   233  		}
   234  	}
   235  
   236  	emitRow := func(
   237  		treeStr string, level int, node, field, fieldVal, columns, ordering string,
   238  	) error {
   239  		var row tree.Datums
   240  		if !e.showMetadata {
   241  			row = tree.Datums{
   242  				tree.NewDString(treeStr),  // Tree
   243  				tree.NewDString(field),    // Field
   244  				tree.NewDString(fieldVal), // Description
   245  			}
   246  		} else {
   247  			row = tree.Datums{
   248  				tree.NewDString(treeStr),       // Tree
   249  				tree.NewDInt(tree.DInt(level)), // Level
   250  				tree.NewDString(node),          // Type
   251  				tree.NewDString(field),         // Field
   252  				tree.NewDString(fieldVal),      // Description
   253  				tree.NewDString(columns),       // Columns
   254  				tree.NewDString(ordering),      // Ordering
   255  			}
   256  		}
   257  		_, err := v.rows.AddRow(params.ctx, row)
   258  		return err
   259  	}
   260  
   261  	// First, emit the "distributed" and "vectorized" information rows.
   262  	if err := emitRow("", 0, "", "distributed", fmt.Sprintf("%t", willDistribute), "", ""); err != nil {
   263  		return err
   264  	}
   265  	if err := emitRow("", 0, "", "vectorized", fmt.Sprintf("%t", willVectorize), "", ""); err != nil {
   266  		return err
   267  	}
   268  
   269  	e.populateEntries(params.ctx, plan, explainSubqueryFmtFlags)
   270  	return e.emitRows(emitRow)
   271  }
   272  
   273  func (e *explainer) populateEntries(
   274  	ctx context.Context, plan *planComponents, subqueryFmtFlags tree.FmtFlags,
   275  ) {
   276  	e.entries = nil
   277  	observer := planObserver{
   278  		enterNode: e.enterNode,
   279  		expr:      e.expr,
   280  		attr:      e.attr,
   281  		spans:     e.spans,
   282  		leaveNode: e.leaveNode,
   283  	}
   284  	// observePlan never returns an error when returnError is false.
   285  	_ = observePlan(ctx, plan, observer, false /* returnError */, subqueryFmtFlags)
   286  }
   287  
   288  // observePlan walks the plan tree, executing the appropriate functions in the
   289  // planObserver.
   290  func observePlan(
   291  	ctx context.Context,
   292  	plan *planComponents,
   293  	observer planObserver,
   294  	returnError bool,
   295  	subqueryFmtFlags tree.FmtFlags,
   296  ) error {
   297  	if plan.main.isPhysicalPlan() {
   298  		return errors.AssertionFailedf(
   299  			"EXPLAIN of a query with opt-driven DistSQL planning is not supported",
   300  		)
   301  	}
   302  	// If there are any subqueries, cascades, or checks in the plan, we
   303  	// enclose everything as children of a virtual "root" node.
   304  	if len(plan.subqueryPlans) > 0 || len(plan.cascades) > 0 || len(plan.checkPlans) > 0 {
   305  		if _, err := observer.enterNode(ctx, "root", plan.main.planNode); err != nil && returnError {
   306  			return err
   307  		}
   308  	}
   309  
   310  	// Explain the main plan.
   311  	if err := walkPlan(ctx, plan.main.planNode, observer); err != nil && returnError {
   312  		return err
   313  	}
   314  
   315  	// Explain the subqueries.
   316  	for i := range plan.subqueryPlans {
   317  		s := &plan.subqueryPlans[i]
   318  		if _, err := observer.enterNode(ctx, "subquery", nil /* plan */); err != nil && returnError {
   319  			return err
   320  		}
   321  		observer.attr("subquery", "id", fmt.Sprintf("@S%d", i+1))
   322  		// This field contains the original subquery (which could have been modified
   323  		// by optimizer transformations).
   324  		observer.attr(
   325  			"subquery",
   326  			"original sql",
   327  			tree.AsStringWithFlags(s.subquery, subqueryFmtFlags),
   328  		)
   329  		observer.attr("subquery", "exec mode", rowexec.SubqueryExecModeNames[s.execMode])
   330  		if s.plan.planNode != nil {
   331  			if err := walkPlan(ctx, s.plan.planNode, observer); err != nil && returnError {
   332  				return err
   333  			}
   334  		} else if s.started {
   335  			observer.expr(observeAlways, "subquery", "result", -1, s.result)
   336  		}
   337  		if err := observer.leaveNode("subquery", nil /* plan */); err != nil && returnError {
   338  			return err
   339  		}
   340  	}
   341  
   342  	// Explain the cascades.
   343  	for i := range plan.cascades {
   344  		if _, err := observer.enterNode(ctx, "fk-cascade", nil); err != nil && returnError {
   345  			return err
   346  		}
   347  		observer.attr("cascade", "fk", plan.cascades[i].FKName)
   348  		observer.attr("cascade", "input", plan.cascades[i].Buffer.(*bufferNode).label)
   349  		if err := observer.leaveNode("cascade", nil); err != nil && returnError {
   350  			return err
   351  		}
   352  	}
   353  
   354  	// Explain the checks.
   355  	for i := range plan.checkPlans {
   356  		if _, err := observer.enterNode(ctx, "fk-check", nil /* plan */); err != nil && returnError {
   357  			return err
   358  		}
   359  		if plan.checkPlans[i].plan.planNode != nil {
   360  			if err := walkPlan(ctx, plan.checkPlans[i].plan.planNode, observer); err != nil && returnError {
   361  				return err
   362  			}
   363  		}
   364  		if err := observer.leaveNode("fk-check", nil /* plan */); err != nil && returnError {
   365  			return err
   366  		}
   367  	}
   368  
   369  	if len(plan.subqueryPlans) > 0 || len(plan.cascades) > 0 || len(plan.checkPlans) > 0 {
   370  		if err := observer.leaveNode("root", plan.main.planNode); err != nil && returnError {
   371  			return err
   372  		}
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  // emitExplainRowFn is used to emit an EXPLAIN row.
   379  type emitExplainRowFn func(treeStr string, level int, node, field, fieldVal, columns, ordering string) error
   380  
   381  // emitRows calls the given function for each populated entry.
   382  func (e *explainer) emitRows(emitRow emitExplainRowFn) error {
   383  	tp := treeprinter.New()
   384  	// n keeps track of the current node on each level.
   385  	n := []treeprinter.Node{tp}
   386  
   387  	for _, entry := range e.entries {
   388  		if entry.isNode {
   389  			n = append(n[:entry.level+1], n[entry.level].Child(entry.node))
   390  		} else {
   391  			tp.AddEmptyLine()
   392  		}
   393  	}
   394  
   395  	treeRows := tp.FormattedRows()
   396  
   397  	for i, entry := range e.entries {
   398  		var columns, ordering string
   399  		if e.showMetadata && entry.plan != nil {
   400  			cols := planColumns(entry.plan)
   401  			columns = formatColumns(cols, e.showTypes)
   402  			ordering = formatOrdering(planReqOrdering(entry.plan), cols)
   403  		}
   404  		if err := emitRow(
   405  			treeRows[i], entry.level, entry.node, entry.field, entry.fieldVal, columns, ordering,
   406  		); err != nil {
   407  			return err
   408  		}
   409  	}
   410  	return nil
   411  }
   412  
   413  // planToString builds a string representation of a plan using the EXPLAIN
   414  // infrastructure.
   415  func planToString(ctx context.Context, p *planTop) string {
   416  	e := explainer{
   417  		explainFlags: explainFlags{
   418  			showMetadata: true,
   419  			showTypes:    true,
   420  		},
   421  		fmtFlags: tree.FmtExpr(tree.FmtSymbolicSubqueries, true, true, true),
   422  	}
   423  
   424  	var buf bytes.Buffer
   425  	tw := tabwriter.NewWriter(&buf, 2, 1, 2, ' ', 0)
   426  
   427  	emitRow := func(
   428  		treeStr string, level int, node, field, fieldVal, columns, ordering string,
   429  	) error {
   430  		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", treeStr, field, fieldVal, columns, ordering)
   431  		return nil
   432  	}
   433  
   434  	e.populateEntries(ctx, &p.planComponents, explainSubqueryFmtFlags)
   435  
   436  	// Our emitRow function never returns errors, so neither will emitRows().
   437  	_ = e.emitRows(emitRow)
   438  	_ = tw.Flush()
   439  
   440  	// Remove trailing whitespace from each line.
   441  	result := strings.TrimRight(buf.String(), "\n")
   442  	buf.Reset()
   443  	for _, line := range strings.Split(result, "\n") {
   444  		fmt.Fprintf(&buf, "%s\n", strings.TrimRight(line, " "))
   445  	}
   446  	return buf.String()
   447  }
   448  
   449  func getAttrForSpansAll(hardLimitSet bool) string {
   450  	if hardLimitSet {
   451  		return "LIMITED SCAN"
   452  	}
   453  	return "FULL SCAN"
   454  }
   455  
   456  // spans implements the planObserver interface.
   457  func (e *explainer) spans(
   458  	nodeName, fieldName string,
   459  	index *sqlbase.IndexDescriptor,
   460  	spans []roachpb.Span,
   461  	hardLimitSet bool,
   462  ) {
   463  	spanss := sqlbase.PrettySpans(index, spans, 2)
   464  	if spanss != "" {
   465  		if spanss == "-" {
   466  			spanss = getAttrForSpansAll(hardLimitSet)
   467  		}
   468  		e.attr(nodeName, fieldName, spanss)
   469  	}
   470  }
   471  
   472  // expr implements the planObserver interface.
   473  func (e *explainer) expr(v observeVerbosity, nodeName, fieldName string, n int, expr tree.Expr) {
   474  	if expr != nil {
   475  		if !e.showMetadata && v == observeMetadata {
   476  			return
   477  		}
   478  		if nodeName == "join" {
   479  			qualifySave := e.fmtFlags
   480  			e.fmtFlags.SetFlags(tree.FmtShowTableAliases)
   481  			defer func(e *explainer, f tree.FmtFlags) { e.fmtFlags = f }(e, qualifySave)
   482  		}
   483  		if n >= 0 {
   484  			fieldName = fmt.Sprintf("%s %d", fieldName, n)
   485  		}
   486  
   487  		f := tree.NewFmtCtx(e.fmtFlags)
   488  		f.SetPlaceholderFormat(e.showPlaceholderValues)
   489  		f.FormatNode(expr)
   490  		e.attr(nodeName, fieldName, f.CloseAndGetString())
   491  	}
   492  }
   493  
   494  // enterNode implements the planObserver interface.
   495  func (e *explainer) enterNode(_ context.Context, name string, plan planNode) (bool, error) {
   496  	e.entries = append(e.entries, explainEntry{
   497  		isNode: true,
   498  		level:  e.level,
   499  		node:   name,
   500  		plan:   plan,
   501  	})
   502  
   503  	e.level++
   504  	return true, nil
   505  }
   506  
   507  // attr implements the planObserver interface.
   508  func (e *explainer) attr(nodeName, fieldName, attr string) {
   509  	e.entries = append(e.entries, explainEntry{
   510  		isNode:   false,
   511  		level:    e.level - 1,
   512  		field:    fieldName,
   513  		fieldVal: attr,
   514  	})
   515  }
   516  
   517  // leaveNode implements the planObserver interface.
   518  func (e *explainer) leaveNode(name string, _ planNode) error {
   519  	e.level--
   520  	return nil
   521  }
   522  
   523  // formatColumns converts a column signature for a data source /
   524  // planNode to a string. The column types are printed iff the 2nd
   525  // argument specifies so.
   526  func formatColumns(cols sqlbase.ResultColumns, printTypes bool) string {
   527  	f := tree.NewFmtCtx(tree.FmtSimple)
   528  	f.WriteByte('(')
   529  	for i := range cols {
   530  		rCol := &cols[i]
   531  		if i > 0 {
   532  			f.WriteString(", ")
   533  		}
   534  		f.FormatNameP(&rCol.Name)
   535  		// Output extra properties like [hidden,omitted].
   536  		hasProps := false
   537  		outputProp := func(prop string) {
   538  			if hasProps {
   539  				f.WriteByte(',')
   540  			} else {
   541  				f.WriteByte('[')
   542  			}
   543  			hasProps = true
   544  			f.WriteString(prop)
   545  		}
   546  		if rCol.Hidden {
   547  			outputProp("hidden")
   548  		}
   549  		if hasProps {
   550  			f.WriteByte(']')
   551  		}
   552  
   553  		if printTypes {
   554  			f.WriteByte(' ')
   555  			f.WriteString(rCol.Typ.String())
   556  		}
   557  	}
   558  	f.WriteByte(')')
   559  	return f.CloseAndGetString()
   560  }