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