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