1 /*------------------------------------------------------------------------- 2 * 3 * explain.c 4 * Explain query execution plans 5 * 6 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group 7 * Portions Copyright (c) 1994-5, Regents of the University of California 8 * 9 * IDENTIFICATION 10 * src/backend/commands/explain.c 11 * 12 *------------------------------------------------------------------------- 13 */ 14 #include "postgres.h" 15 16 #include "access/xact.h" 17 #include "catalog/pg_type.h" 18 #include "commands/createas.h" 19 #include "commands/defrem.h" 20 #include "commands/prepare.h" 21 #include "executor/nodeHash.h" 22 #include "foreign/fdwapi.h" 23 #include "jit/jit.h" 24 #include "nodes/extensible.h" 25 #include "nodes/makefuncs.h" 26 #include "nodes/nodeFuncs.h" 27 #include "parser/parsetree.h" 28 #include "rewrite/rewriteHandler.h" 29 #include "storage/bufmgr.h" 30 #include "tcop/tcopprot.h" 31 #include "utils/builtins.h" 32 #include "utils/guc_tables.h" 33 #include "utils/json.h" 34 #include "utils/lsyscache.h" 35 #include "utils/rel.h" 36 #include "utils/ruleutils.h" 37 #include "utils/snapmgr.h" 38 #include "utils/tuplesort.h" 39 #include "utils/typcache.h" 40 #include "utils/xml.h" 41 42 43 /* Hook for plugins to get control in ExplainOneQuery() */ 44 ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL; 45 46 /* Hook for plugins to get control in explain_get_index_name() */ 47 explain_get_index_name_hook_type explain_get_index_name_hook = NULL; 48 49 50 /* OR-able flags for ExplainXMLTag() */ 51 #define X_OPENING 0 52 #define X_CLOSING 1 53 #define X_CLOSE_IMMEDIATE 2 54 #define X_NOWHITESPACE 4 55 56 static void ExplainOneQuery(Query *query, int cursorOptions, 57 IntoClause *into, ExplainState *es, 58 const char *queryString, ParamListInfo params, 59 QueryEnvironment *queryEnv); 60 static void ExplainPrintJIT(ExplainState *es, int jit_flags, 61 JitInstrumentation *ji); 62 static void report_triggers(ResultRelInfo *rInfo, bool show_relname, 63 ExplainState *es); 64 static double elapsed_time(instr_time *starttime); 65 static bool ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used); 66 static void ExplainNode(PlanState *planstate, List *ancestors, 67 const char *relationship, const char *plan_name, 68 ExplainState *es); 69 static void show_plan_tlist(PlanState *planstate, List *ancestors, 70 ExplainState *es); 71 static void show_expression(Node *node, const char *qlabel, 72 PlanState *planstate, List *ancestors, 73 bool useprefix, ExplainState *es); 74 static void show_qual(List *qual, const char *qlabel, 75 PlanState *planstate, List *ancestors, 76 bool useprefix, ExplainState *es); 77 static void show_scan_qual(List *qual, const char *qlabel, 78 PlanState *planstate, List *ancestors, 79 ExplainState *es); 80 static void show_upper_qual(List *qual, const char *qlabel, 81 PlanState *planstate, List *ancestors, 82 ExplainState *es); 83 static void show_sort_keys(SortState *sortstate, List *ancestors, 84 ExplainState *es); 85 static void show_incremental_sort_keys(IncrementalSortState *incrsortstate, 86 List *ancestors, ExplainState *es); 87 static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors, 88 ExplainState *es); 89 static void show_agg_keys(AggState *astate, List *ancestors, 90 ExplainState *es); 91 static void show_grouping_sets(PlanState *planstate, Agg *agg, 92 List *ancestors, ExplainState *es); 93 static void show_grouping_set_keys(PlanState *planstate, 94 Agg *aggnode, Sort *sortnode, 95 List *context, bool useprefix, 96 List *ancestors, ExplainState *es); 97 static void show_group_keys(GroupState *gstate, List *ancestors, 98 ExplainState *es); 99 static void show_sort_group_keys(PlanState *planstate, const char *qlabel, 100 int nkeys, int nPresortedKeys, AttrNumber *keycols, 101 Oid *sortOperators, Oid *collations, bool *nullsFirst, 102 List *ancestors, ExplainState *es); 103 static void show_sortorder_options(StringInfo buf, Node *sortexpr, 104 Oid sortOperator, Oid collation, bool nullsFirst); 105 static void show_tablesample(TableSampleClause *tsc, PlanState *planstate, 106 List *ancestors, ExplainState *es); 107 static void show_sort_info(SortState *sortstate, ExplainState *es); 108 static void show_incremental_sort_info(IncrementalSortState *incrsortstate, 109 ExplainState *es); 110 static void show_hash_info(HashState *hashstate, ExplainState *es); 111 static void show_hashagg_info(AggState *hashstate, ExplainState *es); 112 static void show_tidbitmap_info(BitmapHeapScanState *planstate, 113 ExplainState *es); 114 static void show_instrumentation_count(const char *qlabel, int which, 115 PlanState *planstate, ExplainState *es); 116 static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es); 117 static void show_eval_params(Bitmapset *bms_params, ExplainState *es); 118 static const char *explain_get_index_name(Oid indexId); 119 static void show_buffer_usage(ExplainState *es, const BufferUsage *usage, 120 bool planning); 121 static void show_wal_usage(ExplainState *es, const WalUsage *usage); 122 static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir, 123 ExplainState *es); 124 static void ExplainScanTarget(Scan *plan, ExplainState *es); 125 static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es); 126 static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es); 127 static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors, 128 ExplainState *es); 129 static void ExplainMemberNodes(PlanState **planstates, int nplans, 130 List *ancestors, ExplainState *es); 131 static void ExplainMissingMembers(int nplans, int nchildren, ExplainState *es); 132 static void ExplainSubPlans(List *plans, List *ancestors, 133 const char *relationship, ExplainState *es); 134 static void ExplainCustomChildren(CustomScanState *css, 135 List *ancestors, ExplainState *es); 136 static ExplainWorkersState *ExplainCreateWorkersState(int num_workers); 137 static void ExplainOpenWorker(int n, ExplainState *es); 138 static void ExplainCloseWorker(int n, ExplainState *es); 139 static void ExplainFlushWorkersState(ExplainState *es); 140 static void ExplainProperty(const char *qlabel, const char *unit, 141 const char *value, bool numeric, ExplainState *es); 142 static void ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, 143 bool labeled, int depth, ExplainState *es); 144 static void ExplainSaveGroup(ExplainState *es, int depth, int *state_save); 145 static void ExplainRestoreGroup(ExplainState *es, int depth, int *state_save); 146 static void ExplainDummyGroup(const char *objtype, const char *labelname, 147 ExplainState *es); 148 static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); 149 static void ExplainIndentText(ExplainState *es); 150 static void ExplainJSONLineEnding(ExplainState *es); 151 static void ExplainYAMLLineStarting(ExplainState *es); 152 static void escape_yaml(StringInfo buf, const char *str); 153 154 155 156 /* 157 * ExplainQuery - 158 * execute an EXPLAIN command 159 */ 160 void 161 ExplainQuery(ParseState *pstate, ExplainStmt *stmt, 162 ParamListInfo params, DestReceiver *dest) 163 { 164 ExplainState *es = NewExplainState(); 165 TupOutputState *tstate; 166 List *rewritten; 167 ListCell *lc; 168 bool timing_set = false; 169 bool summary_set = false; 170 171 /* Parse options list. */ 172 foreach(lc, stmt->options) 173 { 174 DefElem *opt = (DefElem *) lfirst(lc); 175 176 if (strcmp(opt->defname, "analyze") == 0) 177 es->analyze = defGetBoolean(opt); 178 else if (strcmp(opt->defname, "verbose") == 0) 179 es->verbose = defGetBoolean(opt); 180 else if (strcmp(opt->defname, "costs") == 0) 181 es->costs = defGetBoolean(opt); 182 else if (strcmp(opt->defname, "buffers") == 0) 183 es->buffers = defGetBoolean(opt); 184 else if (strcmp(opt->defname, "wal") == 0) 185 es->wal = defGetBoolean(opt); 186 else if (strcmp(opt->defname, "settings") == 0) 187 es->settings = defGetBoolean(opt); 188 else if (strcmp(opt->defname, "timing") == 0) 189 { 190 timing_set = true; 191 es->timing = defGetBoolean(opt); 192 } 193 else if (strcmp(opt->defname, "summary") == 0) 194 { 195 summary_set = true; 196 es->summary = defGetBoolean(opt); 197 } 198 else if (strcmp(opt->defname, "format") == 0) 199 { 200 char *p = defGetString(opt); 201 202 if (strcmp(p, "text") == 0) 203 es->format = EXPLAIN_FORMAT_TEXT; 204 else if (strcmp(p, "xml") == 0) 205 es->format = EXPLAIN_FORMAT_XML; 206 else if (strcmp(p, "json") == 0) 207 es->format = EXPLAIN_FORMAT_JSON; 208 else if (strcmp(p, "yaml") == 0) 209 es->format = EXPLAIN_FORMAT_YAML; 210 else 211 ereport(ERROR, 212 (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 213 errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"", 214 opt->defname, p), 215 parser_errposition(pstate, opt->location))); 216 } 217 else 218 ereport(ERROR, 219 (errcode(ERRCODE_SYNTAX_ERROR), 220 errmsg("unrecognized EXPLAIN option \"%s\"", 221 opt->defname), 222 parser_errposition(pstate, opt->location))); 223 } 224 225 if (es->wal && !es->analyze) 226 ereport(ERROR, 227 (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 228 errmsg("EXPLAIN option WAL requires ANALYZE"))); 229 230 /* if the timing was not set explicitly, set default value */ 231 es->timing = (timing_set) ? es->timing : es->analyze; 232 233 /* check that timing is used with EXPLAIN ANALYZE */ 234 if (es->timing && !es->analyze) 235 ereport(ERROR, 236 (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 237 errmsg("EXPLAIN option TIMING requires ANALYZE"))); 238 239 /* if the summary was not set explicitly, set default value */ 240 es->summary = (summary_set) ? es->summary : es->analyze; 241 242 /* 243 * Parse analysis was done already, but we still have to run the rule 244 * rewriter. We do not do AcquireRewriteLocks: we assume the query either 245 * came straight from the parser, or suitable locks were acquired by 246 * plancache.c. 247 * 248 * Because the rewriter and planner tend to scribble on the input, we make 249 * a preliminary copy of the source querytree. This prevents problems in 250 * the case that the EXPLAIN is in a portal or plpgsql function and is 251 * executed repeatedly. (See also the same hack in DECLARE CURSOR and 252 * PREPARE.) XXX FIXME someday. 253 */ 254 rewritten = QueryRewrite(castNode(Query, copyObject(stmt->query))); 255 256 /* emit opening boilerplate */ 257 ExplainBeginOutput(es); 258 259 if (rewritten == NIL) 260 { 261 /* 262 * In the case of an INSTEAD NOTHING, tell at least that. But in 263 * non-text format, the output is delimited, so this isn't necessary. 264 */ 265 if (es->format == EXPLAIN_FORMAT_TEXT) 266 appendStringInfoString(es->str, "Query rewrites to nothing\n"); 267 } 268 else 269 { 270 ListCell *l; 271 272 /* Explain every plan */ 273 foreach(l, rewritten) 274 { 275 ExplainOneQuery(lfirst_node(Query, l), 276 CURSOR_OPT_PARALLEL_OK, NULL, es, 277 pstate->p_sourcetext, params, pstate->p_queryEnv); 278 279 /* Separate plans with an appropriate separator */ 280 if (lnext(rewritten, l) != NULL) 281 ExplainSeparatePlans(es); 282 } 283 } 284 285 /* emit closing boilerplate */ 286 ExplainEndOutput(es); 287 Assert(es->indent == 0); 288 289 /* output tuples */ 290 tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt), 291 &TTSOpsVirtual); 292 if (es->format == EXPLAIN_FORMAT_TEXT) 293 do_text_output_multiline(tstate, es->str->data); 294 else 295 do_text_output_oneline(tstate, es->str->data); 296 end_tup_output(tstate); 297 298 pfree(es->str->data); 299 } 300 301 /* 302 * Create a new ExplainState struct initialized with default options. 303 */ 304 ExplainState * 305 NewExplainState(void) 306 { 307 ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState)); 308 309 /* Set default options (most fields can be left as zeroes). */ 310 es->costs = true; 311 /* Prepare output buffer. */ 312 es->str = makeStringInfo(); 313 314 return es; 315 } 316 317 /* 318 * ExplainResultDesc - 319 * construct the result tupledesc for an EXPLAIN 320 */ 321 TupleDesc 322 ExplainResultDesc(ExplainStmt *stmt) 323 { 324 TupleDesc tupdesc; 325 ListCell *lc; 326 Oid result_type = TEXTOID; 327 328 /* Check for XML format option */ 329 foreach(lc, stmt->options) 330 { 331 DefElem *opt = (DefElem *) lfirst(lc); 332 333 if (strcmp(opt->defname, "format") == 0) 334 { 335 char *p = defGetString(opt); 336 337 if (strcmp(p, "xml") == 0) 338 result_type = XMLOID; 339 else if (strcmp(p, "json") == 0) 340 result_type = JSONOID; 341 else 342 result_type = TEXTOID; 343 /* don't "break", as ExplainQuery will use the last value */ 344 } 345 } 346 347 /* Need a tuple descriptor representing a single TEXT or XML column */ 348 tupdesc = CreateTemplateTupleDesc(1); 349 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN", 350 result_type, -1, 0); 351 return tupdesc; 352 } 353 354 /* 355 * ExplainOneQuery - 356 * print out the execution plan for one Query 357 * 358 * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt. 359 */ 360 static void 361 ExplainOneQuery(Query *query, int cursorOptions, 362 IntoClause *into, ExplainState *es, 363 const char *queryString, ParamListInfo params, 364 QueryEnvironment *queryEnv) 365 { 366 /* planner will not cope with utility statements */ 367 if (query->commandType == CMD_UTILITY) 368 { 369 ExplainOneUtility(query->utilityStmt, into, es, queryString, params, 370 queryEnv); 371 return; 372 } 373 374 /* if an advisor plugin is present, let it manage things */ 375 if (ExplainOneQuery_hook) 376 (*ExplainOneQuery_hook) (query, cursorOptions, into, es, 377 queryString, params, queryEnv); 378 else 379 { 380 PlannedStmt *plan; 381 instr_time planstart, 382 planduration; 383 BufferUsage bufusage_start, 384 bufusage; 385 386 if (es->buffers) 387 bufusage_start = pgBufferUsage; 388 INSTR_TIME_SET_CURRENT(planstart); 389 390 /* plan the query */ 391 plan = pg_plan_query(query, queryString, cursorOptions, params); 392 393 INSTR_TIME_SET_CURRENT(planduration); 394 INSTR_TIME_SUBTRACT(planduration, planstart); 395 396 /* calc differences of buffer counters. */ 397 if (es->buffers) 398 { 399 memset(&bufusage, 0, sizeof(BufferUsage)); 400 BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start); 401 } 402 403 /* run it (if needed) and produce output */ 404 ExplainOnePlan(plan, into, es, queryString, params, queryEnv, 405 &planduration, (es->buffers ? &bufusage : NULL)); 406 } 407 } 408 409 /* 410 * ExplainOneUtility - 411 * print out the execution plan for one utility statement 412 * (In general, utility statements don't have plans, but there are some 413 * we treat as special cases) 414 * 415 * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt. 416 * 417 * This is exported because it's called back from prepare.c in the 418 * EXPLAIN EXECUTE case. 419 */ 420 void 421 ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, 422 const char *queryString, ParamListInfo params, 423 QueryEnvironment *queryEnv) 424 { 425 if (utilityStmt == NULL) 426 return; 427 428 if (IsA(utilityStmt, CreateTableAsStmt)) 429 { 430 /* 431 * We have to rewrite the contained SELECT and then pass it back to 432 * ExplainOneQuery. It's probably not really necessary to copy the 433 * contained parsetree another time, but let's be safe. 434 */ 435 CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt; 436 List *rewritten; 437 438 rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query))); 439 Assert(list_length(rewritten) == 1); 440 ExplainOneQuery(linitial_node(Query, rewritten), 441 CURSOR_OPT_PARALLEL_OK, ctas->into, es, 442 queryString, params, queryEnv); 443 } 444 else if (IsA(utilityStmt, DeclareCursorStmt)) 445 { 446 /* 447 * Likewise for DECLARE CURSOR. 448 * 449 * Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll 450 * actually run the query. This is different from pre-8.3 behavior 451 * but seems more useful than not running the query. No cursor will 452 * be created, however. 453 */ 454 DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt; 455 List *rewritten; 456 457 rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query))); 458 Assert(list_length(rewritten) == 1); 459 ExplainOneQuery(linitial_node(Query, rewritten), 460 dcs->options, NULL, es, 461 queryString, params, queryEnv); 462 } 463 else if (IsA(utilityStmt, ExecuteStmt)) 464 ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es, 465 queryString, params, queryEnv); 466 else if (IsA(utilityStmt, NotifyStmt)) 467 { 468 if (es->format == EXPLAIN_FORMAT_TEXT) 469 appendStringInfoString(es->str, "NOTIFY\n"); 470 else 471 ExplainDummyGroup("Notify", NULL, es); 472 } 473 else 474 { 475 if (es->format == EXPLAIN_FORMAT_TEXT) 476 appendStringInfoString(es->str, 477 "Utility statements have no plan structure\n"); 478 else 479 ExplainDummyGroup("Utility Statement", NULL, es); 480 } 481 } 482 483 /* 484 * ExplainOnePlan - 485 * given a planned query, execute it if needed, and then print 486 * EXPLAIN output 487 * 488 * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt, 489 * in which case executing the query should result in creating that table. 490 * 491 * This is exported because it's called back from prepare.c in the 492 * EXPLAIN EXECUTE case, and because an index advisor plugin would need 493 * to call it. 494 */ 495 void 496 ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, 497 const char *queryString, ParamListInfo params, 498 QueryEnvironment *queryEnv, const instr_time *planduration, 499 const BufferUsage *bufusage) 500 { 501 DestReceiver *dest; 502 QueryDesc *queryDesc; 503 instr_time starttime; 504 double totaltime = 0; 505 int eflags; 506 int instrument_option = 0; 507 508 Assert(plannedstmt->commandType != CMD_UTILITY); 509 510 if (es->analyze && es->timing) 511 instrument_option |= INSTRUMENT_TIMER; 512 else if (es->analyze) 513 instrument_option |= INSTRUMENT_ROWS; 514 515 if (es->buffers) 516 instrument_option |= INSTRUMENT_BUFFERS; 517 if (es->wal) 518 instrument_option |= INSTRUMENT_WAL; 519 520 /* 521 * We always collect timing for the entire statement, even when node-level 522 * timing is off, so we don't look at es->timing here. (We could skip 523 * this if !es->summary, but it's hardly worth the complication.) 524 */ 525 INSTR_TIME_SET_CURRENT(starttime); 526 527 /* 528 * Use a snapshot with an updated command ID to ensure this query sees 529 * results of any previously executed queries. 530 */ 531 PushCopiedSnapshot(GetActiveSnapshot()); 532 UpdateActiveSnapshotCommandId(); 533 534 /* 535 * Normally we discard the query's output, but if explaining CREATE TABLE 536 * AS, we'd better use the appropriate tuple receiver. 537 */ 538 if (into) 539 dest = CreateIntoRelDestReceiver(into); 540 else 541 dest = None_Receiver; 542 543 /* Create a QueryDesc for the query */ 544 queryDesc = CreateQueryDesc(plannedstmt, queryString, 545 GetActiveSnapshot(), InvalidSnapshot, 546 dest, params, queryEnv, instrument_option); 547 548 /* Select execution options */ 549 if (es->analyze) 550 eflags = 0; /* default run-to-completion flags */ 551 else 552 eflags = EXEC_FLAG_EXPLAIN_ONLY; 553 if (into) 554 eflags |= GetIntoRelEFlags(into); 555 556 /* call ExecutorStart to prepare the plan for execution */ 557 ExecutorStart(queryDesc, eflags); 558 559 /* Execute the plan for statistics if asked for */ 560 if (es->analyze) 561 { 562 ScanDirection dir; 563 564 /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */ 565 if (into && into->skipData) 566 dir = NoMovementScanDirection; 567 else 568 dir = ForwardScanDirection; 569 570 /* run the plan */ 571 ExecutorRun(queryDesc, dir, 0L, true); 572 573 /* run cleanup too */ 574 ExecutorFinish(queryDesc); 575 576 /* We can't run ExecutorEnd 'till we're done printing the stats... */ 577 totaltime += elapsed_time(&starttime); 578 } 579 580 ExplainOpenGroup("Query", NULL, true, es); 581 582 /* Create textual dump of plan tree */ 583 ExplainPrintPlan(es, queryDesc); 584 585 /* Show buffer usage in planning */ 586 if (bufusage) 587 { 588 ExplainOpenGroup("Planning", "Planning", true, es); 589 show_buffer_usage(es, bufusage, true); 590 ExplainCloseGroup("Planning", "Planning", true, es); 591 } 592 593 if (es->summary && planduration) 594 { 595 double plantime = INSTR_TIME_GET_DOUBLE(*planduration); 596 597 ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es); 598 } 599 600 /* Print info about runtime of triggers */ 601 if (es->analyze) 602 ExplainPrintTriggers(es, queryDesc); 603 604 /* 605 * Print info about JITing. Tied to es->costs because we don't want to 606 * display this in regression tests, as it'd cause output differences 607 * depending on build options. Might want to separate that out from COSTS 608 * at a later stage. 609 */ 610 if (es->costs) 611 ExplainPrintJITSummary(es, queryDesc); 612 613 /* 614 * Close down the query and free resources. Include time for this in the 615 * total execution time (although it should be pretty minimal). 616 */ 617 INSTR_TIME_SET_CURRENT(starttime); 618 619 ExecutorEnd(queryDesc); 620 621 FreeQueryDesc(queryDesc); 622 623 PopActiveSnapshot(); 624 625 /* We need a CCI just in case query expanded to multiple plans */ 626 if (es->analyze) 627 CommandCounterIncrement(); 628 629 totaltime += elapsed_time(&starttime); 630 631 /* 632 * We only report execution time if we actually ran the query (that is, 633 * the user specified ANALYZE), and if summary reporting is enabled (the 634 * user can set SUMMARY OFF to not have the timing information included in 635 * the output). By default, ANALYZE sets SUMMARY to true. 636 */ 637 if (es->summary && es->analyze) 638 ExplainPropertyFloat("Execution Time", "ms", 1000.0 * totaltime, 3, 639 es); 640 641 ExplainCloseGroup("Query", NULL, true, es); 642 } 643 644 /* 645 * ExplainPrintSettings - 646 * Print summary of modified settings affecting query planning. 647 */ 648 static void 649 ExplainPrintSettings(ExplainState *es) 650 { 651 int num; 652 struct config_generic **gucs; 653 654 /* bail out if information about settings not requested */ 655 if (!es->settings) 656 return; 657 658 /* request an array of relevant settings */ 659 gucs = get_explain_guc_options(&num); 660 661 if (es->format != EXPLAIN_FORMAT_TEXT) 662 { 663 ExplainOpenGroup("Settings", "Settings", true, es); 664 665 for (int i = 0; i < num; i++) 666 { 667 char *setting; 668 struct config_generic *conf = gucs[i]; 669 670 setting = GetConfigOptionByName(conf->name, NULL, true); 671 672 ExplainPropertyText(conf->name, setting, es); 673 } 674 675 ExplainCloseGroup("Settings", "Settings", true, es); 676 } 677 else 678 { 679 StringInfoData str; 680 681 /* In TEXT mode, print nothing if there are no options */ 682 if (num <= 0) 683 return; 684 685 initStringInfo(&str); 686 687 for (int i = 0; i < num; i++) 688 { 689 char *setting; 690 struct config_generic *conf = gucs[i]; 691 692 if (i > 0) 693 appendStringInfoString(&str, ", "); 694 695 setting = GetConfigOptionByName(conf->name, NULL, true); 696 697 if (setting) 698 appendStringInfo(&str, "%s = '%s'", conf->name, setting); 699 else 700 appendStringInfo(&str, "%s = NULL", conf->name); 701 } 702 703 ExplainPropertyText("Settings", str.data, es); 704 } 705 } 706 707 /* 708 * ExplainPrintPlan - 709 * convert a QueryDesc's plan tree to text and append it to es->str 710 * 711 * The caller should have set up the options fields of *es, as well as 712 * initializing the output buffer es->str. Also, output formatting state 713 * such as the indent level is assumed valid. Plan-tree-specific fields 714 * in *es are initialized here. 715 * 716 * NB: will not work on utility statements 717 */ 718 void 719 ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc) 720 { 721 Bitmapset *rels_used = NULL; 722 PlanState *ps; 723 724 /* Set up ExplainState fields associated with this plan tree */ 725 Assert(queryDesc->plannedstmt != NULL); 726 es->pstmt = queryDesc->plannedstmt; 727 es->rtable = queryDesc->plannedstmt->rtable; 728 ExplainPreScanNode(queryDesc->planstate, &rels_used); 729 es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used); 730 es->deparse_cxt = deparse_context_for_plan_tree(queryDesc->plannedstmt, 731 es->rtable_names); 732 es->printed_subplans = NULL; 733 734 /* 735 * Sometimes we mark a Gather node as "invisible", which means that it's 736 * not to be displayed in EXPLAIN output. The purpose of this is to allow 737 * running regression tests with force_parallel_mode=regress to get the 738 * same results as running the same tests with force_parallel_mode=off. 739 * Such marking is currently only supported on a Gather at the top of the 740 * plan. We skip that node, and we must also hide per-worker detail data 741 * further down in the plan tree. 742 */ 743 ps = queryDesc->planstate; 744 if (IsA(ps, GatherState) && ((Gather *) ps->plan)->invisible) 745 { 746 ps = outerPlanState(ps); 747 es->hide_workers = true; 748 } 749 ExplainNode(ps, NIL, NULL, NULL, es); 750 751 /* 752 * If requested, include information about GUC parameters with values that 753 * don't match the built-in defaults. 754 */ 755 ExplainPrintSettings(es); 756 } 757 758 /* 759 * ExplainPrintTriggers - 760 * convert a QueryDesc's trigger statistics to text and append it to 761 * es->str 762 * 763 * The caller should have set up the options fields of *es, as well as 764 * initializing the output buffer es->str. Other fields in *es are 765 * initialized here. 766 */ 767 void 768 ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc) 769 { 770 ResultRelInfo *rInfo; 771 bool show_relname; 772 int numrels = queryDesc->estate->es_num_result_relations; 773 int numrootrels = queryDesc->estate->es_num_root_result_relations; 774 List *routerels; 775 List *targrels; 776 int nr; 777 ListCell *l; 778 779 routerels = queryDesc->estate->es_tuple_routing_result_relations; 780 targrels = queryDesc->estate->es_trig_target_relations; 781 782 ExplainOpenGroup("Triggers", "Triggers", false, es); 783 784 show_relname = (numrels > 1 || numrootrels > 0 || 785 routerels != NIL || targrels != NIL); 786 rInfo = queryDesc->estate->es_result_relations; 787 for (nr = 0; nr < numrels; rInfo++, nr++) 788 report_triggers(rInfo, show_relname, es); 789 790 rInfo = queryDesc->estate->es_root_result_relations; 791 for (nr = 0; nr < numrootrels; rInfo++, nr++) 792 report_triggers(rInfo, show_relname, es); 793 794 foreach(l, routerels) 795 { 796 rInfo = (ResultRelInfo *) lfirst(l); 797 report_triggers(rInfo, show_relname, es); 798 } 799 800 foreach(l, targrels) 801 { 802 rInfo = (ResultRelInfo *) lfirst(l); 803 report_triggers(rInfo, show_relname, es); 804 } 805 806 ExplainCloseGroup("Triggers", "Triggers", false, es); 807 } 808 809 /* 810 * ExplainPrintJITSummary - 811 * Print summarized JIT instrumentation from leader and workers 812 */ 813 void 814 ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc) 815 { 816 JitInstrumentation ji = {0}; 817 818 if (!(queryDesc->estate->es_jit_flags & PGJIT_PERFORM)) 819 return; 820 821 /* 822 * Work with a copy instead of modifying the leader state, since this 823 * function may be called twice 824 */ 825 if (queryDesc->estate->es_jit) 826 InstrJitAgg(&ji, &queryDesc->estate->es_jit->instr); 827 828 /* If this process has done JIT in parallel workers, merge stats */ 829 if (queryDesc->estate->es_jit_worker_instr) 830 InstrJitAgg(&ji, queryDesc->estate->es_jit_worker_instr); 831 832 ExplainPrintJIT(es, queryDesc->estate->es_jit_flags, &ji); 833 } 834 835 /* 836 * ExplainPrintJIT - 837 * Append information about JITing to es->str. 838 */ 839 static void 840 ExplainPrintJIT(ExplainState *es, int jit_flags, JitInstrumentation *ji) 841 { 842 instr_time total_time; 843 844 /* don't print information if no JITing happened */ 845 if (!ji || ji->created_functions == 0) 846 return; 847 848 /* calculate total time */ 849 INSTR_TIME_SET_ZERO(total_time); 850 INSTR_TIME_ADD(total_time, ji->generation_counter); 851 INSTR_TIME_ADD(total_time, ji->inlining_counter); 852 INSTR_TIME_ADD(total_time, ji->optimization_counter); 853 INSTR_TIME_ADD(total_time, ji->emission_counter); 854 855 ExplainOpenGroup("JIT", "JIT", true, es); 856 857 /* for higher density, open code the text output format */ 858 if (es->format == EXPLAIN_FORMAT_TEXT) 859 { 860 ExplainIndentText(es); 861 appendStringInfoString(es->str, "JIT:\n"); 862 es->indent++; 863 864 ExplainPropertyInteger("Functions", NULL, ji->created_functions, es); 865 866 ExplainIndentText(es); 867 appendStringInfo(es->str, "Options: %s %s, %s %s, %s %s, %s %s\n", 868 "Inlining", jit_flags & PGJIT_INLINE ? "true" : "false", 869 "Optimization", jit_flags & PGJIT_OPT3 ? "true" : "false", 870 "Expressions", jit_flags & PGJIT_EXPR ? "true" : "false", 871 "Deforming", jit_flags & PGJIT_DEFORM ? "true" : "false"); 872 873 if (es->analyze && es->timing) 874 { 875 ExplainIndentText(es); 876 appendStringInfo(es->str, 877 "Timing: %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms\n", 878 "Generation", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter), 879 "Inlining", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter), 880 "Optimization", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter), 881 "Emission", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter), 882 "Total", 1000.0 * INSTR_TIME_GET_DOUBLE(total_time)); 883 } 884 885 es->indent--; 886 } 887 else 888 { 889 ExplainPropertyInteger("Functions", NULL, ji->created_functions, es); 890 891 ExplainOpenGroup("Options", "Options", true, es); 892 ExplainPropertyBool("Inlining", jit_flags & PGJIT_INLINE, es); 893 ExplainPropertyBool("Optimization", jit_flags & PGJIT_OPT3, es); 894 ExplainPropertyBool("Expressions", jit_flags & PGJIT_EXPR, es); 895 ExplainPropertyBool("Deforming", jit_flags & PGJIT_DEFORM, es); 896 ExplainCloseGroup("Options", "Options", true, es); 897 898 if (es->analyze && es->timing) 899 { 900 ExplainOpenGroup("Timing", "Timing", true, es); 901 902 ExplainPropertyFloat("Generation", "ms", 903 1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter), 904 3, es); 905 ExplainPropertyFloat("Inlining", "ms", 906 1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter), 907 3, es); 908 ExplainPropertyFloat("Optimization", "ms", 909 1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter), 910 3, es); 911 ExplainPropertyFloat("Emission", "ms", 912 1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter), 913 3, es); 914 ExplainPropertyFloat("Total", "ms", 915 1000.0 * INSTR_TIME_GET_DOUBLE(total_time), 916 3, es); 917 918 ExplainCloseGroup("Timing", "Timing", true, es); 919 } 920 } 921 922 ExplainCloseGroup("JIT", "JIT", true, es); 923 } 924 925 /* 926 * ExplainQueryText - 927 * add a "Query Text" node that contains the actual text of the query 928 * 929 * The caller should have set up the options fields of *es, as well as 930 * initializing the output buffer es->str. 931 * 932 */ 933 void 934 ExplainQueryText(ExplainState *es, QueryDesc *queryDesc) 935 { 936 if (queryDesc->sourceText) 937 ExplainPropertyText("Query Text", queryDesc->sourceText, es); 938 } 939 940 /* 941 * report_triggers - 942 * report execution stats for a single relation's triggers 943 */ 944 static void 945 report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es) 946 { 947 int nt; 948 949 if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) 950 return; 951 for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++) 952 { 953 Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; 954 Instrumentation *instr = rInfo->ri_TrigInstrument + nt; 955 char *relname; 956 char *conname = NULL; 957 958 /* Must clean up instrumentation state */ 959 InstrEndLoop(instr); 960 961 /* 962 * We ignore triggers that were never invoked; they likely aren't 963 * relevant to the current query type. 964 */ 965 if (instr->ntuples == 0) 966 continue; 967 968 ExplainOpenGroup("Trigger", NULL, true, es); 969 970 relname = RelationGetRelationName(rInfo->ri_RelationDesc); 971 if (OidIsValid(trig->tgconstraint)) 972 conname = get_constraint_name(trig->tgconstraint); 973 974 /* 975 * In text format, we avoid printing both the trigger name and the 976 * constraint name unless VERBOSE is specified. In non-text formats 977 * we just print everything. 978 */ 979 if (es->format == EXPLAIN_FORMAT_TEXT) 980 { 981 if (es->verbose || conname == NULL) 982 appendStringInfo(es->str, "Trigger %s", trig->tgname); 983 else 984 appendStringInfoString(es->str, "Trigger"); 985 if (conname) 986 appendStringInfo(es->str, " for constraint %s", conname); 987 if (show_relname) 988 appendStringInfo(es->str, " on %s", relname); 989 if (es->timing) 990 appendStringInfo(es->str, ": time=%.3f calls=%.0f\n", 991 1000.0 * instr->total, instr->ntuples); 992 else 993 appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples); 994 } 995 else 996 { 997 ExplainPropertyText("Trigger Name", trig->tgname, es); 998 if (conname) 999 ExplainPropertyText("Constraint Name", conname, es); 1000 ExplainPropertyText("Relation", relname, es); 1001 if (es->timing) 1002 ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3, 1003 es); 1004 ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es); 1005 } 1006 1007 if (conname) 1008 pfree(conname); 1009 1010 ExplainCloseGroup("Trigger", NULL, true, es); 1011 } 1012 } 1013 1014 /* Compute elapsed time in seconds since given timestamp */ 1015 static double 1016 elapsed_time(instr_time *starttime) 1017 { 1018 instr_time endtime; 1019 1020 INSTR_TIME_SET_CURRENT(endtime); 1021 INSTR_TIME_SUBTRACT(endtime, *starttime); 1022 return INSTR_TIME_GET_DOUBLE(endtime); 1023 } 1024 1025 /* 1026 * ExplainPreScanNode - 1027 * Prescan the planstate tree to identify which RTEs are referenced 1028 * 1029 * Adds the relid of each referenced RTE to *rels_used. The result controls 1030 * which RTEs are assigned aliases by select_rtable_names_for_explain. 1031 * This ensures that we don't confusingly assign un-suffixed aliases to RTEs 1032 * that never appear in the EXPLAIN output (such as inheritance parents). 1033 */ 1034 static bool 1035 ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) 1036 { 1037 Plan *plan = planstate->plan; 1038 1039 switch (nodeTag(plan)) 1040 { 1041 case T_SeqScan: 1042 case T_SampleScan: 1043 case T_IndexScan: 1044 case T_IndexOnlyScan: 1045 case T_BitmapHeapScan: 1046 case T_TidScan: 1047 case T_SubqueryScan: 1048 case T_FunctionScan: 1049 case T_TableFuncScan: 1050 case T_ValuesScan: 1051 case T_CteScan: 1052 case T_NamedTuplestoreScan: 1053 case T_WorkTableScan: 1054 *rels_used = bms_add_member(*rels_used, 1055 ((Scan *) plan)->scanrelid); 1056 break; 1057 case T_ForeignScan: 1058 *rels_used = bms_add_members(*rels_used, 1059 ((ForeignScan *) plan)->fs_relids); 1060 break; 1061 case T_CustomScan: 1062 *rels_used = bms_add_members(*rels_used, 1063 ((CustomScan *) plan)->custom_relids); 1064 break; 1065 case T_ModifyTable: 1066 *rels_used = bms_add_member(*rels_used, 1067 ((ModifyTable *) plan)->nominalRelation); 1068 if (((ModifyTable *) plan)->exclRelRTI) 1069 *rels_used = bms_add_member(*rels_used, 1070 ((ModifyTable *) plan)->exclRelRTI); 1071 break; 1072 case T_Append: 1073 *rels_used = bms_add_members(*rels_used, 1074 ((Append *) plan)->apprelids); 1075 break; 1076 case T_MergeAppend: 1077 *rels_used = bms_add_members(*rels_used, 1078 ((MergeAppend *) plan)->apprelids); 1079 break; 1080 default: 1081 break; 1082 } 1083 1084 return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used); 1085 } 1086 1087 /* 1088 * ExplainNode - 1089 * Appends a description of a plan tree to es->str 1090 * 1091 * planstate points to the executor state node for the current plan node. 1092 * We need to work from a PlanState node, not just a Plan node, in order to 1093 * get at the instrumentation data (if any) as well as the list of subplans. 1094 * 1095 * ancestors is a list of parent Plan and SubPlan nodes, most-closely-nested 1096 * first. These are needed in order to interpret PARAM_EXEC Params. 1097 * 1098 * relationship describes the relationship of this plan node to its parent 1099 * (eg, "Outer", "Inner"); it can be null at top level. plan_name is an 1100 * optional name to be attached to the node. 1101 * 1102 * In text format, es->indent is controlled in this function since we only 1103 * want it to change at plan-node boundaries (but a few subroutines will 1104 * transiently increment it). In non-text formats, es->indent corresponds 1105 * to the nesting depth of logical output groups, and therefore is controlled 1106 * by ExplainOpenGroup/ExplainCloseGroup. 1107 */ 1108 static void 1109 ExplainNode(PlanState *planstate, List *ancestors, 1110 const char *relationship, const char *plan_name, 1111 ExplainState *es) 1112 { 1113 Plan *plan = planstate->plan; 1114 const char *pname; /* node type name for text output */ 1115 const char *sname; /* node type name for non-text output */ 1116 const char *strategy = NULL; 1117 const char *partialmode = NULL; 1118 const char *operation = NULL; 1119 const char *custom_name = NULL; 1120 ExplainWorkersState *save_workers_state = es->workers_state; 1121 int save_indent = es->indent; 1122 bool haschildren; 1123 1124 /* 1125 * Prepare per-worker output buffers, if needed. We'll append the data in 1126 * these to the main output string further down. 1127 */ 1128 if (planstate->worker_instrument && es->analyze && !es->hide_workers) 1129 es->workers_state = ExplainCreateWorkersState(planstate->worker_instrument->num_workers); 1130 else 1131 es->workers_state = NULL; 1132 1133 /* Identify plan node type, and print generic details */ 1134 switch (nodeTag(plan)) 1135 { 1136 case T_Result: 1137 pname = sname = "Result"; 1138 break; 1139 case T_ProjectSet: 1140 pname = sname = "ProjectSet"; 1141 break; 1142 case T_ModifyTable: 1143 sname = "ModifyTable"; 1144 switch (((ModifyTable *) plan)->operation) 1145 { 1146 case CMD_INSERT: 1147 pname = operation = "Insert"; 1148 break; 1149 case CMD_UPDATE: 1150 pname = operation = "Update"; 1151 break; 1152 case CMD_DELETE: 1153 pname = operation = "Delete"; 1154 break; 1155 default: 1156 pname = "???"; 1157 break; 1158 } 1159 break; 1160 case T_Append: 1161 pname = sname = "Append"; 1162 break; 1163 case T_MergeAppend: 1164 pname = sname = "Merge Append"; 1165 break; 1166 case T_RecursiveUnion: 1167 pname = sname = "Recursive Union"; 1168 break; 1169 case T_BitmapAnd: 1170 pname = sname = "BitmapAnd"; 1171 break; 1172 case T_BitmapOr: 1173 pname = sname = "BitmapOr"; 1174 break; 1175 case T_NestLoop: 1176 pname = sname = "Nested Loop"; 1177 break; 1178 case T_MergeJoin: 1179 pname = "Merge"; /* "Join" gets added by jointype switch */ 1180 sname = "Merge Join"; 1181 break; 1182 case T_HashJoin: 1183 pname = "Hash"; /* "Join" gets added by jointype switch */ 1184 sname = "Hash Join"; 1185 break; 1186 case T_SeqScan: 1187 pname = sname = "Seq Scan"; 1188 break; 1189 case T_SampleScan: 1190 pname = sname = "Sample Scan"; 1191 break; 1192 case T_Gather: 1193 pname = sname = "Gather"; 1194 break; 1195 case T_GatherMerge: 1196 pname = sname = "Gather Merge"; 1197 break; 1198 case T_IndexScan: 1199 pname = sname = "Index Scan"; 1200 break; 1201 case T_IndexOnlyScan: 1202 pname = sname = "Index Only Scan"; 1203 break; 1204 case T_BitmapIndexScan: 1205 pname = sname = "Bitmap Index Scan"; 1206 break; 1207 case T_BitmapHeapScan: 1208 pname = sname = "Bitmap Heap Scan"; 1209 break; 1210 case T_TidScan: 1211 pname = sname = "Tid Scan"; 1212 break; 1213 case T_SubqueryScan: 1214 pname = sname = "Subquery Scan"; 1215 break; 1216 case T_FunctionScan: 1217 pname = sname = "Function Scan"; 1218 break; 1219 case T_TableFuncScan: 1220 pname = sname = "Table Function Scan"; 1221 break; 1222 case T_ValuesScan: 1223 pname = sname = "Values Scan"; 1224 break; 1225 case T_CteScan: 1226 pname = sname = "CTE Scan"; 1227 break; 1228 case T_NamedTuplestoreScan: 1229 pname = sname = "Named Tuplestore Scan"; 1230 break; 1231 case T_WorkTableScan: 1232 pname = sname = "WorkTable Scan"; 1233 break; 1234 case T_ForeignScan: 1235 sname = "Foreign Scan"; 1236 switch (((ForeignScan *) plan)->operation) 1237 { 1238 case CMD_SELECT: 1239 pname = "Foreign Scan"; 1240 operation = "Select"; 1241 break; 1242 case CMD_INSERT: 1243 pname = "Foreign Insert"; 1244 operation = "Insert"; 1245 break; 1246 case CMD_UPDATE: 1247 pname = "Foreign Update"; 1248 operation = "Update"; 1249 break; 1250 case CMD_DELETE: 1251 pname = "Foreign Delete"; 1252 operation = "Delete"; 1253 break; 1254 default: 1255 pname = "???"; 1256 break; 1257 } 1258 break; 1259 case T_CustomScan: 1260 sname = "Custom Scan"; 1261 custom_name = ((CustomScan *) plan)->methods->CustomName; 1262 if (custom_name) 1263 pname = psprintf("Custom Scan (%s)", custom_name); 1264 else 1265 pname = sname; 1266 break; 1267 case T_Material: 1268 pname = sname = "Materialize"; 1269 break; 1270 case T_Sort: 1271 pname = sname = "Sort"; 1272 break; 1273 case T_IncrementalSort: 1274 pname = sname = "Incremental Sort"; 1275 break; 1276 case T_Group: 1277 pname = sname = "Group"; 1278 break; 1279 case T_Agg: 1280 { 1281 Agg *agg = (Agg *) plan; 1282 1283 sname = "Aggregate"; 1284 switch (agg->aggstrategy) 1285 { 1286 case AGG_PLAIN: 1287 pname = "Aggregate"; 1288 strategy = "Plain"; 1289 break; 1290 case AGG_SORTED: 1291 pname = "GroupAggregate"; 1292 strategy = "Sorted"; 1293 break; 1294 case AGG_HASHED: 1295 pname = "HashAggregate"; 1296 strategy = "Hashed"; 1297 break; 1298 case AGG_MIXED: 1299 pname = "MixedAggregate"; 1300 strategy = "Mixed"; 1301 break; 1302 default: 1303 pname = "Aggregate ???"; 1304 strategy = "???"; 1305 break; 1306 } 1307 1308 if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit)) 1309 { 1310 partialmode = "Partial"; 1311 pname = psprintf("%s %s", partialmode, pname); 1312 } 1313 else if (DO_AGGSPLIT_COMBINE(agg->aggsplit)) 1314 { 1315 partialmode = "Finalize"; 1316 pname = psprintf("%s %s", partialmode, pname); 1317 } 1318 else 1319 partialmode = "Simple"; 1320 } 1321 break; 1322 case T_WindowAgg: 1323 pname = sname = "WindowAgg"; 1324 break; 1325 case T_Unique: 1326 pname = sname = "Unique"; 1327 break; 1328 case T_SetOp: 1329 sname = "SetOp"; 1330 switch (((SetOp *) plan)->strategy) 1331 { 1332 case SETOP_SORTED: 1333 pname = "SetOp"; 1334 strategy = "Sorted"; 1335 break; 1336 case SETOP_HASHED: 1337 pname = "HashSetOp"; 1338 strategy = "Hashed"; 1339 break; 1340 default: 1341 pname = "SetOp ???"; 1342 strategy = "???"; 1343 break; 1344 } 1345 break; 1346 case T_LockRows: 1347 pname = sname = "LockRows"; 1348 break; 1349 case T_Limit: 1350 pname = sname = "Limit"; 1351 break; 1352 case T_Hash: 1353 pname = sname = "Hash"; 1354 break; 1355 default: 1356 pname = sname = "???"; 1357 break; 1358 } 1359 1360 ExplainOpenGroup("Plan", 1361 relationship ? NULL : "Plan", 1362 true, es); 1363 1364 if (es->format == EXPLAIN_FORMAT_TEXT) 1365 { 1366 if (plan_name) 1367 { 1368 ExplainIndentText(es); 1369 appendStringInfo(es->str, "%s\n", plan_name); 1370 es->indent++; 1371 } 1372 if (es->indent) 1373 { 1374 ExplainIndentText(es); 1375 appendStringInfoString(es->str, "-> "); 1376 es->indent += 2; 1377 } 1378 if (plan->parallel_aware) 1379 appendStringInfoString(es->str, "Parallel "); 1380 appendStringInfoString(es->str, pname); 1381 es->indent++; 1382 } 1383 else 1384 { 1385 ExplainPropertyText("Node Type", sname, es); 1386 if (strategy) 1387 ExplainPropertyText("Strategy", strategy, es); 1388 if (partialmode) 1389 ExplainPropertyText("Partial Mode", partialmode, es); 1390 if (operation) 1391 ExplainPropertyText("Operation", operation, es); 1392 if (relationship) 1393 ExplainPropertyText("Parent Relationship", relationship, es); 1394 if (plan_name) 1395 ExplainPropertyText("Subplan Name", plan_name, es); 1396 if (custom_name) 1397 ExplainPropertyText("Custom Plan Provider", custom_name, es); 1398 ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es); 1399 } 1400 1401 switch (nodeTag(plan)) 1402 { 1403 case T_SeqScan: 1404 case T_SampleScan: 1405 case T_BitmapHeapScan: 1406 case T_TidScan: 1407 case T_SubqueryScan: 1408 case T_FunctionScan: 1409 case T_TableFuncScan: 1410 case T_ValuesScan: 1411 case T_CteScan: 1412 case T_WorkTableScan: 1413 ExplainScanTarget((Scan *) plan, es); 1414 break; 1415 case T_ForeignScan: 1416 case T_CustomScan: 1417 if (((Scan *) plan)->scanrelid > 0) 1418 ExplainScanTarget((Scan *) plan, es); 1419 break; 1420 case T_IndexScan: 1421 { 1422 IndexScan *indexscan = (IndexScan *) plan; 1423 1424 ExplainIndexScanDetails(indexscan->indexid, 1425 indexscan->indexorderdir, 1426 es); 1427 ExplainScanTarget((Scan *) indexscan, es); 1428 } 1429 break; 1430 case T_IndexOnlyScan: 1431 { 1432 IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan; 1433 1434 ExplainIndexScanDetails(indexonlyscan->indexid, 1435 indexonlyscan->indexorderdir, 1436 es); 1437 ExplainScanTarget((Scan *) indexonlyscan, es); 1438 } 1439 break; 1440 case T_BitmapIndexScan: 1441 { 1442 BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan; 1443 const char *indexname = 1444 explain_get_index_name(bitmapindexscan->indexid); 1445 1446 if (es->format == EXPLAIN_FORMAT_TEXT) 1447 appendStringInfo(es->str, " on %s", 1448 quote_identifier(indexname)); 1449 else 1450 ExplainPropertyText("Index Name", indexname, es); 1451 } 1452 break; 1453 case T_ModifyTable: 1454 ExplainModifyTarget((ModifyTable *) plan, es); 1455 break; 1456 case T_NestLoop: 1457 case T_MergeJoin: 1458 case T_HashJoin: 1459 { 1460 const char *jointype; 1461 1462 switch (((Join *) plan)->jointype) 1463 { 1464 case JOIN_INNER: 1465 jointype = "Inner"; 1466 break; 1467 case JOIN_LEFT: 1468 jointype = "Left"; 1469 break; 1470 case JOIN_FULL: 1471 jointype = "Full"; 1472 break; 1473 case JOIN_RIGHT: 1474 jointype = "Right"; 1475 break; 1476 case JOIN_SEMI: 1477 jointype = "Semi"; 1478 break; 1479 case JOIN_ANTI: 1480 jointype = "Anti"; 1481 break; 1482 default: 1483 jointype = "???"; 1484 break; 1485 } 1486 if (es->format == EXPLAIN_FORMAT_TEXT) 1487 { 1488 /* 1489 * For historical reasons, the join type is interpolated 1490 * into the node type name... 1491 */ 1492 if (((Join *) plan)->jointype != JOIN_INNER) 1493 appendStringInfo(es->str, " %s Join", jointype); 1494 else if (!IsA(plan, NestLoop)) 1495 appendStringInfoString(es->str, " Join"); 1496 } 1497 else 1498 ExplainPropertyText("Join Type", jointype, es); 1499 } 1500 break; 1501 case T_SetOp: 1502 { 1503 const char *setopcmd; 1504 1505 switch (((SetOp *) plan)->cmd) 1506 { 1507 case SETOPCMD_INTERSECT: 1508 setopcmd = "Intersect"; 1509 break; 1510 case SETOPCMD_INTERSECT_ALL: 1511 setopcmd = "Intersect All"; 1512 break; 1513 case SETOPCMD_EXCEPT: 1514 setopcmd = "Except"; 1515 break; 1516 case SETOPCMD_EXCEPT_ALL: 1517 setopcmd = "Except All"; 1518 break; 1519 default: 1520 setopcmd = "???"; 1521 break; 1522 } 1523 if (es->format == EXPLAIN_FORMAT_TEXT) 1524 appendStringInfo(es->str, " %s", setopcmd); 1525 else 1526 ExplainPropertyText("Command", setopcmd, es); 1527 } 1528 break; 1529 default: 1530 break; 1531 } 1532 1533 if (es->costs) 1534 { 1535 if (es->format == EXPLAIN_FORMAT_TEXT) 1536 { 1537 appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)", 1538 plan->startup_cost, plan->total_cost, 1539 plan->plan_rows, plan->plan_width); 1540 } 1541 else 1542 { 1543 ExplainPropertyFloat("Startup Cost", NULL, plan->startup_cost, 1544 2, es); 1545 ExplainPropertyFloat("Total Cost", NULL, plan->total_cost, 1546 2, es); 1547 ExplainPropertyFloat("Plan Rows", NULL, plan->plan_rows, 1548 0, es); 1549 ExplainPropertyInteger("Plan Width", NULL, plan->plan_width, 1550 es); 1551 } 1552 } 1553 1554 /* 1555 * We have to forcibly clean up the instrumentation state because we 1556 * haven't done ExecutorEnd yet. This is pretty grotty ... 1557 * 1558 * Note: contrib/auto_explain could cause instrumentation to be set up 1559 * even though we didn't ask for it here. Be careful not to print any 1560 * instrumentation results the user didn't ask for. But we do the 1561 * InstrEndLoop call anyway, if possible, to reduce the number of cases 1562 * auto_explain has to contend with. 1563 */ 1564 if (planstate->instrument) 1565 InstrEndLoop(planstate->instrument); 1566 1567 if (es->analyze && 1568 planstate->instrument && planstate->instrument->nloops > 0) 1569 { 1570 double nloops = planstate->instrument->nloops; 1571 double startup_ms = 1000.0 * planstate->instrument->startup / nloops; 1572 double total_ms = 1000.0 * planstate->instrument->total / nloops; 1573 double rows = planstate->instrument->ntuples / nloops; 1574 1575 if (es->format == EXPLAIN_FORMAT_TEXT) 1576 { 1577 if (es->timing) 1578 appendStringInfo(es->str, 1579 " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", 1580 startup_ms, total_ms, rows, nloops); 1581 else 1582 appendStringInfo(es->str, 1583 " (actual rows=%.0f loops=%.0f)", 1584 rows, nloops); 1585 } 1586 else 1587 { 1588 if (es->timing) 1589 { 1590 ExplainPropertyFloat("Actual Startup Time", "s", startup_ms, 1591 3, es); 1592 ExplainPropertyFloat("Actual Total Time", "s", total_ms, 1593 3, es); 1594 } 1595 ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es); 1596 ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es); 1597 } 1598 } 1599 else if (es->analyze) 1600 { 1601 if (es->format == EXPLAIN_FORMAT_TEXT) 1602 appendStringInfoString(es->str, " (never executed)"); 1603 else 1604 { 1605 if (es->timing) 1606 { 1607 ExplainPropertyFloat("Actual Startup Time", "ms", 0.0, 3, es); 1608 ExplainPropertyFloat("Actual Total Time", "ms", 0.0, 3, es); 1609 } 1610 ExplainPropertyFloat("Actual Rows", NULL, 0.0, 0, es); 1611 ExplainPropertyFloat("Actual Loops", NULL, 0.0, 0, es); 1612 } 1613 } 1614 1615 /* in text format, first line ends here */ 1616 if (es->format == EXPLAIN_FORMAT_TEXT) 1617 appendStringInfoChar(es->str, '\n'); 1618 1619 /* prepare per-worker general execution details */ 1620 if (es->workers_state && es->verbose) 1621 { 1622 WorkerInstrumentation *w = planstate->worker_instrument; 1623 1624 for (int n = 0; n < w->num_workers; n++) 1625 { 1626 Instrumentation *instrument = &w->instrument[n]; 1627 double nloops = instrument->nloops; 1628 double startup_ms; 1629 double total_ms; 1630 double rows; 1631 1632 if (nloops <= 0) 1633 continue; 1634 startup_ms = 1000.0 * instrument->startup / nloops; 1635 total_ms = 1000.0 * instrument->total / nloops; 1636 rows = instrument->ntuples / nloops; 1637 1638 ExplainOpenWorker(n, es); 1639 1640 if (es->format == EXPLAIN_FORMAT_TEXT) 1641 { 1642 ExplainIndentText(es); 1643 if (es->timing) 1644 appendStringInfo(es->str, 1645 "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n", 1646 startup_ms, total_ms, rows, nloops); 1647 else 1648 appendStringInfo(es->str, 1649 "actual rows=%.0f loops=%.0f\n", 1650 rows, nloops); 1651 } 1652 else 1653 { 1654 if (es->timing) 1655 { 1656 ExplainPropertyFloat("Actual Startup Time", "ms", 1657 startup_ms, 3, es); 1658 ExplainPropertyFloat("Actual Total Time", "ms", 1659 total_ms, 3, es); 1660 } 1661 ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es); 1662 ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es); 1663 } 1664 1665 ExplainCloseWorker(n, es); 1666 } 1667 } 1668 1669 /* target list */ 1670 if (es->verbose) 1671 show_plan_tlist(planstate, ancestors, es); 1672 1673 /* unique join */ 1674 switch (nodeTag(plan)) 1675 { 1676 case T_NestLoop: 1677 case T_MergeJoin: 1678 case T_HashJoin: 1679 /* try not to be too chatty about this in text mode */ 1680 if (es->format != EXPLAIN_FORMAT_TEXT || 1681 (es->verbose && ((Join *) plan)->inner_unique)) 1682 ExplainPropertyBool("Inner Unique", 1683 ((Join *) plan)->inner_unique, 1684 es); 1685 break; 1686 default: 1687 break; 1688 } 1689 1690 /* quals, sort keys, etc */ 1691 switch (nodeTag(plan)) 1692 { 1693 case T_IndexScan: 1694 show_scan_qual(((IndexScan *) plan)->indexqualorig, 1695 "Index Cond", planstate, ancestors, es); 1696 if (((IndexScan *) plan)->indexqualorig) 1697 show_instrumentation_count("Rows Removed by Index Recheck", 2, 1698 planstate, es); 1699 show_scan_qual(((IndexScan *) plan)->indexorderbyorig, 1700 "Order By", planstate, ancestors, es); 1701 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1702 if (plan->qual) 1703 show_instrumentation_count("Rows Removed by Filter", 1, 1704 planstate, es); 1705 break; 1706 case T_IndexOnlyScan: 1707 show_scan_qual(((IndexOnlyScan *) plan)->indexqual, 1708 "Index Cond", planstate, ancestors, es); 1709 if (((IndexOnlyScan *) plan)->indexqual) 1710 show_instrumentation_count("Rows Removed by Index Recheck", 2, 1711 planstate, es); 1712 show_scan_qual(((IndexOnlyScan *) plan)->indexorderby, 1713 "Order By", planstate, ancestors, es); 1714 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1715 if (plan->qual) 1716 show_instrumentation_count("Rows Removed by Filter", 1, 1717 planstate, es); 1718 if (es->analyze) 1719 ExplainPropertyFloat("Heap Fetches", NULL, 1720 planstate->instrument->ntuples2, 0, es); 1721 break; 1722 case T_BitmapIndexScan: 1723 show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, 1724 "Index Cond", planstate, ancestors, es); 1725 break; 1726 case T_BitmapHeapScan: 1727 show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, 1728 "Recheck Cond", planstate, ancestors, es); 1729 if (((BitmapHeapScan *) plan)->bitmapqualorig) 1730 show_instrumentation_count("Rows Removed by Index Recheck", 2, 1731 planstate, es); 1732 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1733 if (plan->qual) 1734 show_instrumentation_count("Rows Removed by Filter", 1, 1735 planstate, es); 1736 if (es->analyze) 1737 show_tidbitmap_info((BitmapHeapScanState *) planstate, es); 1738 break; 1739 case T_SampleScan: 1740 show_tablesample(((SampleScan *) plan)->tablesample, 1741 planstate, ancestors, es); 1742 /* fall through to print additional fields the same as SeqScan */ 1743 /* FALLTHROUGH */ 1744 case T_SeqScan: 1745 case T_ValuesScan: 1746 case T_CteScan: 1747 case T_NamedTuplestoreScan: 1748 case T_WorkTableScan: 1749 case T_SubqueryScan: 1750 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1751 if (plan->qual) 1752 show_instrumentation_count("Rows Removed by Filter", 1, 1753 planstate, es); 1754 break; 1755 case T_Gather: 1756 { 1757 Gather *gather = (Gather *) plan; 1758 1759 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1760 if (plan->qual) 1761 show_instrumentation_count("Rows Removed by Filter", 1, 1762 planstate, es); 1763 ExplainPropertyInteger("Workers Planned", NULL, 1764 gather->num_workers, es); 1765 1766 /* Show params evaluated at gather node */ 1767 if (gather->initParam) 1768 show_eval_params(gather->initParam, es); 1769 1770 if (es->analyze) 1771 { 1772 int nworkers; 1773 1774 nworkers = ((GatherState *) planstate)->nworkers_launched; 1775 ExplainPropertyInteger("Workers Launched", NULL, 1776 nworkers, es); 1777 } 1778 1779 if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT) 1780 ExplainPropertyBool("Single Copy", gather->single_copy, es); 1781 } 1782 break; 1783 case T_GatherMerge: 1784 { 1785 GatherMerge *gm = (GatherMerge *) plan; 1786 1787 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1788 if (plan->qual) 1789 show_instrumentation_count("Rows Removed by Filter", 1, 1790 planstate, es); 1791 ExplainPropertyInteger("Workers Planned", NULL, 1792 gm->num_workers, es); 1793 1794 /* Show params evaluated at gather-merge node */ 1795 if (gm->initParam) 1796 show_eval_params(gm->initParam, es); 1797 1798 if (es->analyze) 1799 { 1800 int nworkers; 1801 1802 nworkers = ((GatherMergeState *) planstate)->nworkers_launched; 1803 ExplainPropertyInteger("Workers Launched", NULL, 1804 nworkers, es); 1805 } 1806 } 1807 break; 1808 case T_FunctionScan: 1809 if (es->verbose) 1810 { 1811 List *fexprs = NIL; 1812 ListCell *lc; 1813 1814 foreach(lc, ((FunctionScan *) plan)->functions) 1815 { 1816 RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); 1817 1818 fexprs = lappend(fexprs, rtfunc->funcexpr); 1819 } 1820 /* We rely on show_expression to insert commas as needed */ 1821 show_expression((Node *) fexprs, 1822 "Function Call", planstate, ancestors, 1823 es->verbose, es); 1824 } 1825 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1826 if (plan->qual) 1827 show_instrumentation_count("Rows Removed by Filter", 1, 1828 planstate, es); 1829 break; 1830 case T_TableFuncScan: 1831 if (es->verbose) 1832 { 1833 TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc; 1834 1835 show_expression((Node *) tablefunc, 1836 "Table Function Call", planstate, ancestors, 1837 es->verbose, es); 1838 } 1839 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1840 if (plan->qual) 1841 show_instrumentation_count("Rows Removed by Filter", 1, 1842 planstate, es); 1843 break; 1844 case T_TidScan: 1845 { 1846 /* 1847 * The tidquals list has OR semantics, so be sure to show it 1848 * as an OR condition. 1849 */ 1850 List *tidquals = ((TidScan *) plan)->tidquals; 1851 1852 if (list_length(tidquals) > 1) 1853 tidquals = list_make1(make_orclause(tidquals)); 1854 show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es); 1855 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1856 if (plan->qual) 1857 show_instrumentation_count("Rows Removed by Filter", 1, 1858 planstate, es); 1859 } 1860 break; 1861 case T_ForeignScan: 1862 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1863 if (plan->qual) 1864 show_instrumentation_count("Rows Removed by Filter", 1, 1865 planstate, es); 1866 show_foreignscan_info((ForeignScanState *) planstate, es); 1867 break; 1868 case T_CustomScan: 1869 { 1870 CustomScanState *css = (CustomScanState *) planstate; 1871 1872 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); 1873 if (plan->qual) 1874 show_instrumentation_count("Rows Removed by Filter", 1, 1875 planstate, es); 1876 if (css->methods->ExplainCustomScan) 1877 css->methods->ExplainCustomScan(css, ancestors, es); 1878 } 1879 break; 1880 case T_NestLoop: 1881 show_upper_qual(((NestLoop *) plan)->join.joinqual, 1882 "Join Filter", planstate, ancestors, es); 1883 if (((NestLoop *) plan)->join.joinqual) 1884 show_instrumentation_count("Rows Removed by Join Filter", 1, 1885 planstate, es); 1886 show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); 1887 if (plan->qual) 1888 show_instrumentation_count("Rows Removed by Filter", 2, 1889 planstate, es); 1890 break; 1891 case T_MergeJoin: 1892 show_upper_qual(((MergeJoin *) plan)->mergeclauses, 1893 "Merge Cond", planstate, ancestors, es); 1894 show_upper_qual(((MergeJoin *) plan)->join.joinqual, 1895 "Join Filter", planstate, ancestors, es); 1896 if (((MergeJoin *) plan)->join.joinqual) 1897 show_instrumentation_count("Rows Removed by Join Filter", 1, 1898 planstate, es); 1899 show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); 1900 if (plan->qual) 1901 show_instrumentation_count("Rows Removed by Filter", 2, 1902 planstate, es); 1903 break; 1904 case T_HashJoin: 1905 show_upper_qual(((HashJoin *) plan)->hashclauses, 1906 "Hash Cond", planstate, ancestors, es); 1907 show_upper_qual(((HashJoin *) plan)->join.joinqual, 1908 "Join Filter", planstate, ancestors, es); 1909 if (((HashJoin *) plan)->join.joinqual) 1910 show_instrumentation_count("Rows Removed by Join Filter", 1, 1911 planstate, es); 1912 show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); 1913 if (plan->qual) 1914 show_instrumentation_count("Rows Removed by Filter", 2, 1915 planstate, es); 1916 break; 1917 case T_Agg: 1918 show_agg_keys(castNode(AggState, planstate), ancestors, es); 1919 show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); 1920 show_hashagg_info((AggState *) planstate, es); 1921 if (plan->qual) 1922 show_instrumentation_count("Rows Removed by Filter", 1, 1923 planstate, es); 1924 break; 1925 case T_Group: 1926 show_group_keys(castNode(GroupState, planstate), ancestors, es); 1927 show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); 1928 if (plan->qual) 1929 show_instrumentation_count("Rows Removed by Filter", 1, 1930 planstate, es); 1931 break; 1932 case T_Sort: 1933 show_sort_keys(castNode(SortState, planstate), ancestors, es); 1934 show_sort_info(castNode(SortState, planstate), es); 1935 break; 1936 case T_IncrementalSort: 1937 show_incremental_sort_keys(castNode(IncrementalSortState, planstate), 1938 ancestors, es); 1939 show_incremental_sort_info(castNode(IncrementalSortState, planstate), 1940 es); 1941 break; 1942 case T_MergeAppend: 1943 show_merge_append_keys(castNode(MergeAppendState, planstate), 1944 ancestors, es); 1945 break; 1946 case T_Result: 1947 show_upper_qual((List *) ((Result *) plan)->resconstantqual, 1948 "One-Time Filter", planstate, ancestors, es); 1949 show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); 1950 if (plan->qual) 1951 show_instrumentation_count("Rows Removed by Filter", 1, 1952 planstate, es); 1953 break; 1954 case T_ModifyTable: 1955 show_modifytable_info(castNode(ModifyTableState, planstate), ancestors, 1956 es); 1957 break; 1958 case T_Hash: 1959 show_hash_info(castNode(HashState, planstate), es); 1960 break; 1961 default: 1962 break; 1963 } 1964 1965 /* 1966 * Prepare per-worker JIT instrumentation. As with the overall JIT 1967 * summary, this is printed only if printing costs is enabled. 1968 */ 1969 if (es->workers_state && es->costs && es->verbose) 1970 { 1971 SharedJitInstrumentation *w = planstate->worker_jit_instrument; 1972 1973 if (w) 1974 { 1975 for (int n = 0; n < w->num_workers; n++) 1976 { 1977 ExplainOpenWorker(n, es); 1978 ExplainPrintJIT(es, planstate->state->es_jit_flags, 1979 &w->jit_instr[n]); 1980 ExplainCloseWorker(n, es); 1981 } 1982 } 1983 } 1984 1985 /* Show buffer/WAL usage */ 1986 if (es->buffers && planstate->instrument) 1987 show_buffer_usage(es, &planstate->instrument->bufusage, false); 1988 if (es->wal && planstate->instrument) 1989 show_wal_usage(es, &planstate->instrument->walusage); 1990 1991 /* Prepare per-worker buffer/WAL usage */ 1992 if (es->workers_state && (es->buffers || es->wal) && es->verbose) 1993 { 1994 WorkerInstrumentation *w = planstate->worker_instrument; 1995 1996 for (int n = 0; n < w->num_workers; n++) 1997 { 1998 Instrumentation *instrument = &w->instrument[n]; 1999 double nloops = instrument->nloops; 2000 2001 if (nloops <= 0) 2002 continue; 2003 2004 ExplainOpenWorker(n, es); 2005 if (es->buffers) 2006 show_buffer_usage(es, &instrument->bufusage, false); 2007 if (es->wal) 2008 show_wal_usage(es, &instrument->walusage); 2009 ExplainCloseWorker(n, es); 2010 } 2011 } 2012 2013 /* Show per-worker details for this plan node, then pop that stack */ 2014 if (es->workers_state) 2015 ExplainFlushWorkersState(es); 2016 es->workers_state = save_workers_state; 2017 2018 /* 2019 * If partition pruning was done during executor initialization, the 2020 * number of child plans we'll display below will be less than the number 2021 * of subplans that was specified in the plan. To make this a bit less 2022 * mysterious, emit an indication that this happened. Note that this 2023 * field is emitted now because we want it to be a property of the parent 2024 * node; it *cannot* be emitted within the Plans sub-node we'll open next. 2025 */ 2026 switch (nodeTag(plan)) 2027 { 2028 case T_Append: 2029 ExplainMissingMembers(((AppendState *) planstate)->as_nplans, 2030 list_length(((Append *) plan)->appendplans), 2031 es); 2032 break; 2033 case T_MergeAppend: 2034 ExplainMissingMembers(((MergeAppendState *) planstate)->ms_nplans, 2035 list_length(((MergeAppend *) plan)->mergeplans), 2036 es); 2037 break; 2038 default: 2039 break; 2040 } 2041 2042 /* Get ready to display the child plans */ 2043 haschildren = planstate->initPlan || 2044 outerPlanState(planstate) || 2045 innerPlanState(planstate) || 2046 IsA(plan, ModifyTable) || 2047 IsA(plan, Append) || 2048 IsA(plan, MergeAppend) || 2049 IsA(plan, BitmapAnd) || 2050 IsA(plan, BitmapOr) || 2051 IsA(plan, SubqueryScan) || 2052 (IsA(planstate, CustomScanState) && 2053 ((CustomScanState *) planstate)->custom_ps != NIL) || 2054 planstate->subPlan; 2055 if (haschildren) 2056 { 2057 ExplainOpenGroup("Plans", "Plans", false, es); 2058 /* Pass current Plan as head of ancestors list for children */ 2059 ancestors = lcons(plan, ancestors); 2060 } 2061 2062 /* initPlan-s */ 2063 if (planstate->initPlan) 2064 ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es); 2065 2066 /* lefttree */ 2067 if (outerPlanState(planstate)) 2068 ExplainNode(outerPlanState(planstate), ancestors, 2069 "Outer", NULL, es); 2070 2071 /* righttree */ 2072 if (innerPlanState(planstate)) 2073 ExplainNode(innerPlanState(planstate), ancestors, 2074 "Inner", NULL, es); 2075 2076 /* special child plans */ 2077 switch (nodeTag(plan)) 2078 { 2079 case T_ModifyTable: 2080 ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans, 2081 ((ModifyTableState *) planstate)->mt_nplans, 2082 ancestors, es); 2083 break; 2084 case T_Append: 2085 ExplainMemberNodes(((AppendState *) planstate)->appendplans, 2086 ((AppendState *) planstate)->as_nplans, 2087 ancestors, es); 2088 break; 2089 case T_MergeAppend: 2090 ExplainMemberNodes(((MergeAppendState *) planstate)->mergeplans, 2091 ((MergeAppendState *) planstate)->ms_nplans, 2092 ancestors, es); 2093 break; 2094 case T_BitmapAnd: 2095 ExplainMemberNodes(((BitmapAndState *) planstate)->bitmapplans, 2096 ((BitmapAndState *) planstate)->nplans, 2097 ancestors, es); 2098 break; 2099 case T_BitmapOr: 2100 ExplainMemberNodes(((BitmapOrState *) planstate)->bitmapplans, 2101 ((BitmapOrState *) planstate)->nplans, 2102 ancestors, es); 2103 break; 2104 case T_SubqueryScan: 2105 ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors, 2106 "Subquery", NULL, es); 2107 break; 2108 case T_CustomScan: 2109 ExplainCustomChildren((CustomScanState *) planstate, 2110 ancestors, es); 2111 break; 2112 default: 2113 break; 2114 } 2115 2116 /* subPlan-s */ 2117 if (planstate->subPlan) 2118 ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es); 2119 2120 /* end of child plans */ 2121 if (haschildren) 2122 { 2123 ancestors = list_delete_first(ancestors); 2124 ExplainCloseGroup("Plans", "Plans", false, es); 2125 } 2126 2127 /* in text format, undo whatever indentation we added */ 2128 if (es->format == EXPLAIN_FORMAT_TEXT) 2129 es->indent = save_indent; 2130 2131 ExplainCloseGroup("Plan", 2132 relationship ? NULL : "Plan", 2133 true, es); 2134 } 2135 2136 /* 2137 * Show the targetlist of a plan node 2138 */ 2139 static void 2140 show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es) 2141 { 2142 Plan *plan = planstate->plan; 2143 List *context; 2144 List *result = NIL; 2145 bool useprefix; 2146 ListCell *lc; 2147 2148 /* No work if empty tlist (this occurs eg in bitmap indexscans) */ 2149 if (plan->targetlist == NIL) 2150 return; 2151 /* The tlist of an Append isn't real helpful, so suppress it */ 2152 if (IsA(plan, Append)) 2153 return; 2154 /* Likewise for MergeAppend and RecursiveUnion */ 2155 if (IsA(plan, MergeAppend)) 2156 return; 2157 if (IsA(plan, RecursiveUnion)) 2158 return; 2159 2160 /* 2161 * Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE 2162 * 2163 * Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE 2164 * might contain subplan output expressions that are confusing in this 2165 * context. The tlist for a ForeignScan that executes a direct UPDATE/ 2166 * DELETE always contains "junk" target columns to identify the exact row 2167 * to update or delete, which would be confusing in this context. So, we 2168 * suppress it in all the cases. 2169 */ 2170 if (IsA(plan, ForeignScan) && 2171 ((ForeignScan *) plan)->operation != CMD_SELECT) 2172 return; 2173 2174 /* Set up deparsing context */ 2175 context = set_deparse_context_plan(es->deparse_cxt, 2176 plan, 2177 ancestors); 2178 useprefix = list_length(es->rtable) > 1; 2179 2180 /* Deparse each result column (we now include resjunk ones) */ 2181 foreach(lc, plan->targetlist) 2182 { 2183 TargetEntry *tle = (TargetEntry *) lfirst(lc); 2184 2185 result = lappend(result, 2186 deparse_expression((Node *) tle->expr, context, 2187 useprefix, false)); 2188 } 2189 2190 /* Print results */ 2191 ExplainPropertyList("Output", result, es); 2192 } 2193 2194 /* 2195 * Show a generic expression 2196 */ 2197 static void 2198 show_expression(Node *node, const char *qlabel, 2199 PlanState *planstate, List *ancestors, 2200 bool useprefix, ExplainState *es) 2201 { 2202 List *context; 2203 char *exprstr; 2204 2205 /* Set up deparsing context */ 2206 context = set_deparse_context_plan(es->deparse_cxt, 2207 planstate->plan, 2208 ancestors); 2209 2210 /* Deparse the expression */ 2211 exprstr = deparse_expression(node, context, useprefix, false); 2212 2213 /* And add to es->str */ 2214 ExplainPropertyText(qlabel, exprstr, es); 2215 } 2216 2217 /* 2218 * Show a qualifier expression (which is a List with implicit AND semantics) 2219 */ 2220 static void 2221 show_qual(List *qual, const char *qlabel, 2222 PlanState *planstate, List *ancestors, 2223 bool useprefix, ExplainState *es) 2224 { 2225 Node *node; 2226 2227 /* No work if empty qual */ 2228 if (qual == NIL) 2229 return; 2230 2231 /* Convert AND list to explicit AND */ 2232 node = (Node *) make_ands_explicit(qual); 2233 2234 /* And show it */ 2235 show_expression(node, qlabel, planstate, ancestors, useprefix, es); 2236 } 2237 2238 /* 2239 * Show a qualifier expression for a scan plan node 2240 */ 2241 static void 2242 show_scan_qual(List *qual, const char *qlabel, 2243 PlanState *planstate, List *ancestors, 2244 ExplainState *es) 2245 { 2246 bool useprefix; 2247 2248 useprefix = (IsA(planstate->plan, SubqueryScan) || es->verbose); 2249 show_qual(qual, qlabel, planstate, ancestors, useprefix, es); 2250 } 2251 2252 /* 2253 * Show a qualifier expression for an upper-level plan node 2254 */ 2255 static void 2256 show_upper_qual(List *qual, const char *qlabel, 2257 PlanState *planstate, List *ancestors, 2258 ExplainState *es) 2259 { 2260 bool useprefix; 2261 2262 useprefix = (list_length(es->rtable) > 1 || es->verbose); 2263 show_qual(qual, qlabel, planstate, ancestors, useprefix, es); 2264 } 2265 2266 /* 2267 * Show the sort keys for a Sort node. 2268 */ 2269 static void 2270 show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es) 2271 { 2272 Sort *plan = (Sort *) sortstate->ss.ps.plan; 2273 2274 show_sort_group_keys((PlanState *) sortstate, "Sort Key", 2275 plan->numCols, 0, plan->sortColIdx, 2276 plan->sortOperators, plan->collations, 2277 plan->nullsFirst, 2278 ancestors, es); 2279 } 2280 2281 /* 2282 * Show the sort keys for a IncrementalSort node. 2283 */ 2284 static void 2285 show_incremental_sort_keys(IncrementalSortState *incrsortstate, 2286 List *ancestors, ExplainState *es) 2287 { 2288 IncrementalSort *plan = (IncrementalSort *) incrsortstate->ss.ps.plan; 2289 2290 show_sort_group_keys((PlanState *) incrsortstate, "Sort Key", 2291 plan->sort.numCols, plan->nPresortedCols, 2292 plan->sort.sortColIdx, 2293 plan->sort.sortOperators, plan->sort.collations, 2294 plan->sort.nullsFirst, 2295 ancestors, es); 2296 } 2297 2298 /* 2299 * Likewise, for a MergeAppend node. 2300 */ 2301 static void 2302 show_merge_append_keys(MergeAppendState *mstate, List *ancestors, 2303 ExplainState *es) 2304 { 2305 MergeAppend *plan = (MergeAppend *) mstate->ps.plan; 2306 2307 show_sort_group_keys((PlanState *) mstate, "Sort Key", 2308 plan->numCols, 0, plan->sortColIdx, 2309 plan->sortOperators, plan->collations, 2310 plan->nullsFirst, 2311 ancestors, es); 2312 } 2313 2314 /* 2315 * Show the grouping keys for an Agg node. 2316 */ 2317 static void 2318 show_agg_keys(AggState *astate, List *ancestors, 2319 ExplainState *es) 2320 { 2321 Agg *plan = (Agg *) astate->ss.ps.plan; 2322 2323 if (plan->numCols > 0 || plan->groupingSets) 2324 { 2325 /* The key columns refer to the tlist of the child plan */ 2326 ancestors = lcons(plan, ancestors); 2327 2328 if (plan->groupingSets) 2329 show_grouping_sets(outerPlanState(astate), plan, ancestors, es); 2330 else 2331 show_sort_group_keys(outerPlanState(astate), "Group Key", 2332 plan->numCols, 0, plan->grpColIdx, 2333 NULL, NULL, NULL, 2334 ancestors, es); 2335 2336 ancestors = list_delete_first(ancestors); 2337 } 2338 } 2339 2340 static void 2341 show_grouping_sets(PlanState *planstate, Agg *agg, 2342 List *ancestors, ExplainState *es) 2343 { 2344 List *context; 2345 bool useprefix; 2346 ListCell *lc; 2347 2348 /* Set up deparsing context */ 2349 context = set_deparse_context_plan(es->deparse_cxt, 2350 planstate->plan, 2351 ancestors); 2352 useprefix = (list_length(es->rtable) > 1 || es->verbose); 2353 2354 ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es); 2355 2356 show_grouping_set_keys(planstate, agg, NULL, 2357 context, useprefix, ancestors, es); 2358 2359 foreach(lc, agg->chain) 2360 { 2361 Agg *aggnode = lfirst(lc); 2362 Sort *sortnode = (Sort *) aggnode->plan.lefttree; 2363 2364 show_grouping_set_keys(planstate, aggnode, sortnode, 2365 context, useprefix, ancestors, es); 2366 } 2367 2368 ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es); 2369 } 2370 2371 static void 2372 show_grouping_set_keys(PlanState *planstate, 2373 Agg *aggnode, Sort *sortnode, 2374 List *context, bool useprefix, 2375 List *ancestors, ExplainState *es) 2376 { 2377 Plan *plan = planstate->plan; 2378 char *exprstr; 2379 ListCell *lc; 2380 List *gsets = aggnode->groupingSets; 2381 AttrNumber *keycols = aggnode->grpColIdx; 2382 const char *keyname; 2383 const char *keysetname; 2384 2385 if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED) 2386 { 2387 keyname = "Hash Key"; 2388 keysetname = "Hash Keys"; 2389 } 2390 else 2391 { 2392 keyname = "Group Key"; 2393 keysetname = "Group Keys"; 2394 } 2395 2396 ExplainOpenGroup("Grouping Set", NULL, true, es); 2397 2398 if (sortnode) 2399 { 2400 show_sort_group_keys(planstate, "Sort Key", 2401 sortnode->numCols, 0, sortnode->sortColIdx, 2402 sortnode->sortOperators, sortnode->collations, 2403 sortnode->nullsFirst, 2404 ancestors, es); 2405 if (es->format == EXPLAIN_FORMAT_TEXT) 2406 es->indent++; 2407 } 2408 2409 ExplainOpenGroup(keysetname, keysetname, false, es); 2410 2411 foreach(lc, gsets) 2412 { 2413 List *result = NIL; 2414 ListCell *lc2; 2415 2416 foreach(lc2, (List *) lfirst(lc)) 2417 { 2418 Index i = lfirst_int(lc2); 2419 AttrNumber keyresno = keycols[i]; 2420 TargetEntry *target = get_tle_by_resno(plan->targetlist, 2421 keyresno); 2422 2423 if (!target) 2424 elog(ERROR, "no tlist entry for key %d", keyresno); 2425 /* Deparse the expression, showing any top-level cast */ 2426 exprstr = deparse_expression((Node *) target->expr, context, 2427 useprefix, true); 2428 2429 result = lappend(result, exprstr); 2430 } 2431 2432 if (!result && es->format == EXPLAIN_FORMAT_TEXT) 2433 ExplainPropertyText(keyname, "()", es); 2434 else 2435 ExplainPropertyListNested(keyname, result, es); 2436 } 2437 2438 ExplainCloseGroup(keysetname, keysetname, false, es); 2439 2440 if (sortnode && es->format == EXPLAIN_FORMAT_TEXT) 2441 es->indent--; 2442 2443 ExplainCloseGroup("Grouping Set", NULL, true, es); 2444 } 2445 2446 /* 2447 * Show the grouping keys for a Group node. 2448 */ 2449 static void 2450 show_group_keys(GroupState *gstate, List *ancestors, 2451 ExplainState *es) 2452 { 2453 Group *plan = (Group *) gstate->ss.ps.plan; 2454 2455 /* The key columns refer to the tlist of the child plan */ 2456 ancestors = lcons(plan, ancestors); 2457 show_sort_group_keys(outerPlanState(gstate), "Group Key", 2458 plan->numCols, 0, plan->grpColIdx, 2459 NULL, NULL, NULL, 2460 ancestors, es); 2461 ancestors = list_delete_first(ancestors); 2462 } 2463 2464 /* 2465 * Common code to show sort/group keys, which are represented in plan nodes 2466 * as arrays of targetlist indexes. If it's a sort key rather than a group 2467 * key, also pass sort operators/collations/nullsFirst arrays. 2468 */ 2469 static void 2470 show_sort_group_keys(PlanState *planstate, const char *qlabel, 2471 int nkeys, int nPresortedKeys, AttrNumber *keycols, 2472 Oid *sortOperators, Oid *collations, bool *nullsFirst, 2473 List *ancestors, ExplainState *es) 2474 { 2475 Plan *plan = planstate->plan; 2476 List *context; 2477 List *result = NIL; 2478 List *resultPresorted = NIL; 2479 StringInfoData sortkeybuf; 2480 bool useprefix; 2481 int keyno; 2482 2483 if (nkeys <= 0) 2484 return; 2485 2486 initStringInfo(&sortkeybuf); 2487 2488 /* Set up deparsing context */ 2489 context = set_deparse_context_plan(es->deparse_cxt, 2490 plan, 2491 ancestors); 2492 useprefix = (list_length(es->rtable) > 1 || es->verbose); 2493 2494 for (keyno = 0; keyno < nkeys; keyno++) 2495 { 2496 /* find key expression in tlist */ 2497 AttrNumber keyresno = keycols[keyno]; 2498 TargetEntry *target = get_tle_by_resno(plan->targetlist, 2499 keyresno); 2500 char *exprstr; 2501 2502 if (!target) 2503 elog(ERROR, "no tlist entry for key %d", keyresno); 2504 /* Deparse the expression, showing any top-level cast */ 2505 exprstr = deparse_expression((Node *) target->expr, context, 2506 useprefix, true); 2507 resetStringInfo(&sortkeybuf); 2508 appendStringInfoString(&sortkeybuf, exprstr); 2509 /* Append sort order information, if relevant */ 2510 if (sortOperators != NULL) 2511 show_sortorder_options(&sortkeybuf, 2512 (Node *) target->expr, 2513 sortOperators[keyno], 2514 collations[keyno], 2515 nullsFirst[keyno]); 2516 /* Emit one property-list item per sort key */ 2517 result = lappend(result, pstrdup(sortkeybuf.data)); 2518 if (keyno < nPresortedKeys) 2519 resultPresorted = lappend(resultPresorted, exprstr); 2520 } 2521 2522 ExplainPropertyList(qlabel, result, es); 2523 if (nPresortedKeys > 0) 2524 ExplainPropertyList("Presorted Key", resultPresorted, es); 2525 } 2526 2527 /* 2528 * Append nondefault characteristics of the sort ordering of a column to buf 2529 * (collation, direction, NULLS FIRST/LAST) 2530 */ 2531 static void 2532 show_sortorder_options(StringInfo buf, Node *sortexpr, 2533 Oid sortOperator, Oid collation, bool nullsFirst) 2534 { 2535 Oid sortcoltype = exprType(sortexpr); 2536 bool reverse = false; 2537 TypeCacheEntry *typentry; 2538 2539 typentry = lookup_type_cache(sortcoltype, 2540 TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); 2541 2542 /* 2543 * Print COLLATE if it's not default for the column's type. There are 2544 * some cases where this is redundant, eg if expression is a column whose 2545 * declared collation is that collation, but it's hard to distinguish that 2546 * here (and arguably, printing COLLATE explicitly is a good idea anyway 2547 * in such cases). 2548 */ 2549 if (OidIsValid(collation) && collation != get_typcollation(sortcoltype)) 2550 { 2551 char *collname = get_collation_name(collation); 2552 2553 if (collname == NULL) 2554 elog(ERROR, "cache lookup failed for collation %u", collation); 2555 appendStringInfo(buf, " COLLATE %s", quote_identifier(collname)); 2556 } 2557 2558 /* Print direction if not ASC, or USING if non-default sort operator */ 2559 if (sortOperator == typentry->gt_opr) 2560 { 2561 appendStringInfoString(buf, " DESC"); 2562 reverse = true; 2563 } 2564 else if (sortOperator != typentry->lt_opr) 2565 { 2566 char *opname = get_opname(sortOperator); 2567 2568 if (opname == NULL) 2569 elog(ERROR, "cache lookup failed for operator %u", sortOperator); 2570 appendStringInfo(buf, " USING %s", opname); 2571 /* Determine whether operator would be considered ASC or DESC */ 2572 (void) get_equality_op_for_ordering_op(sortOperator, &reverse); 2573 } 2574 2575 /* Add NULLS FIRST/LAST only if it wouldn't be default */ 2576 if (nullsFirst && !reverse) 2577 { 2578 appendStringInfoString(buf, " NULLS FIRST"); 2579 } 2580 else if (!nullsFirst && reverse) 2581 { 2582 appendStringInfoString(buf, " NULLS LAST"); 2583 } 2584 } 2585 2586 /* 2587 * Show TABLESAMPLE properties 2588 */ 2589 static void 2590 show_tablesample(TableSampleClause *tsc, PlanState *planstate, 2591 List *ancestors, ExplainState *es) 2592 { 2593 List *context; 2594 bool useprefix; 2595 char *method_name; 2596 List *params = NIL; 2597 char *repeatable; 2598 ListCell *lc; 2599 2600 /* Set up deparsing context */ 2601 context = set_deparse_context_plan(es->deparse_cxt, 2602 planstate->plan, 2603 ancestors); 2604 useprefix = list_length(es->rtable) > 1; 2605 2606 /* Get the tablesample method name */ 2607 method_name = get_func_name(tsc->tsmhandler); 2608 2609 /* Deparse parameter expressions */ 2610 foreach(lc, tsc->args) 2611 { 2612 Node *arg = (Node *) lfirst(lc); 2613 2614 params = lappend(params, 2615 deparse_expression(arg, context, 2616 useprefix, false)); 2617 } 2618 if (tsc->repeatable) 2619 repeatable = deparse_expression((Node *) tsc->repeatable, context, 2620 useprefix, false); 2621 else 2622 repeatable = NULL; 2623 2624 /* Print results */ 2625 if (es->format == EXPLAIN_FORMAT_TEXT) 2626 { 2627 bool first = true; 2628 2629 ExplainIndentText(es); 2630 appendStringInfo(es->str, "Sampling: %s (", method_name); 2631 foreach(lc, params) 2632 { 2633 if (!first) 2634 appendStringInfoString(es->str, ", "); 2635 appendStringInfoString(es->str, (const char *) lfirst(lc)); 2636 first = false; 2637 } 2638 appendStringInfoChar(es->str, ')'); 2639 if (repeatable) 2640 appendStringInfo(es->str, " REPEATABLE (%s)", repeatable); 2641 appendStringInfoChar(es->str, '\n'); 2642 } 2643 else 2644 { 2645 ExplainPropertyText("Sampling Method", method_name, es); 2646 ExplainPropertyList("Sampling Parameters", params, es); 2647 if (repeatable) 2648 ExplainPropertyText("Repeatable Seed", repeatable, es); 2649 } 2650 } 2651 2652 /* 2653 * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node 2654 */ 2655 static void 2656 show_sort_info(SortState *sortstate, ExplainState *es) 2657 { 2658 if (!es->analyze) 2659 return; 2660 2661 if (sortstate->sort_Done && sortstate->tuplesortstate != NULL) 2662 { 2663 Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate; 2664 TuplesortInstrumentation stats; 2665 const char *sortMethod; 2666 const char *spaceType; 2667 int64 spaceUsed; 2668 2669 tuplesort_get_stats(state, &stats); 2670 sortMethod = tuplesort_method_name(stats.sortMethod); 2671 spaceType = tuplesort_space_type_name(stats.spaceType); 2672 spaceUsed = stats.spaceUsed; 2673 2674 if (es->format == EXPLAIN_FORMAT_TEXT) 2675 { 2676 ExplainIndentText(es); 2677 appendStringInfo(es->str, "Sort Method: %s %s: " INT64_FORMAT "kB\n", 2678 sortMethod, spaceType, spaceUsed); 2679 } 2680 else 2681 { 2682 ExplainPropertyText("Sort Method", sortMethod, es); 2683 ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es); 2684 ExplainPropertyText("Sort Space Type", spaceType, es); 2685 } 2686 } 2687 2688 /* 2689 * You might think we should just skip this stanza entirely when 2690 * es->hide_workers is true, but then we'd get no sort-method output at 2691 * all. We have to make it look like worker 0's data is top-level data. 2692 * This is easily done by just skipping the OpenWorker/CloseWorker calls. 2693 * Currently, we don't worry about the possibility that there are multiple 2694 * workers in such a case; if there are, duplicate output fields will be 2695 * emitted. 2696 */ 2697 if (sortstate->shared_info != NULL) 2698 { 2699 int n; 2700 2701 for (n = 0; n < sortstate->shared_info->num_workers; n++) 2702 { 2703 TuplesortInstrumentation *sinstrument; 2704 const char *sortMethod; 2705 const char *spaceType; 2706 int64 spaceUsed; 2707 2708 sinstrument = &sortstate->shared_info->sinstrument[n]; 2709 if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS) 2710 continue; /* ignore any unfilled slots */ 2711 sortMethod = tuplesort_method_name(sinstrument->sortMethod); 2712 spaceType = tuplesort_space_type_name(sinstrument->spaceType); 2713 spaceUsed = sinstrument->spaceUsed; 2714 2715 if (es->workers_state) 2716 ExplainOpenWorker(n, es); 2717 2718 if (es->format == EXPLAIN_FORMAT_TEXT) 2719 { 2720 ExplainIndentText(es); 2721 appendStringInfo(es->str, 2722 "Sort Method: %s %s: " INT64_FORMAT "kB\n", 2723 sortMethod, spaceType, spaceUsed); 2724 } 2725 else 2726 { 2727 ExplainPropertyText("Sort Method", sortMethod, es); 2728 ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es); 2729 ExplainPropertyText("Sort Space Type", spaceType, es); 2730 } 2731 2732 if (es->workers_state) 2733 ExplainCloseWorker(n, es); 2734 } 2735 } 2736 } 2737 2738 /* 2739 * Incremental sort nodes sort in (a potentially very large number of) batches, 2740 * so EXPLAIN ANALYZE needs to roll up the tuplesort stats from each batch into 2741 * an intelligible summary. 2742 * 2743 * This function is used for both a non-parallel node and each worker in a 2744 * parallel incremental sort node. 2745 */ 2746 static void 2747 show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo, 2748 const char *groupLabel, bool indent, ExplainState *es) 2749 { 2750 ListCell *methodCell; 2751 List *methodNames = NIL; 2752 2753 /* Generate a list of sort methods used across all groups. */ 2754 for (int bit = 0; bit < NUM_TUPLESORTMETHODS; bit++) 2755 { 2756 TuplesortMethod sortMethod = (1 << bit); 2757 2758 if (groupInfo->sortMethods & sortMethod) 2759 { 2760 const char *methodName = tuplesort_method_name(sortMethod); 2761 2762 methodNames = lappend(methodNames, unconstify(char *, methodName)); 2763 } 2764 } 2765 2766 if (es->format == EXPLAIN_FORMAT_TEXT) 2767 { 2768 if (indent) 2769 appendStringInfoSpaces(es->str, es->indent * 2); 2770 appendStringInfo(es->str, "%s Groups: " INT64_FORMAT " Sort Method", groupLabel, 2771 groupInfo->groupCount); 2772 /* plural/singular based on methodNames size */ 2773 if (list_length(methodNames) > 1) 2774 appendStringInfo(es->str, "s: "); 2775 else 2776 appendStringInfo(es->str, ": "); 2777 foreach(methodCell, methodNames) 2778 { 2779 appendStringInfo(es->str, "%s", (char *) methodCell->ptr_value); 2780 if (foreach_current_index(methodCell) < list_length(methodNames) - 1) 2781 appendStringInfo(es->str, ", "); 2782 } 2783 2784 if (groupInfo->maxMemorySpaceUsed > 0) 2785 { 2786 int64 avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount; 2787 const char *spaceTypeName; 2788 2789 spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY); 2790 appendStringInfo(es->str, " Average %s: " INT64_FORMAT "kB Peak %s: " INT64_FORMAT "kB", 2791 spaceTypeName, avgSpace, 2792 spaceTypeName, groupInfo->maxMemorySpaceUsed); 2793 } 2794 2795 if (groupInfo->maxDiskSpaceUsed > 0) 2796 { 2797 int64 avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount; 2798 2799 const char *spaceTypeName; 2800 2801 spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK); 2802 appendStringInfo(es->str, " Average %s: " INT64_FORMAT "kB Peak %s: " INT64_FORMAT "kB", 2803 spaceTypeName, avgSpace, 2804 spaceTypeName, groupInfo->maxDiskSpaceUsed); 2805 } 2806 } 2807 else 2808 { 2809 StringInfoData groupName; 2810 2811 initStringInfo(&groupName); 2812 appendStringInfo(&groupName, "%s Groups", groupLabel); 2813 ExplainOpenGroup("Incremental Sort Groups", groupName.data, true, es); 2814 ExplainPropertyInteger("Group Count", NULL, groupInfo->groupCount, es); 2815 2816 ExplainPropertyList("Sort Methods Used", methodNames, es); 2817 2818 if (groupInfo->maxMemorySpaceUsed > 0) 2819 { 2820 int64 avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount; 2821 const char *spaceTypeName; 2822 StringInfoData memoryName; 2823 2824 spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY); 2825 initStringInfo(&memoryName); 2826 appendStringInfo(&memoryName, "Sort Space %s", spaceTypeName); 2827 ExplainOpenGroup("Sort Space", memoryName.data, true, es); 2828 2829 ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es); 2830 ExplainPropertyInteger("Peak Sort Space Used", "kB", 2831 groupInfo->maxMemorySpaceUsed, es); 2832 2833 ExplainCloseGroup("Sort Space", memoryName.data, true, es); 2834 } 2835 if (groupInfo->maxDiskSpaceUsed > 0) 2836 { 2837 int64 avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount; 2838 const char *spaceTypeName; 2839 StringInfoData diskName; 2840 2841 spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK); 2842 initStringInfo(&diskName); 2843 appendStringInfo(&diskName, "Sort Space %s", spaceTypeName); 2844 ExplainOpenGroup("Sort Space", diskName.data, true, es); 2845 2846 ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es); 2847 ExplainPropertyInteger("Peak Sort Space Used", "kB", 2848 groupInfo->maxDiskSpaceUsed, es); 2849 2850 ExplainCloseGroup("Sort Space", diskName.data, true, es); 2851 } 2852 2853 ExplainCloseGroup("Incremental Sort Groups", groupName.data, true, es); 2854 } 2855 } 2856 2857 /* 2858 * If it's EXPLAIN ANALYZE, show tuplesort stats for an incremental sort node 2859 */ 2860 static void 2861 show_incremental_sort_info(IncrementalSortState *incrsortstate, 2862 ExplainState *es) 2863 { 2864 IncrementalSortGroupInfo *fullsortGroupInfo; 2865 IncrementalSortGroupInfo *prefixsortGroupInfo; 2866 2867 fullsortGroupInfo = &incrsortstate->incsort_info.fullsortGroupInfo; 2868 2869 if (!es->analyze) 2870 return; 2871 2872 /* 2873 * Since we never have any prefix groups unless we've first sorted a full 2874 * groups and transitioned modes (copying the tuples into a prefix group), 2875 * we don't need to do anything if there were 0 full groups. 2876 * 2877 * We still have to continue after this block if there are no full groups, 2878 * though, since it's possible that we have workers that did real work 2879 * even if the leader didn't participate. 2880 */ 2881 if (fullsortGroupInfo->groupCount > 0) 2882 { 2883 show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", true, es); 2884 prefixsortGroupInfo = &incrsortstate->incsort_info.prefixsortGroupInfo; 2885 if (prefixsortGroupInfo->groupCount > 0) 2886 { 2887 if (es->format == EXPLAIN_FORMAT_TEXT) 2888 appendStringInfo(es->str, "\n"); 2889 show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es); 2890 } 2891 if (es->format == EXPLAIN_FORMAT_TEXT) 2892 appendStringInfo(es->str, "\n"); 2893 } 2894 2895 if (incrsortstate->shared_info != NULL) 2896 { 2897 int n; 2898 bool indent_first_line; 2899 2900 for (n = 0; n < incrsortstate->shared_info->num_workers; n++) 2901 { 2902 IncrementalSortInfo *incsort_info = 2903 &incrsortstate->shared_info->sinfo[n]; 2904 2905 /* 2906 * If a worker hasn't processed any sort groups at all, then 2907 * exclude it from output since it either didn't launch or didn't 2908 * contribute anything meaningful. 2909 */ 2910 fullsortGroupInfo = &incsort_info->fullsortGroupInfo; 2911 2912 /* 2913 * Since we never have any prefix groups unless we've first sorted 2914 * a full groups and transitioned modes (copying the tuples into a 2915 * prefix group), we don't need to do anything if there were 0 2916 * full groups. 2917 */ 2918 if (fullsortGroupInfo->groupCount == 0) 2919 continue; 2920 2921 if (es->workers_state) 2922 ExplainOpenWorker(n, es); 2923 2924 indent_first_line = es->workers_state == NULL || es->verbose; 2925 show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", 2926 indent_first_line, es); 2927 prefixsortGroupInfo = &incsort_info->prefixsortGroupInfo; 2928 if (prefixsortGroupInfo->groupCount > 0) 2929 { 2930 if (es->format == EXPLAIN_FORMAT_TEXT) 2931 appendStringInfo(es->str, "\n"); 2932 show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es); 2933 } 2934 if (es->format == EXPLAIN_FORMAT_TEXT) 2935 appendStringInfo(es->str, "\n"); 2936 2937 if (es->workers_state) 2938 ExplainCloseWorker(n, es); 2939 } 2940 } 2941 } 2942 2943 /* 2944 * Show information on hash buckets/batches. 2945 */ 2946 static void 2947 show_hash_info(HashState *hashstate, ExplainState *es) 2948 { 2949 HashInstrumentation hinstrument = {0}; 2950 2951 /* 2952 * Collect stats from the local process, even when it's a parallel query. 2953 * In a parallel query, the leader process may or may not have run the 2954 * hash join, and even if it did it may not have built a hash table due to 2955 * timing (if it started late it might have seen no tuples in the outer 2956 * relation and skipped building the hash table). Therefore we have to be 2957 * prepared to get instrumentation data from all participants. 2958 */ 2959 if (hashstate->hinstrument) 2960 memcpy(&hinstrument, hashstate->hinstrument, 2961 sizeof(HashInstrumentation)); 2962 2963 /* 2964 * Merge results from workers. In the parallel-oblivious case, the 2965 * results from all participants should be identical, except where 2966 * participants didn't run the join at all so have no data. In the 2967 * parallel-aware case, we need to consider all the results. Each worker 2968 * may have seen a different subset of batches and we want to report the 2969 * highest memory usage across all batches. We take the maxima of other 2970 * values too, for the same reasons as in ExecHashAccumInstrumentation. 2971 */ 2972 if (hashstate->shared_info) 2973 { 2974 SharedHashInfo *shared_info = hashstate->shared_info; 2975 int i; 2976 2977 for (i = 0; i < shared_info->num_workers; ++i) 2978 { 2979 HashInstrumentation *worker_hi = &shared_info->hinstrument[i]; 2980 2981 hinstrument.nbuckets = Max(hinstrument.nbuckets, 2982 worker_hi->nbuckets); 2983 hinstrument.nbuckets_original = Max(hinstrument.nbuckets_original, 2984 worker_hi->nbuckets_original); 2985 hinstrument.nbatch = Max(hinstrument.nbatch, 2986 worker_hi->nbatch); 2987 hinstrument.nbatch_original = Max(hinstrument.nbatch_original, 2988 worker_hi->nbatch_original); 2989 hinstrument.space_peak = Max(hinstrument.space_peak, 2990 worker_hi->space_peak); 2991 } 2992 } 2993 2994 if (hinstrument.nbatch > 0) 2995 { 2996 long spacePeakKb = (hinstrument.space_peak + 1023) / 1024; 2997 2998 if (es->format != EXPLAIN_FORMAT_TEXT) 2999 { 3000 ExplainPropertyInteger("Hash Buckets", NULL, 3001 hinstrument.nbuckets, es); 3002 ExplainPropertyInteger("Original Hash Buckets", NULL, 3003 hinstrument.nbuckets_original, es); 3004 ExplainPropertyInteger("Hash Batches", NULL, 3005 hinstrument.nbatch, es); 3006 ExplainPropertyInteger("Original Hash Batches", NULL, 3007 hinstrument.nbatch_original, es); 3008 ExplainPropertyInteger("Peak Memory Usage", "kB", 3009 spacePeakKb, es); 3010 } 3011 else if (hinstrument.nbatch_original != hinstrument.nbatch || 3012 hinstrument.nbuckets_original != hinstrument.nbuckets) 3013 { 3014 ExplainIndentText(es); 3015 appendStringInfo(es->str, 3016 "Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n", 3017 hinstrument.nbuckets, 3018 hinstrument.nbuckets_original, 3019 hinstrument.nbatch, 3020 hinstrument.nbatch_original, 3021 spacePeakKb); 3022 } 3023 else 3024 { 3025 ExplainIndentText(es); 3026 appendStringInfo(es->str, 3027 "Buckets: %d Batches: %d Memory Usage: %ldkB\n", 3028 hinstrument.nbuckets, hinstrument.nbatch, 3029 spacePeakKb); 3030 } 3031 } 3032 } 3033 3034 /* 3035 * Show information on hash aggregate memory usage and batches. 3036 */ 3037 static void 3038 show_hashagg_info(AggState *aggstate, ExplainState *es) 3039 { 3040 Agg *agg = (Agg *) aggstate->ss.ps.plan; 3041 int64 memPeakKb = (aggstate->hash_mem_peak + 1023) / 1024; 3042 3043 if (agg->aggstrategy != AGG_HASHED && 3044 agg->aggstrategy != AGG_MIXED) 3045 return; 3046 3047 if (es->format != EXPLAIN_FORMAT_TEXT) 3048 { 3049 3050 if (es->costs) 3051 ExplainPropertyInteger("Planned Partitions", NULL, 3052 aggstate->hash_planned_partitions, es); 3053 3054 /* 3055 * During parallel query the leader may have not helped out. We 3056 * detect this by checking how much memory it used. If we find it 3057 * didn't do any work then we don't show its properties. 3058 */ 3059 if (es->analyze && aggstate->hash_mem_peak > 0) 3060 { 3061 ExplainPropertyInteger("HashAgg Batches", NULL, 3062 aggstate->hash_batches_used, es); 3063 ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, es); 3064 ExplainPropertyInteger("Disk Usage", "kB", 3065 aggstate->hash_disk_used, es); 3066 } 3067 } 3068 else 3069 { 3070 bool gotone = false; 3071 3072 if (es->costs && aggstate->hash_planned_partitions > 0) 3073 { 3074 ExplainIndentText(es); 3075 appendStringInfo(es->str, "Planned Partitions: %d", 3076 aggstate->hash_planned_partitions); 3077 gotone = true; 3078 } 3079 3080 /* 3081 * During parallel query the leader may have not helped out. We 3082 * detect this by checking how much memory it used. If we find it 3083 * didn't do any work then we don't show its properties. 3084 */ 3085 if (es->analyze && aggstate->hash_mem_peak > 0) 3086 { 3087 if (!gotone) 3088 ExplainIndentText(es); 3089 else 3090 appendStringInfoString(es->str, " "); 3091 3092 appendStringInfo(es->str, "Batches: %d Memory Usage: " INT64_FORMAT "kB", 3093 aggstate->hash_batches_used, memPeakKb); 3094 gotone = true; 3095 3096 /* Only display disk usage if we spilled to disk */ 3097 if (aggstate->hash_batches_used > 1) 3098 { 3099 appendStringInfo(es->str, " Disk Usage: " UINT64_FORMAT "kB", 3100 aggstate->hash_disk_used); 3101 } 3102 } 3103 3104 if (gotone) 3105 appendStringInfoChar(es->str, '\n'); 3106 } 3107 3108 /* Display stats for each parallel worker */ 3109 if (es->analyze && aggstate->shared_info != NULL) 3110 { 3111 for (int n = 0; n < aggstate->shared_info->num_workers; n++) 3112 { 3113 AggregateInstrumentation *sinstrument; 3114 uint64 hash_disk_used; 3115 int hash_batches_used; 3116 3117 sinstrument = &aggstate->shared_info->sinstrument[n]; 3118 /* Skip workers that didn't do anything */ 3119 if (sinstrument->hash_mem_peak == 0) 3120 continue; 3121 hash_disk_used = sinstrument->hash_disk_used; 3122 hash_batches_used = sinstrument->hash_batches_used; 3123 memPeakKb = (sinstrument->hash_mem_peak + 1023) / 1024; 3124 3125 if (es->workers_state) 3126 ExplainOpenWorker(n, es); 3127 3128 if (es->format == EXPLAIN_FORMAT_TEXT) 3129 { 3130 ExplainIndentText(es); 3131 3132 appendStringInfo(es->str, "Batches: %d Memory Usage: " INT64_FORMAT "kB", 3133 hash_batches_used, memPeakKb); 3134 3135 /* Only display disk usage if we spilled to disk */ 3136 if (hash_batches_used > 1) 3137 appendStringInfo(es->str, " Disk Usage: " UINT64_FORMAT "kB", 3138 hash_disk_used); 3139 appendStringInfoChar(es->str, '\n'); 3140 } 3141 else 3142 { 3143 ExplainPropertyInteger("HashAgg Batches", NULL, 3144 hash_batches_used, es); 3145 ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, 3146 es); 3147 ExplainPropertyInteger("Disk Usage", "kB", hash_disk_used, es); 3148 } 3149 3150 if (es->workers_state) 3151 ExplainCloseWorker(n, es); 3152 } 3153 } 3154 } 3155 3156 /* 3157 * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node 3158 */ 3159 static void 3160 show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es) 3161 { 3162 if (es->format != EXPLAIN_FORMAT_TEXT) 3163 { 3164 ExplainPropertyInteger("Exact Heap Blocks", NULL, 3165 planstate->exact_pages, es); 3166 ExplainPropertyInteger("Lossy Heap Blocks", NULL, 3167 planstate->lossy_pages, es); 3168 } 3169 else 3170 { 3171 if (planstate->exact_pages > 0 || planstate->lossy_pages > 0) 3172 { 3173 ExplainIndentText(es); 3174 appendStringInfoString(es->str, "Heap Blocks:"); 3175 if (planstate->exact_pages > 0) 3176 appendStringInfo(es->str, " exact=%ld", planstate->exact_pages); 3177 if (planstate->lossy_pages > 0) 3178 appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages); 3179 appendStringInfoChar(es->str, '\n'); 3180 } 3181 } 3182 } 3183 3184 /* 3185 * If it's EXPLAIN ANALYZE, show instrumentation information for a plan node 3186 * 3187 * "which" identifies which instrumentation counter to print 3188 */ 3189 static void 3190 show_instrumentation_count(const char *qlabel, int which, 3191 PlanState *planstate, ExplainState *es) 3192 { 3193 double nfiltered; 3194 double nloops; 3195 3196 if (!es->analyze || !planstate->instrument) 3197 return; 3198 3199 if (which == 2) 3200 nfiltered = planstate->instrument->nfiltered2; 3201 else 3202 nfiltered = planstate->instrument->nfiltered1; 3203 nloops = planstate->instrument->nloops; 3204 3205 /* In text mode, suppress zero counts; they're not interesting enough */ 3206 if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT) 3207 { 3208 if (nloops > 0) 3209 ExplainPropertyFloat(qlabel, NULL, nfiltered / nloops, 0, es); 3210 else 3211 ExplainPropertyFloat(qlabel, NULL, 0.0, 0, es); 3212 } 3213 } 3214 3215 /* 3216 * Show extra information for a ForeignScan node. 3217 */ 3218 static void 3219 show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es) 3220 { 3221 FdwRoutine *fdwroutine = fsstate->fdwroutine; 3222 3223 /* Let the FDW emit whatever fields it wants */ 3224 if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT) 3225 { 3226 if (fdwroutine->ExplainDirectModify != NULL) 3227 fdwroutine->ExplainDirectModify(fsstate, es); 3228 } 3229 else 3230 { 3231 if (fdwroutine->ExplainForeignScan != NULL) 3232 fdwroutine->ExplainForeignScan(fsstate, es); 3233 } 3234 } 3235 3236 /* 3237 * Show initplan params evaluated at Gather or Gather Merge node. 3238 */ 3239 static void 3240 show_eval_params(Bitmapset *bms_params, ExplainState *es) 3241 { 3242 int paramid = -1; 3243 List *params = NIL; 3244 3245 Assert(bms_params); 3246 3247 while ((paramid = bms_next_member(bms_params, paramid)) >= 0) 3248 { 3249 char param[32]; 3250 3251 snprintf(param, sizeof(param), "$%d", paramid); 3252 params = lappend(params, pstrdup(param)); 3253 } 3254 3255 if (params) 3256 ExplainPropertyList("Params Evaluated", params, es); 3257 } 3258 3259 /* 3260 * Fetch the name of an index in an EXPLAIN 3261 * 3262 * We allow plugins to get control here so that plans involving hypothetical 3263 * indexes can be explained. 3264 * 3265 * Note: names returned by this function should be "raw"; the caller will 3266 * apply quoting if needed. Formerly the convention was to do quoting here, 3267 * but we don't want that in non-text output formats. 3268 */ 3269 static const char * 3270 explain_get_index_name(Oid indexId) 3271 { 3272 const char *result; 3273 3274 if (explain_get_index_name_hook) 3275 result = (*explain_get_index_name_hook) (indexId); 3276 else 3277 result = NULL; 3278 if (result == NULL) 3279 { 3280 /* default behavior: look it up in the catalogs */ 3281 result = get_rel_name(indexId); 3282 if (result == NULL) 3283 elog(ERROR, "cache lookup failed for index %u", indexId); 3284 } 3285 return result; 3286 } 3287 3288 /* 3289 * Show buffer usage details. 3290 */ 3291 static void 3292 show_buffer_usage(ExplainState *es, const BufferUsage *usage, bool planning) 3293 { 3294 if (es->format == EXPLAIN_FORMAT_TEXT) 3295 { 3296 bool has_shared = (usage->shared_blks_hit > 0 || 3297 usage->shared_blks_read > 0 || 3298 usage->shared_blks_dirtied > 0 || 3299 usage->shared_blks_written > 0); 3300 bool has_local = (usage->local_blks_hit > 0 || 3301 usage->local_blks_read > 0 || 3302 usage->local_blks_dirtied > 0 || 3303 usage->local_blks_written > 0); 3304 bool has_temp = (usage->temp_blks_read > 0 || 3305 usage->temp_blks_written > 0); 3306 bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) || 3307 !INSTR_TIME_IS_ZERO(usage->blk_write_time)); 3308 bool show_planning = (planning && (has_shared || 3309 has_local || has_temp || has_timing)); 3310 3311 if (show_planning) 3312 { 3313 ExplainIndentText(es); 3314 appendStringInfoString(es->str, "Planning:\n"); 3315 es->indent++; 3316 } 3317 3318 /* Show only positive counter values. */ 3319 if (has_shared || has_local || has_temp) 3320 { 3321 ExplainIndentText(es); 3322 appendStringInfoString(es->str, "Buffers:"); 3323 3324 if (has_shared) 3325 { 3326 appendStringInfoString(es->str, " shared"); 3327 if (usage->shared_blks_hit > 0) 3328 appendStringInfo(es->str, " hit=%ld", 3329 usage->shared_blks_hit); 3330 if (usage->shared_blks_read > 0) 3331 appendStringInfo(es->str, " read=%ld", 3332 usage->shared_blks_read); 3333 if (usage->shared_blks_dirtied > 0) 3334 appendStringInfo(es->str, " dirtied=%ld", 3335 usage->shared_blks_dirtied); 3336 if (usage->shared_blks_written > 0) 3337 appendStringInfo(es->str, " written=%ld", 3338 usage->shared_blks_written); 3339 if (has_local || has_temp) 3340 appendStringInfoChar(es->str, ','); 3341 } 3342 if (has_local) 3343 { 3344 appendStringInfoString(es->str, " local"); 3345 if (usage->local_blks_hit > 0) 3346 appendStringInfo(es->str, " hit=%ld", 3347 usage->local_blks_hit); 3348 if (usage->local_blks_read > 0) 3349 appendStringInfo(es->str, " read=%ld", 3350 usage->local_blks_read); 3351 if (usage->local_blks_dirtied > 0) 3352 appendStringInfo(es->str, " dirtied=%ld", 3353 usage->local_blks_dirtied); 3354 if (usage->local_blks_written > 0) 3355 appendStringInfo(es->str, " written=%ld", 3356 usage->local_blks_written); 3357 if (has_temp) 3358 appendStringInfoChar(es->str, ','); 3359 } 3360 if (has_temp) 3361 { 3362 appendStringInfoString(es->str, " temp"); 3363 if (usage->temp_blks_read > 0) 3364 appendStringInfo(es->str, " read=%ld", 3365 usage->temp_blks_read); 3366 if (usage->temp_blks_written > 0) 3367 appendStringInfo(es->str, " written=%ld", 3368 usage->temp_blks_written); 3369 } 3370 appendStringInfoChar(es->str, '\n'); 3371 } 3372 3373 /* As above, show only positive counter values. */ 3374 if (has_timing) 3375 { 3376 ExplainIndentText(es); 3377 appendStringInfoString(es->str, "I/O Timings:"); 3378 if (!INSTR_TIME_IS_ZERO(usage->blk_read_time)) 3379 appendStringInfo(es->str, " read=%0.3f", 3380 INSTR_TIME_GET_MILLISEC(usage->blk_read_time)); 3381 if (!INSTR_TIME_IS_ZERO(usage->blk_write_time)) 3382 appendStringInfo(es->str, " write=%0.3f", 3383 INSTR_TIME_GET_MILLISEC(usage->blk_write_time)); 3384 appendStringInfoChar(es->str, '\n'); 3385 } 3386 3387 if (show_planning) 3388 es->indent--; 3389 } 3390 else 3391 { 3392 ExplainPropertyInteger("Shared Hit Blocks", NULL, 3393 usage->shared_blks_hit, es); 3394 ExplainPropertyInteger("Shared Read Blocks", NULL, 3395 usage->shared_blks_read, es); 3396 ExplainPropertyInteger("Shared Dirtied Blocks", NULL, 3397 usage->shared_blks_dirtied, es); 3398 ExplainPropertyInteger("Shared Written Blocks", NULL, 3399 usage->shared_blks_written, es); 3400 ExplainPropertyInteger("Local Hit Blocks", NULL, 3401 usage->local_blks_hit, es); 3402 ExplainPropertyInteger("Local Read Blocks", NULL, 3403 usage->local_blks_read, es); 3404 ExplainPropertyInteger("Local Dirtied Blocks", NULL, 3405 usage->local_blks_dirtied, es); 3406 ExplainPropertyInteger("Local Written Blocks", NULL, 3407 usage->local_blks_written, es); 3408 ExplainPropertyInteger("Temp Read Blocks", NULL, 3409 usage->temp_blks_read, es); 3410 ExplainPropertyInteger("Temp Written Blocks", NULL, 3411 usage->temp_blks_written, es); 3412 if (track_io_timing) 3413 { 3414 ExplainPropertyFloat("I/O Read Time", "ms", 3415 INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3416 3, es); 3417 ExplainPropertyFloat("I/O Write Time", "ms", 3418 INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3419 3, es); 3420 } 3421 } 3422 } 3423 3424 /* 3425 * Show WAL usage details. 3426 */ 3427 static void 3428 show_wal_usage(ExplainState *es, const WalUsage *usage) 3429 { 3430 if (es->format == EXPLAIN_FORMAT_TEXT) 3431 { 3432 /* Show only positive counter values. */ 3433 if ((usage->wal_records > 0) || (usage->wal_fpi > 0) || 3434 (usage->wal_bytes > 0)) 3435 { 3436 ExplainIndentText(es); 3437 appendStringInfoString(es->str, "WAL:"); 3438 3439 if (usage->wal_records > 0) 3440 appendStringInfo(es->str, " records=%ld", 3441 usage->wal_records); 3442 if (usage->wal_fpi > 0) 3443 appendStringInfo(es->str, " fpi=%ld", 3444 usage->wal_fpi); 3445 if (usage->wal_bytes > 0) 3446 appendStringInfo(es->str, " bytes=" UINT64_FORMAT, 3447 usage->wal_bytes); 3448 appendStringInfoChar(es->str, '\n'); 3449 } 3450 } 3451 else 3452 { 3453 ExplainPropertyInteger("WAL Records", NULL, 3454 usage->wal_records, es); 3455 ExplainPropertyInteger("WAL FPI", NULL, 3456 usage->wal_fpi, es); 3457 ExplainPropertyUInteger("WAL Bytes", NULL, 3458 usage->wal_bytes, es); 3459 } 3460 } 3461 3462 /* 3463 * Add some additional details about an IndexScan or IndexOnlyScan 3464 */ 3465 static void 3466 ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir, 3467 ExplainState *es) 3468 { 3469 const char *indexname = explain_get_index_name(indexid); 3470 3471 if (es->format == EXPLAIN_FORMAT_TEXT) 3472 { 3473 if (ScanDirectionIsBackward(indexorderdir)) 3474 appendStringInfoString(es->str, " Backward"); 3475 appendStringInfo(es->str, " using %s", quote_identifier(indexname)); 3476 } 3477 else 3478 { 3479 const char *scandir; 3480 3481 switch (indexorderdir) 3482 { 3483 case BackwardScanDirection: 3484 scandir = "Backward"; 3485 break; 3486 case NoMovementScanDirection: 3487 scandir = "NoMovement"; 3488 break; 3489 case ForwardScanDirection: 3490 scandir = "Forward"; 3491 break; 3492 default: 3493 scandir = "???"; 3494 break; 3495 } 3496 ExplainPropertyText("Scan Direction", scandir, es); 3497 ExplainPropertyText("Index Name", indexname, es); 3498 } 3499 } 3500 3501 /* 3502 * Show the target of a Scan node 3503 */ 3504 static void 3505 ExplainScanTarget(Scan *plan, ExplainState *es) 3506 { 3507 ExplainTargetRel((Plan *) plan, plan->scanrelid, es); 3508 } 3509 3510 /* 3511 * Show the target of a ModifyTable node 3512 * 3513 * Here we show the nominal target (ie, the relation that was named in the 3514 * original query). If the actual target(s) is/are different, we'll show them 3515 * in show_modifytable_info(). 3516 */ 3517 static void 3518 ExplainModifyTarget(ModifyTable *plan, ExplainState *es) 3519 { 3520 ExplainTargetRel((Plan *) plan, plan->nominalRelation, es); 3521 } 3522 3523 /* 3524 * Show the target relation of a scan or modify node 3525 */ 3526 static void 3527 ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) 3528 { 3529 char *objectname = NULL; 3530 char *namespace = NULL; 3531 const char *objecttag = NULL; 3532 RangeTblEntry *rte; 3533 char *refname; 3534 3535 rte = rt_fetch(rti, es->rtable); 3536 refname = (char *) list_nth(es->rtable_names, rti - 1); 3537 if (refname == NULL) 3538 refname = rte->eref->aliasname; 3539 3540 switch (nodeTag(plan)) 3541 { 3542 case T_SeqScan: 3543 case T_SampleScan: 3544 case T_IndexScan: 3545 case T_IndexOnlyScan: 3546 case T_BitmapHeapScan: 3547 case T_TidScan: 3548 case T_ForeignScan: 3549 case T_CustomScan: 3550 case T_ModifyTable: 3551 /* Assert it's on a real relation */ 3552 Assert(rte->rtekind == RTE_RELATION); 3553 objectname = get_rel_name(rte->relid); 3554 if (es->verbose) 3555 namespace = get_namespace_name(get_rel_namespace(rte->relid)); 3556 objecttag = "Relation Name"; 3557 break; 3558 case T_FunctionScan: 3559 { 3560 FunctionScan *fscan = (FunctionScan *) plan; 3561 3562 /* Assert it's on a RangeFunction */ 3563 Assert(rte->rtekind == RTE_FUNCTION); 3564 3565 /* 3566 * If the expression is still a function call of a single 3567 * function, we can get the real name of the function. 3568 * Otherwise, punt. (Even if it was a single function call 3569 * originally, the optimizer could have simplified it away.) 3570 */ 3571 if (list_length(fscan->functions) == 1) 3572 { 3573 RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions); 3574 3575 if (IsA(rtfunc->funcexpr, FuncExpr)) 3576 { 3577 FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr; 3578 Oid funcid = funcexpr->funcid; 3579 3580 objectname = get_func_name(funcid); 3581 if (es->verbose) 3582 namespace = 3583 get_namespace_name(get_func_namespace(funcid)); 3584 } 3585 } 3586 objecttag = "Function Name"; 3587 } 3588 break; 3589 case T_TableFuncScan: 3590 Assert(rte->rtekind == RTE_TABLEFUNC); 3591 objectname = "xmltable"; 3592 objecttag = "Table Function Name"; 3593 break; 3594 case T_ValuesScan: 3595 Assert(rte->rtekind == RTE_VALUES); 3596 break; 3597 case T_CteScan: 3598 /* Assert it's on a non-self-reference CTE */ 3599 Assert(rte->rtekind == RTE_CTE); 3600 Assert(!rte->self_reference); 3601 objectname = rte->ctename; 3602 objecttag = "CTE Name"; 3603 break; 3604 case T_NamedTuplestoreScan: 3605 Assert(rte->rtekind == RTE_NAMEDTUPLESTORE); 3606 objectname = rte->enrname; 3607 objecttag = "Tuplestore Name"; 3608 break; 3609 case T_WorkTableScan: 3610 /* Assert it's on a self-reference CTE */ 3611 Assert(rte->rtekind == RTE_CTE); 3612 Assert(rte->self_reference); 3613 objectname = rte->ctename; 3614 objecttag = "CTE Name"; 3615 break; 3616 default: 3617 break; 3618 } 3619 3620 if (es->format == EXPLAIN_FORMAT_TEXT) 3621 { 3622 appendStringInfoString(es->str, " on"); 3623 if (namespace != NULL) 3624 appendStringInfo(es->str, " %s.%s", quote_identifier(namespace), 3625 quote_identifier(objectname)); 3626 else if (objectname != NULL) 3627 appendStringInfo(es->str, " %s", quote_identifier(objectname)); 3628 if (objectname == NULL || strcmp(refname, objectname) != 0) 3629 appendStringInfo(es->str, " %s", quote_identifier(refname)); 3630 } 3631 else 3632 { 3633 if (objecttag != NULL && objectname != NULL) 3634 ExplainPropertyText(objecttag, objectname, es); 3635 if (namespace != NULL) 3636 ExplainPropertyText("Schema", namespace, es); 3637 ExplainPropertyText("Alias", refname, es); 3638 } 3639 } 3640 3641 /* 3642 * Show extra information for a ModifyTable node 3643 * 3644 * We have three objectives here. First, if there's more than one target 3645 * table or it's different from the nominal target, identify the actual 3646 * target(s). Second, give FDWs a chance to display extra info about foreign 3647 * targets. Third, show information about ON CONFLICT. 3648 */ 3649 static void 3650 show_modifytable_info(ModifyTableState *mtstate, List *ancestors, 3651 ExplainState *es) 3652 { 3653 ModifyTable *node = (ModifyTable *) mtstate->ps.plan; 3654 const char *operation; 3655 const char *foperation; 3656 bool labeltargets; 3657 int j; 3658 List *idxNames = NIL; 3659 ListCell *lst; 3660 3661 switch (node->operation) 3662 { 3663 case CMD_INSERT: 3664 operation = "Insert"; 3665 foperation = "Foreign Insert"; 3666 break; 3667 case CMD_UPDATE: 3668 operation = "Update"; 3669 foperation = "Foreign Update"; 3670 break; 3671 case CMD_DELETE: 3672 operation = "Delete"; 3673 foperation = "Foreign Delete"; 3674 break; 3675 default: 3676 operation = "???"; 3677 foperation = "Foreign ???"; 3678 break; 3679 } 3680 3681 /* Should we explicitly label target relations? */ 3682 labeltargets = (mtstate->mt_nplans > 1 || 3683 (mtstate->mt_nplans == 1 && 3684 mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation)); 3685 3686 if (labeltargets) 3687 ExplainOpenGroup("Target Tables", "Target Tables", false, es); 3688 3689 for (j = 0; j < mtstate->mt_nplans; j++) 3690 { 3691 ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j; 3692 FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; 3693 3694 if (labeltargets) 3695 { 3696 /* Open a group for this target */ 3697 ExplainOpenGroup("Target Table", NULL, true, es); 3698 3699 /* 3700 * In text mode, decorate each target with operation type, so that 3701 * ExplainTargetRel's output of " on foo" will read nicely. 3702 */ 3703 if (es->format == EXPLAIN_FORMAT_TEXT) 3704 { 3705 ExplainIndentText(es); 3706 appendStringInfoString(es->str, 3707 fdwroutine ? foperation : operation); 3708 } 3709 3710 /* Identify target */ 3711 ExplainTargetRel((Plan *) node, 3712 resultRelInfo->ri_RangeTableIndex, 3713 es); 3714 3715 if (es->format == EXPLAIN_FORMAT_TEXT) 3716 { 3717 appendStringInfoChar(es->str, '\n'); 3718 es->indent++; 3719 } 3720 } 3721 3722 /* Give FDW a chance if needed */ 3723 if (!resultRelInfo->ri_usesFdwDirectModify && 3724 fdwroutine != NULL && 3725 fdwroutine->ExplainForeignModify != NULL) 3726 { 3727 List *fdw_private = (List *) list_nth(node->fdwPrivLists, j); 3728 3729 fdwroutine->ExplainForeignModify(mtstate, 3730 resultRelInfo, 3731 fdw_private, 3732 j, 3733 es); 3734 } 3735 3736 if (labeltargets) 3737 { 3738 /* Undo the indentation we added in text format */ 3739 if (es->format == EXPLAIN_FORMAT_TEXT) 3740 es->indent--; 3741 3742 /* Close the group */ 3743 ExplainCloseGroup("Target Table", NULL, true, es); 3744 } 3745 } 3746 3747 /* Gather names of ON CONFLICT arbiter indexes */ 3748 foreach(lst, node->arbiterIndexes) 3749 { 3750 char *indexname = get_rel_name(lfirst_oid(lst)); 3751 3752 idxNames = lappend(idxNames, indexname); 3753 } 3754 3755 if (node->onConflictAction != ONCONFLICT_NONE) 3756 { 3757 ExplainPropertyText("Conflict Resolution", 3758 node->onConflictAction == ONCONFLICT_NOTHING ? 3759 "NOTHING" : "UPDATE", 3760 es); 3761 3762 /* 3763 * Don't display arbiter indexes at all when DO NOTHING variant 3764 * implicitly ignores all conflicts 3765 */ 3766 if (idxNames) 3767 ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es); 3768 3769 /* ON CONFLICT DO UPDATE WHERE qual is specially displayed */ 3770 if (node->onConflictWhere) 3771 { 3772 show_upper_qual((List *) node->onConflictWhere, "Conflict Filter", 3773 &mtstate->ps, ancestors, es); 3774 show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es); 3775 } 3776 3777 /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */ 3778 if (es->analyze && mtstate->ps.instrument) 3779 { 3780 double total; 3781 double insert_path; 3782 double other_path; 3783 3784 InstrEndLoop(mtstate->mt_plans[0]->instrument); 3785 3786 /* count the number of source rows */ 3787 total = mtstate->mt_plans[0]->instrument->ntuples; 3788 other_path = mtstate->ps.instrument->ntuples2; 3789 insert_path = total - other_path; 3790 3791 ExplainPropertyFloat("Tuples Inserted", NULL, 3792 insert_path, 0, es); 3793 ExplainPropertyFloat("Conflicting Tuples", NULL, 3794 other_path, 0, es); 3795 } 3796 } 3797 3798 if (labeltargets) 3799 ExplainCloseGroup("Target Tables", "Target Tables", false, es); 3800 } 3801 3802 /* 3803 * Explain the constituent plans of a ModifyTable, Append, MergeAppend, 3804 * BitmapAnd, or BitmapOr node. 3805 * 3806 * The ancestors list should already contain the immediate parent of these 3807 * plans. 3808 */ 3809 static void 3810 ExplainMemberNodes(PlanState **planstates, int nplans, 3811 List *ancestors, ExplainState *es) 3812 { 3813 int j; 3814 3815 for (j = 0; j < nplans; j++) 3816 ExplainNode(planstates[j], ancestors, 3817 "Member", NULL, es); 3818 } 3819 3820 /* 3821 * Report about any pruned subnodes of an Append or MergeAppend node. 3822 * 3823 * nplans indicates the number of live subplans. 3824 * nchildren indicates the original number of subnodes in the Plan; 3825 * some of these may have been pruned by the run-time pruning code. 3826 */ 3827 static void 3828 ExplainMissingMembers(int nplans, int nchildren, ExplainState *es) 3829 { 3830 if (nplans < nchildren || es->format != EXPLAIN_FORMAT_TEXT) 3831 ExplainPropertyInteger("Subplans Removed", NULL, 3832 nchildren - nplans, es); 3833 } 3834 3835 /* 3836 * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes). 3837 * 3838 * The ancestors list should already contain the immediate parent of these 3839 * SubPlans. 3840 */ 3841 static void 3842 ExplainSubPlans(List *plans, List *ancestors, 3843 const char *relationship, ExplainState *es) 3844 { 3845 ListCell *lst; 3846 3847 foreach(lst, plans) 3848 { 3849 SubPlanState *sps = (SubPlanState *) lfirst(lst); 3850 SubPlan *sp = sps->subplan; 3851 3852 /* 3853 * There can be multiple SubPlan nodes referencing the same physical 3854 * subplan (same plan_id, which is its index in PlannedStmt.subplans). 3855 * We should print a subplan only once, so track which ones we already 3856 * printed. This state must be global across the plan tree, since the 3857 * duplicate nodes could be in different plan nodes, eg both a bitmap 3858 * indexscan's indexqual and its parent heapscan's recheck qual. (We 3859 * do not worry too much about which plan node we show the subplan as 3860 * attached to in such cases.) 3861 */ 3862 if (bms_is_member(sp->plan_id, es->printed_subplans)) 3863 continue; 3864 es->printed_subplans = bms_add_member(es->printed_subplans, 3865 sp->plan_id); 3866 3867 /* 3868 * Treat the SubPlan node as an ancestor of the plan node(s) within 3869 * it, so that ruleutils.c can find the referents of subplan 3870 * parameters. 3871 */ 3872 ancestors = lcons(sp, ancestors); 3873 3874 ExplainNode(sps->planstate, ancestors, 3875 relationship, sp->plan_name, es); 3876 3877 ancestors = list_delete_first(ancestors); 3878 } 3879 } 3880 3881 /* 3882 * Explain a list of children of a CustomScan. 3883 */ 3884 static void 3885 ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es) 3886 { 3887 ListCell *cell; 3888 const char *label = 3889 (list_length(css->custom_ps) != 1 ? "children" : "child"); 3890 3891 foreach(cell, css->custom_ps) 3892 ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es); 3893 } 3894 3895 /* 3896 * Create a per-plan-node workspace for collecting per-worker data. 3897 * 3898 * Output related to each worker will be temporarily "set aside" into a 3899 * separate buffer, which we'll merge into the main output stream once 3900 * we've processed all data for the plan node. This makes it feasible to 3901 * generate a coherent sub-group of fields for each worker, even though the 3902 * code that produces the fields is in several different places in this file. 3903 * Formatting of such a set-aside field group is managed by 3904 * ExplainOpenSetAsideGroup and ExplainSaveGroup/ExplainRestoreGroup. 3905 */ 3906 static ExplainWorkersState * 3907 ExplainCreateWorkersState(int num_workers) 3908 { 3909 ExplainWorkersState *wstate; 3910 3911 wstate = (ExplainWorkersState *) palloc(sizeof(ExplainWorkersState)); 3912 wstate->num_workers = num_workers; 3913 wstate->worker_inited = (bool *) palloc0(num_workers * sizeof(bool)); 3914 wstate->worker_str = (StringInfoData *) 3915 palloc0(num_workers * sizeof(StringInfoData)); 3916 wstate->worker_state_save = (int *) palloc(num_workers * sizeof(int)); 3917 return wstate; 3918 } 3919 3920 /* 3921 * Begin or resume output into the set-aside group for worker N. 3922 */ 3923 static void 3924 ExplainOpenWorker(int n, ExplainState *es) 3925 { 3926 ExplainWorkersState *wstate = es->workers_state; 3927 3928 Assert(wstate); 3929 Assert(n >= 0 && n < wstate->num_workers); 3930 3931 /* Save prior output buffer pointer */ 3932 wstate->prev_str = es->str; 3933 3934 if (!wstate->worker_inited[n]) 3935 { 3936 /* First time through, so create the buffer for this worker */ 3937 initStringInfo(&wstate->worker_str[n]); 3938 es->str = &wstate->worker_str[n]; 3939 3940 /* 3941 * Push suitable initial formatting state for this worker's field 3942 * group. We allow one extra logical nesting level, since this group 3943 * will eventually be wrapped in an outer "Workers" group. 3944 */ 3945 ExplainOpenSetAsideGroup("Worker", NULL, true, 2, es); 3946 3947 /* 3948 * In non-TEXT formats we always emit a "Worker Number" field, even if 3949 * there's no other data for this worker. 3950 */ 3951 if (es->format != EXPLAIN_FORMAT_TEXT) 3952 ExplainPropertyInteger("Worker Number", NULL, n, es); 3953 3954 wstate->worker_inited[n] = true; 3955 } 3956 else 3957 { 3958 /* Resuming output for a worker we've already emitted some data for */ 3959 es->str = &wstate->worker_str[n]; 3960 3961 /* Restore formatting state saved by last ExplainCloseWorker() */ 3962 ExplainRestoreGroup(es, 2, &wstate->worker_state_save[n]); 3963 } 3964 3965 /* 3966 * In TEXT format, prefix the first output line for this worker with 3967 * "Worker N:". Then, any additional lines should be indented one more 3968 * stop than the "Worker N" line is. 3969 */ 3970 if (es->format == EXPLAIN_FORMAT_TEXT) 3971 { 3972 if (es->str->len == 0) 3973 { 3974 ExplainIndentText(es); 3975 appendStringInfo(es->str, "Worker %d: ", n); 3976 } 3977 3978 es->indent++; 3979 } 3980 } 3981 3982 /* 3983 * End output for worker N --- must pair with previous ExplainOpenWorker call 3984 */ 3985 static void 3986 ExplainCloseWorker(int n, ExplainState *es) 3987 { 3988 ExplainWorkersState *wstate = es->workers_state; 3989 3990 Assert(wstate); 3991 Assert(n >= 0 && n < wstate->num_workers); 3992 Assert(wstate->worker_inited[n]); 3993 3994 /* 3995 * Save formatting state in case we do another ExplainOpenWorker(), then 3996 * pop the formatting stack. 3997 */ 3998 ExplainSaveGroup(es, 2, &wstate->worker_state_save[n]); 3999 4000 /* 4001 * In TEXT format, if we didn't actually produce any output line(s) then 4002 * truncate off the partial line emitted by ExplainOpenWorker. (This is 4003 * to avoid bogus output if, say, show_buffer_usage chooses not to print 4004 * anything for the worker.) Also fix up the indent level. 4005 */ 4006 if (es->format == EXPLAIN_FORMAT_TEXT) 4007 { 4008 while (es->str->len > 0 && es->str->data[es->str->len - 1] != '\n') 4009 es->str->data[--(es->str->len)] = '\0'; 4010 4011 es->indent--; 4012 } 4013 4014 /* Restore prior output buffer pointer */ 4015 es->str = wstate->prev_str; 4016 } 4017 4018 /* 4019 * Print per-worker info for current node, then free the ExplainWorkersState. 4020 */ 4021 static void 4022 ExplainFlushWorkersState(ExplainState *es) 4023 { 4024 ExplainWorkersState *wstate = es->workers_state; 4025 4026 ExplainOpenGroup("Workers", "Workers", false, es); 4027 for (int i = 0; i < wstate->num_workers; i++) 4028 { 4029 if (wstate->worker_inited[i]) 4030 { 4031 /* This must match previous ExplainOpenSetAsideGroup call */ 4032 ExplainOpenGroup("Worker", NULL, true, es); 4033 appendStringInfoString(es->str, wstate->worker_str[i].data); 4034 ExplainCloseGroup("Worker", NULL, true, es); 4035 4036 pfree(wstate->worker_str[i].data); 4037 } 4038 } 4039 ExplainCloseGroup("Workers", "Workers", false, es); 4040 4041 pfree(wstate->worker_inited); 4042 pfree(wstate->worker_str); 4043 pfree(wstate->worker_state_save); 4044 pfree(wstate); 4045 } 4046 4047 /* 4048 * Explain a property, such as sort keys or targets, that takes the form of 4049 * a list of unlabeled items. "data" is a list of C strings. 4050 */ 4051 void 4052 ExplainPropertyList(const char *qlabel, List *data, ExplainState *es) 4053 { 4054 ListCell *lc; 4055 bool first = true; 4056 4057 switch (es->format) 4058 { 4059 case EXPLAIN_FORMAT_TEXT: 4060 ExplainIndentText(es); 4061 appendStringInfo(es->str, "%s: ", qlabel); 4062 foreach(lc, data) 4063 { 4064 if (!first) 4065 appendStringInfoString(es->str, ", "); 4066 appendStringInfoString(es->str, (const char *) lfirst(lc)); 4067 first = false; 4068 } 4069 appendStringInfoChar(es->str, '\n'); 4070 break; 4071 4072 case EXPLAIN_FORMAT_XML: 4073 ExplainXMLTag(qlabel, X_OPENING, es); 4074 foreach(lc, data) 4075 { 4076 char *str; 4077 4078 appendStringInfoSpaces(es->str, es->indent * 2 + 2); 4079 appendStringInfoString(es->str, "<Item>"); 4080 str = escape_xml((const char *) lfirst(lc)); 4081 appendStringInfoString(es->str, str); 4082 pfree(str); 4083 appendStringInfoString(es->str, "</Item>\n"); 4084 } 4085 ExplainXMLTag(qlabel, X_CLOSING, es); 4086 break; 4087 4088 case EXPLAIN_FORMAT_JSON: 4089 ExplainJSONLineEnding(es); 4090 appendStringInfoSpaces(es->str, es->indent * 2); 4091 escape_json(es->str, qlabel); 4092 appendStringInfoString(es->str, ": ["); 4093 foreach(lc, data) 4094 { 4095 if (!first) 4096 appendStringInfoString(es->str, ", "); 4097 escape_json(es->str, (const char *) lfirst(lc)); 4098 first = false; 4099 } 4100 appendStringInfoChar(es->str, ']'); 4101 break; 4102 4103 case EXPLAIN_FORMAT_YAML: 4104 ExplainYAMLLineStarting(es); 4105 appendStringInfo(es->str, "%s: ", qlabel); 4106 foreach(lc, data) 4107 { 4108 appendStringInfoChar(es->str, '\n'); 4109 appendStringInfoSpaces(es->str, es->indent * 2 + 2); 4110 appendStringInfoString(es->str, "- "); 4111 escape_yaml(es->str, (const char *) lfirst(lc)); 4112 } 4113 break; 4114 } 4115 } 4116 4117 /* 4118 * Explain a property that takes the form of a list of unlabeled items within 4119 * another list. "data" is a list of C strings. 4120 */ 4121 void 4122 ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es) 4123 { 4124 ListCell *lc; 4125 bool first = true; 4126 4127 switch (es->format) 4128 { 4129 case EXPLAIN_FORMAT_TEXT: 4130 case EXPLAIN_FORMAT_XML: 4131 ExplainPropertyList(qlabel, data, es); 4132 return; 4133 4134 case EXPLAIN_FORMAT_JSON: 4135 ExplainJSONLineEnding(es); 4136 appendStringInfoSpaces(es->str, es->indent * 2); 4137 appendStringInfoChar(es->str, '['); 4138 foreach(lc, data) 4139 { 4140 if (!first) 4141 appendStringInfoString(es->str, ", "); 4142 escape_json(es->str, (const char *) lfirst(lc)); 4143 first = false; 4144 } 4145 appendStringInfoChar(es->str, ']'); 4146 break; 4147 4148 case EXPLAIN_FORMAT_YAML: 4149 ExplainYAMLLineStarting(es); 4150 appendStringInfoString(es->str, "- ["); 4151 foreach(lc, data) 4152 { 4153 if (!first) 4154 appendStringInfoString(es->str, ", "); 4155 escape_yaml(es->str, (const char *) lfirst(lc)); 4156 first = false; 4157 } 4158 appendStringInfoChar(es->str, ']'); 4159 break; 4160 } 4161 } 4162 4163 /* 4164 * Explain a simple property. 4165 * 4166 * If "numeric" is true, the value is a number (or other value that 4167 * doesn't need quoting in JSON). 4168 * 4169 * If unit is non-NULL the text format will display it after the value. 4170 * 4171 * This usually should not be invoked directly, but via one of the datatype 4172 * specific routines ExplainPropertyText, ExplainPropertyInteger, etc. 4173 */ 4174 static void 4175 ExplainProperty(const char *qlabel, const char *unit, const char *value, 4176 bool numeric, ExplainState *es) 4177 { 4178 switch (es->format) 4179 { 4180 case EXPLAIN_FORMAT_TEXT: 4181 ExplainIndentText(es); 4182 if (unit) 4183 appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit); 4184 else 4185 appendStringInfo(es->str, "%s: %s\n", qlabel, value); 4186 break; 4187 4188 case EXPLAIN_FORMAT_XML: 4189 { 4190 char *str; 4191 4192 appendStringInfoSpaces(es->str, es->indent * 2); 4193 ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es); 4194 str = escape_xml(value); 4195 appendStringInfoString(es->str, str); 4196 pfree(str); 4197 ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es); 4198 appendStringInfoChar(es->str, '\n'); 4199 } 4200 break; 4201 4202 case EXPLAIN_FORMAT_JSON: 4203 ExplainJSONLineEnding(es); 4204 appendStringInfoSpaces(es->str, es->indent * 2); 4205 escape_json(es->str, qlabel); 4206 appendStringInfoString(es->str, ": "); 4207 if (numeric) 4208 appendStringInfoString(es->str, value); 4209 else 4210 escape_json(es->str, value); 4211 break; 4212 4213 case EXPLAIN_FORMAT_YAML: 4214 ExplainYAMLLineStarting(es); 4215 appendStringInfo(es->str, "%s: ", qlabel); 4216 if (numeric) 4217 appendStringInfoString(es->str, value); 4218 else 4219 escape_yaml(es->str, value); 4220 break; 4221 } 4222 } 4223 4224 /* 4225 * Explain a string-valued property. 4226 */ 4227 void 4228 ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es) 4229 { 4230 ExplainProperty(qlabel, NULL, value, false, es); 4231 } 4232 4233 /* 4234 * Explain an integer-valued property. 4235 */ 4236 void 4237 ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value, 4238 ExplainState *es) 4239 { 4240 char buf[32]; 4241 4242 snprintf(buf, sizeof(buf), INT64_FORMAT, value); 4243 ExplainProperty(qlabel, unit, buf, true, es); 4244 } 4245 4246 /* 4247 * Explain an unsigned integer-valued property. 4248 */ 4249 void 4250 ExplainPropertyUInteger(const char *qlabel, const char *unit, uint64 value, 4251 ExplainState *es) 4252 { 4253 char buf[32]; 4254 4255 snprintf(buf, sizeof(buf), UINT64_FORMAT, value); 4256 ExplainProperty(qlabel, unit, buf, true, es); 4257 } 4258 4259 /* 4260 * Explain a float-valued property, using the specified number of 4261 * fractional digits. 4262 */ 4263 void 4264 ExplainPropertyFloat(const char *qlabel, const char *unit, double value, 4265 int ndigits, ExplainState *es) 4266 { 4267 char *buf; 4268 4269 buf = psprintf("%.*f", ndigits, value); 4270 ExplainProperty(qlabel, unit, buf, true, es); 4271 pfree(buf); 4272 } 4273 4274 /* 4275 * Explain a bool-valued property. 4276 */ 4277 void 4278 ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es) 4279 { 4280 ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es); 4281 } 4282 4283 /* 4284 * Open a group of related objects. 4285 * 4286 * objtype is the type of the group object, labelname is its label within 4287 * a containing object (if any). 4288 * 4289 * If labeled is true, the group members will be labeled properties, 4290 * while if it's false, they'll be unlabeled objects. 4291 */ 4292 void 4293 ExplainOpenGroup(const char *objtype, const char *labelname, 4294 bool labeled, ExplainState *es) 4295 { 4296 switch (es->format) 4297 { 4298 case EXPLAIN_FORMAT_TEXT: 4299 /* nothing to do */ 4300 break; 4301 4302 case EXPLAIN_FORMAT_XML: 4303 ExplainXMLTag(objtype, X_OPENING, es); 4304 es->indent++; 4305 break; 4306 4307 case EXPLAIN_FORMAT_JSON: 4308 ExplainJSONLineEnding(es); 4309 appendStringInfoSpaces(es->str, 2 * es->indent); 4310 if (labelname) 4311 { 4312 escape_json(es->str, labelname); 4313 appendStringInfoString(es->str, ": "); 4314 } 4315 appendStringInfoChar(es->str, labeled ? '{' : '['); 4316 4317 /* 4318 * In JSON format, the grouping_stack is an integer list. 0 means 4319 * we've emitted nothing at this grouping level, 1 means we've 4320 * emitted something (and so the next item needs a comma). See 4321 * ExplainJSONLineEnding(). 4322 */ 4323 es->grouping_stack = lcons_int(0, es->grouping_stack); 4324 es->indent++; 4325 break; 4326 4327 case EXPLAIN_FORMAT_YAML: 4328 4329 /* 4330 * In YAML format, the grouping stack is an integer list. 0 means 4331 * we've emitted nothing at this grouping level AND this grouping 4332 * level is unlabeled and must be marked with "- ". See 4333 * ExplainYAMLLineStarting(). 4334 */ 4335 ExplainYAMLLineStarting(es); 4336 if (labelname) 4337 { 4338 appendStringInfo(es->str, "%s: ", labelname); 4339 es->grouping_stack = lcons_int(1, es->grouping_stack); 4340 } 4341 else 4342 { 4343 appendStringInfoString(es->str, "- "); 4344 es->grouping_stack = lcons_int(0, es->grouping_stack); 4345 } 4346 es->indent++; 4347 break; 4348 } 4349 } 4350 4351 /* 4352 * Close a group of related objects. 4353 * Parameters must match the corresponding ExplainOpenGroup call. 4354 */ 4355 void 4356 ExplainCloseGroup(const char *objtype, const char *labelname, 4357 bool labeled, ExplainState *es) 4358 { 4359 switch (es->format) 4360 { 4361 case EXPLAIN_FORMAT_TEXT: 4362 /* nothing to do */ 4363 break; 4364 4365 case EXPLAIN_FORMAT_XML: 4366 es->indent--; 4367 ExplainXMLTag(objtype, X_CLOSING, es); 4368 break; 4369 4370 case EXPLAIN_FORMAT_JSON: 4371 es->indent--; 4372 appendStringInfoChar(es->str, '\n'); 4373 appendStringInfoSpaces(es->str, 2 * es->indent); 4374 appendStringInfoChar(es->str, labeled ? '}' : ']'); 4375 es->grouping_stack = list_delete_first(es->grouping_stack); 4376 break; 4377 4378 case EXPLAIN_FORMAT_YAML: 4379 es->indent--; 4380 es->grouping_stack = list_delete_first(es->grouping_stack); 4381 break; 4382 } 4383 } 4384 4385 /* 4386 * Open a group of related objects, without emitting actual data. 4387 * 4388 * Prepare the formatting state as though we were beginning a group with 4389 * the identified properties, but don't actually emit anything. Output 4390 * subsequent to this call can be redirected into a separate output buffer, 4391 * and then eventually appended to the main output buffer after doing a 4392 * regular ExplainOpenGroup call (with the same parameters). 4393 * 4394 * The extra "depth" parameter is the new group's depth compared to current. 4395 * It could be more than one, in case the eventual output will be enclosed 4396 * in additional nesting group levels. We assume we don't need to track 4397 * formatting state for those levels while preparing this group's output. 4398 * 4399 * There is no ExplainCloseSetAsideGroup --- in current usage, we always 4400 * pop this state with ExplainSaveGroup. 4401 */ 4402 static void 4403 ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, 4404 bool labeled, int depth, ExplainState *es) 4405 { 4406 switch (es->format) 4407 { 4408 case EXPLAIN_FORMAT_TEXT: 4409 /* nothing to do */ 4410 break; 4411 4412 case EXPLAIN_FORMAT_XML: 4413 es->indent += depth; 4414 break; 4415 4416 case EXPLAIN_FORMAT_JSON: 4417 es->grouping_stack = lcons_int(0, es->grouping_stack); 4418 es->indent += depth; 4419 break; 4420 4421 case EXPLAIN_FORMAT_YAML: 4422 if (labelname) 4423 es->grouping_stack = lcons_int(1, es->grouping_stack); 4424 else 4425 es->grouping_stack = lcons_int(0, es->grouping_stack); 4426 es->indent += depth; 4427 break; 4428 } 4429 } 4430 4431 /* 4432 * Pop one level of grouping state, allowing for a re-push later. 4433 * 4434 * This is typically used after ExplainOpenSetAsideGroup; pass the 4435 * same "depth" used for that. 4436 * 4437 * This should not emit any output. If state needs to be saved, 4438 * save it at *state_save. Currently, an integer save area is sufficient 4439 * for all formats, but we might need to revisit that someday. 4440 */ 4441 static void 4442 ExplainSaveGroup(ExplainState *es, int depth, int *state_save) 4443 { 4444 switch (es->format) 4445 { 4446 case EXPLAIN_FORMAT_TEXT: 4447 /* nothing to do */ 4448 break; 4449 4450 case EXPLAIN_FORMAT_XML: 4451 es->indent -= depth; 4452 break; 4453 4454 case EXPLAIN_FORMAT_JSON: 4455 es->indent -= depth; 4456 *state_save = linitial_int(es->grouping_stack); 4457 es->grouping_stack = list_delete_first(es->grouping_stack); 4458 break; 4459 4460 case EXPLAIN_FORMAT_YAML: 4461 es->indent -= depth; 4462 *state_save = linitial_int(es->grouping_stack); 4463 es->grouping_stack = list_delete_first(es->grouping_stack); 4464 break; 4465 } 4466 } 4467 4468 /* 4469 * Re-push one level of grouping state, undoing the effects of ExplainSaveGroup. 4470 */ 4471 static void 4472 ExplainRestoreGroup(ExplainState *es, int depth, int *state_save) 4473 { 4474 switch (es->format) 4475 { 4476 case EXPLAIN_FORMAT_TEXT: 4477 /* nothing to do */ 4478 break; 4479 4480 case EXPLAIN_FORMAT_XML: 4481 es->indent += depth; 4482 break; 4483 4484 case EXPLAIN_FORMAT_JSON: 4485 es->grouping_stack = lcons_int(*state_save, es->grouping_stack); 4486 es->indent += depth; 4487 break; 4488 4489 case EXPLAIN_FORMAT_YAML: 4490 es->grouping_stack = lcons_int(*state_save, es->grouping_stack); 4491 es->indent += depth; 4492 break; 4493 } 4494 } 4495 4496 /* 4497 * Emit a "dummy" group that never has any members. 4498 * 4499 * objtype is the type of the group object, labelname is its label within 4500 * a containing object (if any). 4501 */ 4502 static void 4503 ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es) 4504 { 4505 switch (es->format) 4506 { 4507 case EXPLAIN_FORMAT_TEXT: 4508 /* nothing to do */ 4509 break; 4510 4511 case EXPLAIN_FORMAT_XML: 4512 ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es); 4513 break; 4514 4515 case EXPLAIN_FORMAT_JSON: 4516 ExplainJSONLineEnding(es); 4517 appendStringInfoSpaces(es->str, 2 * es->indent); 4518 if (labelname) 4519 { 4520 escape_json(es->str, labelname); 4521 appendStringInfoString(es->str, ": "); 4522 } 4523 escape_json(es->str, objtype); 4524 break; 4525 4526 case EXPLAIN_FORMAT_YAML: 4527 ExplainYAMLLineStarting(es); 4528 if (labelname) 4529 { 4530 escape_yaml(es->str, labelname); 4531 appendStringInfoString(es->str, ": "); 4532 } 4533 else 4534 { 4535 appendStringInfoString(es->str, "- "); 4536 } 4537 escape_yaml(es->str, objtype); 4538 break; 4539 } 4540 } 4541 4542 /* 4543 * Emit the start-of-output boilerplate. 4544 * 4545 * This is just enough different from processing a subgroup that we need 4546 * a separate pair of subroutines. 4547 */ 4548 void 4549 ExplainBeginOutput(ExplainState *es) 4550 { 4551 switch (es->format) 4552 { 4553 case EXPLAIN_FORMAT_TEXT: 4554 /* nothing to do */ 4555 break; 4556 4557 case EXPLAIN_FORMAT_XML: 4558 appendStringInfoString(es->str, 4559 "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n"); 4560 es->indent++; 4561 break; 4562 4563 case EXPLAIN_FORMAT_JSON: 4564 /* top-level structure is an array of plans */ 4565 appendStringInfoChar(es->str, '['); 4566 es->grouping_stack = lcons_int(0, es->grouping_stack); 4567 es->indent++; 4568 break; 4569 4570 case EXPLAIN_FORMAT_YAML: 4571 es->grouping_stack = lcons_int(0, es->grouping_stack); 4572 break; 4573 } 4574 } 4575 4576 /* 4577 * Emit the end-of-output boilerplate. 4578 */ 4579 void 4580 ExplainEndOutput(ExplainState *es) 4581 { 4582 switch (es->format) 4583 { 4584 case EXPLAIN_FORMAT_TEXT: 4585 /* nothing to do */ 4586 break; 4587 4588 case EXPLAIN_FORMAT_XML: 4589 es->indent--; 4590 appendStringInfoString(es->str, "</explain>"); 4591 break; 4592 4593 case EXPLAIN_FORMAT_JSON: 4594 es->indent--; 4595 appendStringInfoString(es->str, "\n]"); 4596 es->grouping_stack = list_delete_first(es->grouping_stack); 4597 break; 4598 4599 case EXPLAIN_FORMAT_YAML: 4600 es->grouping_stack = list_delete_first(es->grouping_stack); 4601 break; 4602 } 4603 } 4604 4605 /* 4606 * Put an appropriate separator between multiple plans 4607 */ 4608 void 4609 ExplainSeparatePlans(ExplainState *es) 4610 { 4611 switch (es->format) 4612 { 4613 case EXPLAIN_FORMAT_TEXT: 4614 /* add a blank line */ 4615 appendStringInfoChar(es->str, '\n'); 4616 break; 4617 4618 case EXPLAIN_FORMAT_XML: 4619 case EXPLAIN_FORMAT_JSON: 4620 case EXPLAIN_FORMAT_YAML: 4621 /* nothing to do */ 4622 break; 4623 } 4624 } 4625 4626 /* 4627 * Emit opening or closing XML tag. 4628 * 4629 * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE. 4630 * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally 4631 * add. 4632 * 4633 * XML restricts tag names more than our other output formats, eg they can't 4634 * contain white space or slashes. Replace invalid characters with dashes, 4635 * so that for example "I/O Read Time" becomes "I-O-Read-Time". 4636 */ 4637 static void 4638 ExplainXMLTag(const char *tagname, int flags, ExplainState *es) 4639 { 4640 const char *s; 4641 const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; 4642 4643 if ((flags & X_NOWHITESPACE) == 0) 4644 appendStringInfoSpaces(es->str, 2 * es->indent); 4645 appendStringInfoCharMacro(es->str, '<'); 4646 if ((flags & X_CLOSING) != 0) 4647 appendStringInfoCharMacro(es->str, '/'); 4648 for (s = tagname; *s; s++) 4649 appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-'); 4650 if ((flags & X_CLOSE_IMMEDIATE) != 0) 4651 appendStringInfoString(es->str, " /"); 4652 appendStringInfoCharMacro(es->str, '>'); 4653 if ((flags & X_NOWHITESPACE) == 0) 4654 appendStringInfoCharMacro(es->str, '\n'); 4655 } 4656 4657 /* 4658 * Indent a text-format line. 4659 * 4660 * We indent by two spaces per indentation level. However, when emitting 4661 * data for a parallel worker there might already be data on the current line 4662 * (cf. ExplainOpenWorker); in that case, don't indent any more. 4663 */ 4664 static void 4665 ExplainIndentText(ExplainState *es) 4666 { 4667 Assert(es->format == EXPLAIN_FORMAT_TEXT); 4668 if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n') 4669 appendStringInfoSpaces(es->str, es->indent * 2); 4670 } 4671 4672 /* 4673 * Emit a JSON line ending. 4674 * 4675 * JSON requires a comma after each property but the last. To facilitate this, 4676 * in JSON format, the text emitted for each property begins just prior to the 4677 * preceding line-break (and comma, if applicable). 4678 */ 4679 static void 4680 ExplainJSONLineEnding(ExplainState *es) 4681 { 4682 Assert(es->format == EXPLAIN_FORMAT_JSON); 4683 if (linitial_int(es->grouping_stack) != 0) 4684 appendStringInfoChar(es->str, ','); 4685 else 4686 linitial_int(es->grouping_stack) = 1; 4687 appendStringInfoChar(es->str, '\n'); 4688 } 4689 4690 /* 4691 * Indent a YAML line. 4692 * 4693 * YAML lines are ordinarily indented by two spaces per indentation level. 4694 * The text emitted for each property begins just prior to the preceding 4695 * line-break, except for the first property in an unlabeled group, for which 4696 * it begins immediately after the "- " that introduces the group. The first 4697 * property of the group appears on the same line as the opening "- ". 4698 */ 4699 static void 4700 ExplainYAMLLineStarting(ExplainState *es) 4701 { 4702 Assert(es->format == EXPLAIN_FORMAT_YAML); 4703 if (linitial_int(es->grouping_stack) == 0) 4704 { 4705 linitial_int(es->grouping_stack) = 1; 4706 } 4707 else 4708 { 4709 appendStringInfoChar(es->str, '\n'); 4710 appendStringInfoSpaces(es->str, es->indent * 2); 4711 } 4712 } 4713 4714 /* 4715 * YAML is a superset of JSON; unfortunately, the YAML quoting rules are 4716 * ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of 4717 * http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything. 4718 * Empty strings, strings with leading or trailing whitespace, and strings 4719 * containing a variety of special characters must certainly be quoted or the 4720 * output is invalid; and other seemingly harmless strings like "0xa" or 4721 * "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean 4722 * constant rather than a string. 4723 */ 4724 static void 4725 escape_yaml(StringInfo buf, const char *str) 4726 { 4727 escape_json(buf, str); 4728 } 4729