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