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