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