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