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

     1  // Copyright 2017 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  
    16  	"github.com/cockroachdb/cockroach/pkg/base"
    17  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/tracing"
    23  	"github.com/cockroachdb/errors"
    24  	"github.com/opentracing/opentracing-go"
    25  )
    26  
    27  // explainDistSQLNode is a planNode that wraps a plan and returns
    28  // information related to running that plan under DistSQL.
    29  type explainDistSQLNode struct {
    30  	optColumnsSlot
    31  
    32  	options *tree.ExplainOptions
    33  	plan    planComponents
    34  
    35  	stmtType tree.StatementType
    36  
    37  	// If analyze is set, plan will be executed with tracing enabled and a url
    38  	// pointing to a visual query plan with statistics will be in the row
    39  	// returned by the node.
    40  	analyze bool
    41  
    42  	run explainDistSQLRun
    43  }
    44  
    45  // explainDistSQLRun contains the run-time state of explainDistSQLNode during local execution.
    46  type explainDistSQLRun struct {
    47  	// The single row returned by the node.
    48  	values tree.Datums
    49  
    50  	// done is set if Next() was called.
    51  	done bool
    52  
    53  	// executedStatement is set if EXPLAIN ANALYZE was active and finished
    54  	// executing the query, regardless of query success or failure.
    55  	executedStatement bool
    56  }
    57  
    58  // distSQLExplainable is an interface used for local plan nodes that create
    59  // distributed jobs. The plan node should implement this interface so that
    60  // EXPLAIN (DISTSQL) will show the DistSQL plan instead of the local plan node.
    61  type distSQLExplainable interface {
    62  	// newPlanForExplainDistSQL returns the DistSQL physical plan that can be
    63  	// used by the explainDistSQLNode to generate flow specs (and run in the case
    64  	// of EXPLAIN ANALYZE).
    65  	newPlanForExplainDistSQL(*PlanningCtx, *DistSQLPlanner) (*PhysicalPlan, error)
    66  }
    67  
    68  // willDistributePlanForExplainPurposes determines whether we will distribute
    69  // the given logical plan, based on the gateway's SQL ID and session settings.
    70  // It is similar to willDistributePlan but also pays attention to whether the
    71  // logical plan will be handled as a distributed job. It should *only* be used
    72  // in EXPLAIN variants.
    73  func willDistributePlanForExplainPurposes(
    74  	ctx context.Context,
    75  	nodeID *base.SQLIDContainer,
    76  	distSQLMode sessiondata.DistSQLExecMode,
    77  	plan planMaybePhysical,
    78  ) bool {
    79  	if !plan.isPhysicalPlan() {
    80  		if _, ok := plan.planNode.(distSQLExplainable); ok {
    81  			// This is a special case for plans that will be actually distributed
    82  			// but are represented using local plan nodes (for example, "create
    83  			// statistics" is handled by the jobs framework which is responsible
    84  			// for setting up the correct DistSQL infrastructure).
    85  			return true
    86  		}
    87  	}
    88  	return willDistributePlan(ctx, nodeID, distSQLMode, plan)
    89  }
    90  
    91  func (n *explainDistSQLNode) startExec(params runParams) error {
    92  	distSQLPlanner := params.extendedEvalCtx.DistSQLPlanner
    93  	willDistribute := willDistributePlanForExplainPurposes(
    94  		params.ctx, params.extendedEvalCtx.ExecCfg.NodeID,
    95  		params.extendedEvalCtx.SessionData.DistSQLMode, n.plan.main,
    96  	)
    97  	planCtx := distSQLPlanner.NewPlanningCtx(params.ctx, params.extendedEvalCtx, params.p.txn, willDistribute)
    98  	planCtx.ignoreClose = true
    99  	planCtx.planner = params.p
   100  	planCtx.stmtType = n.stmtType
   101  
   102  	if n.analyze && len(n.plan.cascades) > 0 {
   103  		return errors.New("running EXPLAIN ANALYZE (DISTSQL) on this query is " +
   104  			"unsupported because of the presence of cascades")
   105  	}
   106  
   107  	// In EXPLAIN ANALYZE mode, we need subqueries to be evaluated as normal.
   108  	// In EXPLAIN mode, we don't evaluate subqueries, and instead display their
   109  	// original text in the plan.
   110  	planCtx.noEvalSubqueries = !n.analyze
   111  
   112  	if n.analyze && len(n.plan.subqueryPlans) > 0 {
   113  		outerSubqueries := planCtx.planner.curPlan.subqueryPlans
   114  		defer func() {
   115  			planCtx.planner.curPlan.subqueryPlans = outerSubqueries
   116  		}()
   117  		planCtx.planner.curPlan.subqueryPlans = n.plan.subqueryPlans
   118  
   119  		// Discard rows that are returned.
   120  		rw := newCallbackResultWriter(func(ctx context.Context, row tree.Datums) error {
   121  			return nil
   122  		})
   123  		execCfg := params.p.ExecCfg()
   124  		recv := MakeDistSQLReceiver(
   125  			planCtx.ctx,
   126  			rw,
   127  			tree.Rows,
   128  			execCfg.RangeDescriptorCache,
   129  			execCfg.LeaseHolderCache,
   130  			params.p.txn,
   131  			func(ts hlc.Timestamp) {
   132  				execCfg.Clock.Update(ts)
   133  			},
   134  			params.extendedEvalCtx.Tracing,
   135  		)
   136  		if !distSQLPlanner.PlanAndRunSubqueries(
   137  			planCtx.ctx,
   138  			params.p,
   139  			params.extendedEvalCtx.copy,
   140  			n.plan.subqueryPlans,
   141  			recv,
   142  			willDistribute,
   143  		) {
   144  			if err := rw.Err(); err != nil {
   145  				return err
   146  			}
   147  			return recv.commErr
   148  		}
   149  	}
   150  
   151  	physPlan, err := newPhysPlanForExplainPurposes(planCtx, distSQLPlanner, n.plan.main)
   152  	if err != nil {
   153  		if len(n.plan.subqueryPlans) > 0 {
   154  			return errors.New("running EXPLAIN (DISTSQL) on this query is " +
   155  				"unsupported because of the presence of subqueries")
   156  		}
   157  		return err
   158  	}
   159  	distSQLPlanner.FinalizePlan(planCtx, physPlan)
   160  
   161  	var diagram execinfrapb.FlowDiagram
   162  	if n.analyze {
   163  		// TODO(andrei): We don't create a child span if the parent is already
   164  		// recording because we don't currently have a good way to ask for a
   165  		// separate recording for the child such that it's also guaranteed that we
   166  		// don't get a noopSpan.
   167  		var sp opentracing.Span
   168  		if parentSp := opentracing.SpanFromContext(params.ctx); parentSp != nil &&
   169  			!tracing.IsRecording(parentSp) {
   170  			tracer := parentSp.Tracer()
   171  			sp = tracer.StartSpan(
   172  				"explain-distsql", tracing.Recordable,
   173  				opentracing.ChildOf(parentSp.Context()),
   174  				tracing.LogTagsFromCtx(params.ctx))
   175  		} else {
   176  			tracer := params.extendedEvalCtx.ExecCfg.AmbientCtx.Tracer
   177  			sp = tracer.StartSpan(
   178  				"explain-distsql", tracing.Recordable,
   179  				tracing.LogTagsFromCtx(params.ctx))
   180  		}
   181  		tracing.StartRecording(sp, tracing.SnowballRecording)
   182  		ctx := opentracing.ContextWithSpan(params.ctx, sp)
   183  		planCtx.ctx = ctx
   184  		// Make a copy of the evalContext with the recording span in it; we can't
   185  		// change the original.
   186  		newEvalCtx := params.extendedEvalCtx.copy()
   187  		newEvalCtx.Context = ctx
   188  		newParams := params
   189  		newParams.extendedEvalCtx = newEvalCtx
   190  
   191  		// Discard rows that are returned.
   192  		rw := newCallbackResultWriter(func(ctx context.Context, row tree.Datums) error {
   193  			return nil
   194  		})
   195  		execCfg := newParams.p.ExecCfg()
   196  		const stmtType = tree.Rows
   197  		recv := MakeDistSQLReceiver(
   198  			planCtx.ctx,
   199  			rw,
   200  			stmtType,
   201  			execCfg.RangeDescriptorCache,
   202  			execCfg.LeaseHolderCache,
   203  			newParams.p.txn,
   204  			func(ts hlc.Timestamp) {
   205  				execCfg.Clock.Update(ts)
   206  			},
   207  			newParams.extendedEvalCtx.Tracing,
   208  		)
   209  		defer recv.Release()
   210  
   211  		planCtx.saveDiagram = func(d execinfrapb.FlowDiagram) {
   212  			diagram = d
   213  		}
   214  		planCtx.saveDiagramShowInputTypes = n.options.Flags[tree.ExplainFlagTypes]
   215  
   216  		distSQLPlanner.Run(
   217  			planCtx, newParams.p.txn, physPlan, recv, newParams.extendedEvalCtx, nil, /* finishedSetupFn */
   218  		)()
   219  
   220  		n.run.executedStatement = true
   221  
   222  		sp.Finish()
   223  		spans := tracing.GetRecording(sp)
   224  
   225  		if err := rw.Err(); err != nil {
   226  			return err
   227  		}
   228  		diagram.AddSpans(spans)
   229  	} else {
   230  		// TODO(asubiotto): This cast from SQLInstanceID to NodeID is temporary:
   231  		//  https://github.com/cockroachdb/cockroach/issues/49596
   232  		flows := physPlan.GenerateFlowSpecs(roachpb.NodeID(params.extendedEvalCtx.NodeID.SQLInstanceID()))
   233  		showInputTypes := n.options.Flags[tree.ExplainFlagTypes]
   234  		diagram, err = execinfrapb.GeneratePlanDiagram(params.p.stmt.String(), flows, showInputTypes)
   235  		if err != nil {
   236  			return err
   237  		}
   238  	}
   239  
   240  	if n.analyze && len(n.plan.checkPlans) > 0 {
   241  		outerChecks := planCtx.planner.curPlan.checkPlans
   242  		defer func() {
   243  			planCtx.planner.curPlan.checkPlans = outerChecks
   244  		}()
   245  		planCtx.planner.curPlan.checkPlans = n.plan.checkPlans
   246  
   247  		// Discard rows that are returned.
   248  		rw := newCallbackResultWriter(func(ctx context.Context, row tree.Datums) error {
   249  			return nil
   250  		})
   251  		execCfg := params.p.ExecCfg()
   252  		recv := MakeDistSQLReceiver(
   253  			planCtx.ctx,
   254  			rw,
   255  			tree.Rows,
   256  			execCfg.RangeDescriptorCache,
   257  			execCfg.LeaseHolderCache,
   258  			params.p.txn,
   259  			func(ts hlc.Timestamp) {
   260  				execCfg.Clock.Update(ts)
   261  			},
   262  			params.extendedEvalCtx.Tracing,
   263  		)
   264  		if !distSQLPlanner.PlanAndRunCascadesAndChecks(
   265  			planCtx.ctx,
   266  			params.p,
   267  			params.extendedEvalCtx.copy,
   268  			&n.plan,
   269  			recv,
   270  			willDistribute,
   271  		) {
   272  			if err := rw.Err(); err != nil {
   273  				return err
   274  			}
   275  			return recv.commErr
   276  		}
   277  	}
   278  
   279  	planJSON, planURL, err := diagram.ToURL()
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	n.run.values = tree.Datums{
   285  		tree.MakeDBool(tree.DBool(willDistribute)),
   286  		tree.NewDString(planURL.String()),
   287  		tree.NewDString(planJSON),
   288  	}
   289  	return nil
   290  }
   291  
   292  func (n *explainDistSQLNode) Next(runParams) (bool, error) {
   293  	if n.run.done {
   294  		return false, nil
   295  	}
   296  	n.run.done = true
   297  	return true, nil
   298  }
   299  
   300  func (n *explainDistSQLNode) Values() tree.Datums { return n.run.values }
   301  func (n *explainDistSQLNode) Close(ctx context.Context) {
   302  	n.plan.close(ctx)
   303  }
   304  
   305  func newPhysPlanForExplainPurposes(
   306  	planCtx *PlanningCtx, distSQLPlanner *DistSQLPlanner, plan planMaybePhysical,
   307  ) (*PhysicalPlan, error) {
   308  	if plan.isPhysicalPlan() {
   309  		return plan.physPlan, nil
   310  	}
   311  	var physPlan *PhysicalPlan
   312  	var err error
   313  	if planNode, ok := plan.planNode.(distSQLExplainable); ok {
   314  		physPlan, err = planNode.newPlanForExplainDistSQL(planCtx, distSQLPlanner)
   315  	} else {
   316  		physPlan, err = distSQLPlanner.createPhysPlanForPlanNode(planCtx, plan.planNode)
   317  	}
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	return physPlan, nil
   322  }