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