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