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