Thanks to visit codestin.com
Credit goes to doxygen.postgresql.org

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