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