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