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