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