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 }