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