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 }