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