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