TLA 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 CBC 9927 : ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
165 : ParamListInfo params, DestReceiver *dest)
166 : {
167 9927 : ExplainState *es = NewExplainState();
168 : TupOutputState *tstate;
169 9927 : JumbleState *jstate = NULL;
170 : Query *query;
171 : List *rewritten;
172 : ListCell *lc;
173 9927 : bool timing_set = false;
174 9927 : bool summary_set = false;
175 :
176 : /* Parse options list. */
177 18410 : foreach(lc, stmt->options)
178 : {
179 8483 : DefElem *opt = (DefElem *) lfirst(lc);
180 :
181 8483 : if (strcmp(opt->defname, "analyze") == 0)
182 1593 : es->analyze = defGetBoolean(opt);
183 6890 : else if (strcmp(opt->defname, "verbose") == 0)
184 919 : es->verbose = defGetBoolean(opt);
185 5971 : else if (strcmp(opt->defname, "costs") == 0)
186 5125 : es->costs = defGetBoolean(opt);
187 846 : else if (strcmp(opt->defname, "buffers") == 0)
188 41 : es->buffers = defGetBoolean(opt);
189 805 : else if (strcmp(opt->defname, "wal") == 0)
190 UBC 0 : es->wal = defGetBoolean(opt);
191 CBC 805 : else if (strcmp(opt->defname, "settings") == 0)
192 6 : es->settings = defGetBoolean(opt);
193 GNC 799 : else if (strcmp(opt->defname, "generic_plan") == 0)
194 9 : es->generic = defGetBoolean(opt);
195 CBC 790 : else if (strcmp(opt->defname, "timing") == 0)
196 ECB : {
197 CBC 328 : timing_set = true;
198 GIC 328 : es->timing = defGetBoolean(opt);
199 ECB : }
200 CBC 462 : else if (strcmp(opt->defname, "summary") == 0)
201 : {
202 319 : summary_set = true;
203 GIC 319 : es->summary = defGetBoolean(opt);
204 ECB : }
205 CBC 143 : else if (strcmp(opt->defname, "format") == 0)
206 : {
207 143 : char *p = defGetString(opt);
208 :
209 143 : if (strcmp(p, "text") == 0)
210 GIC 6 : es->format = EXPLAIN_FORMAT_TEXT;
211 CBC 137 : else if (strcmp(p, "xml") == 0)
212 3 : es->format = EXPLAIN_FORMAT_XML;
213 134 : else if (strcmp(p, "json") == 0)
214 131 : es->format = EXPLAIN_FORMAT_JSON;
215 3 : else if (strcmp(p, "yaml") == 0)
216 3 : es->format = EXPLAIN_FORMAT_YAML;
217 ECB : else
218 LBC 0 : ereport(ERROR,
219 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
220 EUB : errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
221 : opt->defname, p),
222 : parser_errposition(pstate, opt->location)));
223 : }
224 : else
225 UIC 0 : ereport(ERROR,
226 : (errcode(ERRCODE_SYNTAX_ERROR),
227 EUB : 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 GIC 9927 : if (es->wal && !es->analyze)
234 UIC 0 : ereport(ERROR,
235 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
236 ECB : errmsg("EXPLAIN option WAL requires ANALYZE")));
237 EUB :
238 : /* if the timing was not set explicitly, set default value */
239 GIC 9927 : es->timing = (timing_set) ? es->timing : es->analyze;
240 :
241 : /* check that timing is used with EXPLAIN ANALYZE */
242 CBC 9927 : if (es->timing && !es->analyze)
243 UIC 0 : ereport(ERROR,
244 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
245 ECB : errmsg("EXPLAIN option TIMING requires ANALYZE")));
246 EUB :
247 : /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
248 GNC 9927 : if (es->generic && es->analyze)
249 3 : 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 GIC 9924 : es->summary = (summary_set) ? es->summary : es->analyze;
255 :
256 9924 : query = castNode(Query, stmt->query);
257 CBC 9924 : if (IsQueryIdEnabled())
258 2860 : jstate = JumbleQuery(query, pstate->p_sourcetext);
259 :
260 GIC 9924 : if (post_parse_analyze_hook)
261 2858 : (*post_parse_analyze_hook) (pstate, query, jstate);
262 :
263 ECB : /*
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 CBC 9924 : rewritten = QueryRewrite(castNode(Query, stmt->query));
270 ECB :
271 : /* emit opening boilerplate */
272 GIC 9924 : ExplainBeginOutput(es);
273 :
274 9924 : if (rewritten == NIL)
275 : {
276 : /*
277 : * In the case of an INSTEAD NOTHING, tell at least that. But in
278 ECB : * non-text format, the output is delimited, so this isn't necessary.
279 : */
280 UIC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
281 LBC 0 : appendStringInfoString(es->str, "Query rewrites to nothing\n");
282 : }
283 ECB : else
284 : {
285 : ListCell *l;
286 :
287 : /* Explain every plan */
288 GIC 19803 : foreach(l, rewritten)
289 EUB : {
290 GBC 9930 : 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 GIC 9879 : if (lnext(rewritten, l) != NULL)
296 6 : ExplainSeparatePlans(es);
297 ECB : }
298 : }
299 :
300 : /* emit closing boilerplate */
301 GIC 9873 : ExplainEndOutput(es);
302 9873 : Assert(es->indent == 0);
303 :
304 ECB : /* output tuples */
305 CBC 9873 : tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt),
306 : &TTSOpsVirtual);
307 GIC 9873 : if (es->format == EXPLAIN_FORMAT_TEXT)
308 9736 : do_text_output_multiline(tstate, es->str->data);
309 : else
310 CBC 137 : do_text_output_oneline(tstate, es->str->data);
311 9873 : end_tup_output(tstate);
312 :
313 GIC 9873 : pfree(es->str->data);
314 CBC 9873 : }
315 :
316 ECB : /*
317 : * Create a new ExplainState struct initialized with default options.
318 : */
319 : ExplainState *
320 CBC 9937 : NewExplainState(void)
321 : {
322 9937 : ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
323 ECB :
324 : /* Set default options (most fields can be left as zeroes). */
325 GIC 9937 : es->costs = true;
326 : /* Prepare output buffer. */
327 9937 : es->str = makeStringInfo();
328 :
329 CBC 9937 : return es;
330 : }
331 ECB :
332 : /*
333 : * ExplainResultDesc -
334 : * construct the result tupledesc for an EXPLAIN
335 : */
336 : TupleDesc
337 GIC 23716 : ExplainResultDesc(ExplainStmt *stmt)
338 ECB : {
339 : TupleDesc tupdesc;
340 : ListCell *lc;
341 GIC 23716 : Oid result_type = TEXTOID;
342 :
343 : /* Check for XML format option */
344 42325 : foreach(lc, stmt->options)
345 : {
346 CBC 18609 : DefElem *opt = (DefElem *) lfirst(lc);
347 :
348 GIC 18609 : if (strcmp(opt->defname, "format") == 0)
349 : {
350 CBC 364 : char *p = defGetString(opt);
351 :
352 GIC 364 : if (strcmp(p, "xml") == 0)
353 CBC 9 : result_type = XMLOID;
354 GIC 355 : else if (strcmp(p, "json") == 0)
355 CBC 328 : result_type = JSONOID;
356 : else
357 27 : result_type = TEXTOID;
358 : /* don't "break", as ExplainQuery will use the last value */
359 ECB : }
360 : }
361 :
362 : /* Need a tuple descriptor representing a single TEXT or XML column */
363 CBC 23716 : tupdesc = CreateTemplateTupleDesc(1);
364 23716 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
365 : result_type, -1, 0);
366 23716 : return tupdesc;
367 : }
368 :
369 : /*
370 : * ExplainOneQuery -
371 : * print out the execution plan for one Query
372 ECB : *
373 : * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
374 : */
375 : static void
376 GIC 9999 : 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 9999 : if (query->commandType == CMD_UTILITY)
383 : {
384 280 : ExplainOneUtility(query->utilityStmt, into, es, queryString, params,
385 ECB : queryEnv);
386 GIC 265 : return;
387 : }
388 :
389 : /* if an advisor plugin is present, let it manage things */
390 9719 : if (ExplainOneQuery_hook)
391 LBC 0 : (*ExplainOneQuery_hook) (query, cursorOptions, into, es,
392 : queryString, params, queryEnv);
393 ECB : else
394 : {
395 : PlannedStmt *plan;
396 : instr_time planstart,
397 : planduration;
398 : BufferUsage bufusage_start,
399 : bufusage;
400 EUB :
401 GIC 9719 : if (es->buffers)
402 41 : bufusage_start = pgBufferUsage;
403 9719 : INSTR_TIME_SET_CURRENT(planstart);
404 :
405 : /* plan the query */
406 9719 : plan = pg_plan_query(query, queryString, cursorOptions, params);
407 :
408 9707 : INSTR_TIME_SET_CURRENT(planduration);
409 9707 : INSTR_TIME_SUBTRACT(planduration, planstart);
410 ECB :
411 : /* calc differences of buffer counters. */
412 CBC 9707 : if (es->buffers)
413 : {
414 GIC 41 : memset(&bufusage, 0, sizeof(BufferUsage));
415 CBC 41 : BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);
416 : }
417 ECB :
418 : /* run it (if needed) and produce output */
419 GIC 9707 : ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
420 9707 : &planduration, (es->buffers ? &bufusage : NULL));
421 ECB : }
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 GIC 280 : ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
438 : const char *queryString, ParamListInfo params,
439 : QueryEnvironment *queryEnv)
440 : {
441 280 : if (utilityStmt == NULL)
442 UIC 0 : return;
443 :
444 GIC 280 : if (IsA(utilityStmt, CreateTableAsStmt))
445 : {
446 ECB : /*
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 CBC 75 : CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
451 EUB : List *rewritten;
452 :
453 ECB : /*
454 : * Check if the relation exists or not. This is done at this stage to
455 : * avoid query planning or execution.
456 : */
457 GIC 75 : if (CreateTableAsRelExists(ctas))
458 : {
459 CBC 15 : if (ctas->objtype == OBJECT_TABLE)
460 GIC 9 : ExplainDummyGroup("CREATE TABLE AS", NULL, es);
461 6 : else if (ctas->objtype == OBJECT_MATVIEW)
462 6 : ExplainDummyGroup("CREATE MATERIALIZED VIEW", NULL, es);
463 : else
464 UIC 0 : elog(ERROR, "unexpected object type: %d",
465 : (int) ctas->objtype);
466 CBC 15 : return;
467 : }
468 ECB :
469 CBC 45 : rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
470 45 : Assert(list_length(rewritten) == 1);
471 45 : ExplainOneQuery(linitial_node(Query, rewritten),
472 : CURSOR_OPT_PARALLEL_OK, ctas->into, es,
473 EUB : queryString, params, queryEnv);
474 : }
475 CBC 205 : else if (IsA(utilityStmt, DeclareCursorStmt))
476 : {
477 : /*
478 ECB : * 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 GIC 24 : DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
486 : List *rewritten;
487 :
488 24 : rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));
489 24 : Assert(list_length(rewritten) == 1);
490 24 : ExplainOneQuery(linitial_node(Query, rewritten),
491 : dcs->options, NULL, es,
492 : queryString, params, queryEnv);
493 : }
494 CBC 181 : else if (IsA(utilityStmt, ExecuteStmt))
495 GIC 181 : ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
496 : queryString, params, queryEnv);
497 LBC 0 : else if (IsA(utilityStmt, NotifyStmt))
498 ECB : {
499 LBC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
500 UIC 0 : appendStringInfoString(es->str, "NOTIFY\n");
501 : else
502 0 : ExplainDummyGroup("Notify", NULL, es);
503 ECB : }
504 : else
505 : {
506 UBC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
507 UIC 0 : appendStringInfoString(es->str,
508 EUB : "Utility statements have no plan structure\n");
509 : else
510 UIC 0 : ExplainDummyGroup("Utility Statement", NULL, es);
511 EUB : }
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 GIC 9888 : 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 9888 : double totaltime = 0;
536 ECB : int eflags;
537 GIC 9888 : int instrument_option = 0;
538 :
539 9888 : Assert(plannedstmt->commandType != CMD_UTILITY);
540 :
541 9888 : if (es->analyze && es->timing)
542 1271 : instrument_option |= INSTRUMENT_TIMER;
543 8617 : else if (es->analyze)
544 CBC 289 : instrument_option |= INSTRUMENT_ROWS;
545 :
546 9888 : if (es->buffers)
547 GIC 41 : instrument_option |= INSTRUMENT_BUFFERS;
548 CBC 9888 : if (es->wal)
549 UIC 0 : instrument_option |= INSTRUMENT_WAL;
550 ECB :
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 CBC 9888 : INSTR_TIME_SET_CURRENT(starttime);
557 ECB :
558 EUB : /*
559 : * Use a snapshot with an updated command ID to ensure this query sees
560 : * results of any previously executed queries.
561 : */
562 GIC 9888 : PushCopiedSnapshot(GetActiveSnapshot());
563 9888 : UpdateActiveSnapshotCommandId();
564 :
565 ECB : /*
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 GIC 9888 : if (into)
570 45 : dest = CreateIntoRelDestReceiver(into);
571 ECB : else
572 CBC 9843 : dest = None_Receiver;
573 :
574 : /* Create a QueryDesc for the query */
575 GIC 9888 : queryDesc = CreateQueryDesc(plannedstmt, queryString,
576 : GetActiveSnapshot(), InvalidSnapshot,
577 : dest, params, queryEnv, instrument_option);
578 ECB :
579 : /* Select execution options */
580 GIC 9888 : if (es->analyze)
581 CBC 1560 : eflags = 0; /* default run-to-completion flags */
582 : else
583 GIC 8328 : eflags = EXEC_FLAG_EXPLAIN_ONLY;
584 GNC 9888 : if (es->generic)
585 6 : eflags |= EXEC_FLAG_EXPLAIN_GENERIC;
586 CBC 9888 : if (into)
587 GIC 45 : eflags |= GetIntoRelEFlags(into);
588 :
589 : /* call ExecutorStart to prepare the plan for execution */
590 9888 : ExecutorStart(queryDesc, eflags);
591 ECB :
592 : /* Execute the plan for statistics if asked for */
593 GIC 9867 : if (es->analyze)
594 ECB : {
595 : ScanDirection dir;
596 :
597 : /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
598 CBC 1560 : if (into && into->skipData)
599 GIC 12 : dir = NoMovementScanDirection;
600 : else
601 CBC 1548 : dir = ForwardScanDirection;
602 :
603 : /* run the plan */
604 GNC 1560 : ExecutorRun(queryDesc, dir, 0, true);
605 :
606 : /* run cleanup too */
607 GIC 1557 : ExecutorFinish(queryDesc);
608 :
609 ECB : /* We can't run ExecutorEnd 'till we're done printing the stats... */
610 CBC 1557 : totaltime += elapsed_time(&starttime);
611 : }
612 ECB :
613 GIC 9864 : ExplainOpenGroup("Query", NULL, true, es);
614 :
615 ECB : /* Create textual dump of plan tree */
616 GIC 9864 : ExplainPrintPlan(es, queryDesc);
617 :
618 ECB : /* Show buffer usage in planning */
619 GIC 9864 : if (bufusage)
620 : {
621 CBC 41 : ExplainOpenGroup("Planning", "Planning", true, es);
622 GIC 41 : show_buffer_usage(es, bufusage, true);
623 CBC 41 : ExplainCloseGroup("Planning", "Planning", true, es);
624 : }
625 ECB :
626 GIC 9864 : if (es->summary && planduration)
627 : {
628 1271 : double plantime = INSTR_TIME_GET_DOUBLE(*planduration);
629 ECB :
630 CBC 1271 : ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es);
631 : }
632 :
633 : /* Print info about runtime of triggers */
634 GIC 9864 : if (es->analyze)
635 1557 : ExplainPrintTriggers(es, queryDesc);
636 :
637 : /*
638 ECB : * 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 GIC 9864 : if (es->costs)
644 4796 : ExplainPrintJITSummary(es, queryDesc);
645 ECB :
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 GIC 9864 : INSTR_TIME_SET_CURRENT(starttime);
651 ECB :
652 GIC 9864 : ExecutorEnd(queryDesc);
653 :
654 CBC 9864 : FreeQueryDesc(queryDesc);
655 ECB :
656 GIC 9864 : PopActiveSnapshot();
657 ECB :
658 : /* We need a CCI just in case query expanded to multiple plans */
659 GIC 9864 : if (es->analyze)
660 1557 : CommandCounterIncrement();
661 :
662 9864 : totaltime += elapsed_time(&starttime);
663 :
664 : /*
665 ECB : * 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 CBC 9864 : if (es->summary && es->analyze)
671 GIC 1271 : ExplainPropertyFloat("Execution Time", "ms", 1000.0 * totaltime, 3,
672 : es);
673 :
674 9864 : ExplainCloseGroup("Query", NULL, true, es);
675 9864 : }
676 :
677 ECB : /*
678 : * ExplainPrintSettings -
679 : * Print summary of modified settings affecting query planning.
680 : */
681 : static void
682 GIC 9874 : ExplainPrintSettings(ExplainState *es)
683 ECB : {
684 : int num;
685 : struct config_generic **gucs;
686 :
687 : /* bail out if information about settings not requested */
688 GIC 9874 : if (!es->settings)
689 CBC 9868 : return;
690 :
691 ECB : /* request an array of relevant settings */
692 GIC 6 : gucs = get_explain_guc_options(&num);
693 ECB :
694 GIC 6 : if (es->format != EXPLAIN_FORMAT_TEXT)
695 : {
696 CBC 3 : ExplainOpenGroup("Settings", "Settings", true, es);
697 :
698 9 : for (int i = 0; i < num; i++)
699 : {
700 ECB : char *setting;
701 GIC 6 : struct config_generic *conf = gucs[i];
702 :
703 CBC 6 : setting = GetConfigOptionByName(conf->name, NULL, true);
704 :
705 GIC 6 : ExplainPropertyText(conf->name, setting, es);
706 : }
707 :
708 3 : ExplainCloseGroup("Settings", "Settings", true, es);
709 : }
710 ECB : else
711 EUB : {
712 : StringInfoData str;
713 ECB :
714 : /* In TEXT mode, print nothing if there are no options */
715 CBC 3 : if (num <= 0)
716 UIC 0 : return;
717 :
718 CBC 3 : initStringInfo(&str);
719 :
720 9 : for (int i = 0; i < num; i++)
721 ECB : {
722 : char *setting;
723 CBC 6 : struct config_generic *conf = gucs[i];
724 :
725 6 : if (i > 0)
726 3 : appendStringInfoString(&str, ", ");
727 :
728 GBC 6 : setting = GetConfigOptionByName(conf->name, NULL, true);
729 :
730 GIC 6 : if (setting)
731 CBC 6 : appendStringInfo(&str, "%s = '%s'", conf->name, setting);
732 : else
733 UIC 0 : appendStringInfo(&str, "%s = NULL", conf->name);
734 : }
735 :
736 GIC 3 : 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 ECB : * in *es are initialized here.
748 : *
749 : * NB: will not work on utility statements
750 : */
751 : void
752 GIC 9874 : ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
753 ECB : {
754 CBC 9874 : Bitmapset *rels_used = NULL;
755 ECB : PlanState *ps;
756 :
757 : /* Set up ExplainState fields associated with this plan tree */
758 CBC 9874 : Assert(queryDesc->plannedstmt != NULL);
759 GIC 9874 : es->pstmt = queryDesc->plannedstmt;
760 CBC 9874 : es->rtable = queryDesc->plannedstmt->rtable;
761 GIC 9874 : ExplainPreScanNode(queryDesc->planstate, &rels_used);
762 9874 : es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
763 9874 : es->deparse_cxt = deparse_context_for_plan_tree(queryDesc->plannedstmt,
764 : es->rtable_names);
765 9874 : 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 ECB : * 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 EUB : * further down in the plan tree.
775 : */
776 GIC 9874 : ps = queryDesc->planstate;
777 CBC 9874 : if (IsA(ps, GatherState) && ((Gather *) ps->plan)->invisible)
778 : {
779 UIC 0 : ps = outerPlanState(ps);
780 0 : es->hide_workers = true;
781 : }
782 GIC 9874 : ExplainNode(ps, NIL, NULL, NULL, es);
783 ECB :
784 : /*
785 : * If requested, include information about GUC parameters with values that
786 : * don't match the built-in defaults.
787 : */
788 GIC 9874 : 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 GNC 9874 : if (es->verbose && queryDesc->plannedstmt->queryId != UINT64CONST(0) &&
796 211 : 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 4 : ExplainPropertyInteger("Query Identifier", NULL, (int64)
803 4 : queryDesc->plannedstmt->queryId, es);
804 : }
805 GIC 9874 : }
806 ECB :
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 GIC 1557 : 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 1557 : resultrels = queryDesc->estate->es_opened_result_relations;
827 1557 : routerels = queryDesc->estate->es_tuple_routing_result_relations;
828 CBC 1557 : targrels = queryDesc->estate->es_trig_target_relations;
829 :
830 GIC 1557 : ExplainOpenGroup("Triggers", "Triggers", false, es);
831 :
832 3108 : show_relname = (list_length(resultrels) > 1 ||
833 3108 : routerels != NIL || targrels != NIL);
834 1605 : foreach(l, resultrels)
835 : {
836 48 : rInfo = (ResultRelInfo *) lfirst(l);
837 CBC 48 : report_triggers(rInfo, show_relname, es);
838 ECB : }
839 :
840 GIC 1557 : foreach(l, routerels)
841 ECB : {
842 UIC 0 : rInfo = (ResultRelInfo *) lfirst(l);
843 LBC 0 : report_triggers(rInfo, show_relname, es);
844 ECB : }
845 :
846 GIC 1557 : foreach(l, targrels)
847 ECB : {
848 LBC 0 : rInfo = (ResultRelInfo *) lfirst(l);
849 UIC 0 : report_triggers(rInfo, show_relname, es);
850 : }
851 ECB :
852 GIC 1557 : ExplainCloseGroup("Triggers", "Triggers", false, es);
853 GBC 1557 : }
854 EUB :
855 : /*
856 : * ExplainPrintJITSummary -
857 ECB : * Print summarized JIT instrumentation from leader and workers
858 : */
859 EUB : void
860 GBC 4806 : ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc)
861 : {
862 GIC 4806 : JitInstrumentation ji = {0};
863 ECB :
864 CBC 4806 : if (!(queryDesc->estate->es_jit_flags & PGJIT_PERFORM))
865 GIC 4774 : return;
866 :
867 : /*
868 : * Work with a copy instead of modifying the leader state, since this
869 : * function may be called twice
870 : */
871 CBC 32 : if (queryDesc->estate->es_jit)
872 GIC 12 : InstrJitAgg(&ji, &queryDesc->estate->es_jit->instr);
873 ECB :
874 : /* If this process has done JIT in parallel workers, merge stats */
875 CBC 32 : if (queryDesc->estate->es_jit_worker_instr)
876 12 : InstrJitAgg(&ji, queryDesc->estate->es_jit_worker_instr);
877 :
878 GIC 32 : ExplainPrintJIT(es, queryDesc->estate->es_jit_flags, &ji);
879 : }
880 :
881 : /*
882 ECB : * ExplainPrintJIT -
883 : * Append information about JITing to es->str.
884 : */
885 : static void
886 CBC 32 : ExplainPrintJIT(ExplainState *es, int jit_flags, JitInstrumentation *ji)
887 ECB : {
888 : instr_time total_time;
889 :
890 : /* don't print information if no JITing happened */
891 GIC 32 : if (!ji || ji->created_functions == 0)
892 20 : return;
893 :
894 : /* calculate total time */
895 12 : INSTR_TIME_SET_ZERO(total_time);
896 12 : INSTR_TIME_ADD(total_time, ji->generation_counter);
897 CBC 12 : INSTR_TIME_ADD(total_time, ji->inlining_counter);
898 GIC 12 : INSTR_TIME_ADD(total_time, ji->optimization_counter);
899 12 : INSTR_TIME_ADD(total_time, ji->emission_counter);
900 :
901 12 : ExplainOpenGroup("JIT", "JIT", true, es);
902 ECB :
903 : /* for higher density, open code the text output format */
904 GIC 12 : if (es->format == EXPLAIN_FORMAT_TEXT)
905 : {
906 LBC 0 : ExplainIndentText(es);
907 0 : appendStringInfoString(es->str, "JIT:\n");
908 0 : es->indent++;
909 ECB :
910 LBC 0 : ExplainPropertyInteger("Functions", NULL, ji->created_functions, es);
911 :
912 0 : ExplainIndentText(es);
913 UIC 0 : appendStringInfo(es->str, "Options: %s %s, %s %s, %s %s, %s %s\n",
914 0 : "Inlining", jit_flags & PGJIT_INLINE ? "true" : "false",
915 LBC 0 : "Optimization", jit_flags & PGJIT_OPT3 ? "true" : "false",
916 UIC 0 : "Expressions", jit_flags & PGJIT_EXPR ? "true" : "false",
917 UBC 0 : "Deforming", jit_flags & PGJIT_DEFORM ? "true" : "false");
918 EUB :
919 UBC 0 : if (es->analyze && es->timing)
920 : {
921 0 : ExplainIndentText(es);
922 UIC 0 : appendStringInfo(es->str,
923 EUB : "Timing: %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms\n",
924 UBC 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 EUB :
931 UIC 0 : es->indent--;
932 EUB : }
933 : else
934 : {
935 GBC 12 : ExplainPropertyInteger("Functions", NULL, ji->created_functions, es);
936 EUB :
937 GBC 12 : ExplainOpenGroup("Options", "Options", true, es);
938 12 : ExplainPropertyBool("Inlining", jit_flags & PGJIT_INLINE, es);
939 12 : ExplainPropertyBool("Optimization", jit_flags & PGJIT_OPT3, es);
940 GIC 12 : ExplainPropertyBool("Expressions", jit_flags & PGJIT_EXPR, es);
941 12 : ExplainPropertyBool("Deforming", jit_flags & PGJIT_DEFORM, es);
942 GBC 12 : ExplainCloseGroup("Options", "Options", true, es);
943 :
944 GIC 12 : if (es->analyze && es->timing)
945 : {
946 CBC 12 : ExplainOpenGroup("Timing", "Timing", true, es);
947 :
948 12 : ExplainPropertyFloat("Generation", "ms",
949 12 : 1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter),
950 ECB : 3, es);
951 CBC 12 : ExplainPropertyFloat("Inlining", "ms",
952 12 : 1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter),
953 ECB : 3, es);
954 GIC 12 : ExplainPropertyFloat("Optimization", "ms",
955 CBC 12 : 1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter),
956 : 3, es);
957 12 : ExplainPropertyFloat("Emission", "ms",
958 GIC 12 : 1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter),
959 ECB : 3, es);
960 CBC 12 : ExplainPropertyFloat("Total", "ms",
961 GIC 12 : 1000.0 * INSTR_TIME_GET_DOUBLE(total_time),
962 ECB : 3, es);
963 :
964 GIC 12 : ExplainCloseGroup("Timing", "Timing", true, es);
965 ECB : }
966 : }
967 :
968 CBC 12 : ExplainCloseGroup("JIT", "JIT", true, es);
969 ECB : }
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 GIC 10 : ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
981 : {
982 10 : if (queryDesc->sourceText)
983 10 : ExplainPropertyText("Query Text", queryDesc->sourceText, es);
984 10 : }
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 GNC 10 : ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen)
996 : {
997 : char *str;
998 :
999 : /* This check is consistent with errdetail_params() */
1000 10 : if (params == NULL || params->numParams <= 0 || maxlen == 0)
1001 7 : return;
1002 :
1003 3 : str = BuildParamLogString(params, NULL, maxlen);
1004 3 : if (str && str[0] != '\0')
1005 3 : 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 CBC 48 : report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
1014 : {
1015 ECB : int nt;
1016 :
1017 CBC 48 : if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
1018 GIC 48 : return;
1019 UIC 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 ECB :
1029 : /*
1030 : * We ignore triggers that were never invoked; they likely aren't
1031 : * relevant to the current query type.
1032 : */
1033 LBC 0 : if (instr->ntuples == 0)
1034 0 : continue;
1035 :
1036 0 : ExplainOpenGroup("Trigger", NULL, true, es);
1037 ECB :
1038 LBC 0 : relname = RelationGetRelationName(rInfo->ri_RelationDesc);
1039 UIC 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 ECB : */
1047 UIC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
1048 : {
1049 0 : if (es->verbose || conname == NULL)
1050 LBC 0 : appendStringInfo(es->str, "Trigger %s", trig->tgname);
1051 ECB : else
1052 UBC 0 : appendStringInfoString(es->str, "Trigger");
1053 UIC 0 : if (conname)
1054 UBC 0 : appendStringInfo(es->str, " for constraint %s", conname);
1055 0 : if (show_relname)
1056 UIC 0 : appendStringInfo(es->str, " on %s", relname);
1057 UBC 0 : if (es->timing)
1058 UIC 0 : appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
1059 0 : 1000.0 * instr->total, instr->ntuples);
1060 EUB : else
1061 UIC 0 : appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples);
1062 : }
1063 : else
1064 : {
1065 0 : ExplainPropertyText("Trigger Name", trig->tgname, es);
1066 UBC 0 : if (conname)
1067 0 : ExplainPropertyText("Constraint Name", conname, es);
1068 UIC 0 : ExplainPropertyText("Relation", relname, es);
1069 UBC 0 : if (es->timing)
1070 UIC 0 : ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3,
1071 EUB : es);
1072 UBC 0 : ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es);
1073 EUB : }
1074 :
1075 UIC 0 : if (conname)
1076 0 : pfree(conname);
1077 :
1078 0 : ExplainCloseGroup("Trigger", NULL, true, es);
1079 : }
1080 EUB : }
1081 :
1082 : /* Compute elapsed time in seconds since given timestamp */
1083 : static double
1084 GIC 11421 : elapsed_time(instr_time *starttime)
1085 EUB : {
1086 : instr_time endtime;
1087 :
1088 GBC 11421 : INSTR_TIME_SET_CURRENT(endtime);
1089 11421 : INSTR_TIME_SUBTRACT(endtime, *starttime);
1090 11421 : return INSTR_TIME_GET_DOUBLE(endtime);
1091 EUB : }
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 GBC 34169 : ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
1104 : {
1105 34169 : Plan *plan = planstate->plan;
1106 :
1107 GIC 34169 : switch (nodeTag(plan))
1108 EUB : {
1109 GBC 16113 : case T_SeqScan:
1110 : case T_SampleScan:
1111 EUB : case T_IndexScan:
1112 : case T_IndexOnlyScan:
1113 : case T_BitmapHeapScan:
1114 : case T_TidScan:
1115 : case T_TidRangeScan:
1116 : case T_SubqueryScan:
1117 ECB : case T_FunctionScan:
1118 : case T_TableFuncScan:
1119 : case T_ValuesScan:
1120 : case T_CteScan:
1121 : case T_NamedTuplestoreScan:
1122 : case T_WorkTableScan:
1123 CBC 32226 : *rels_used = bms_add_member(*rels_used,
1124 GIC 16113 : ((Scan *) plan)->scanrelid);
1125 16113 : break;
1126 391 : case T_ForeignScan:
1127 782 : *rels_used = bms_add_members(*rels_used,
1128 GNC 391 : ((ForeignScan *) plan)->fs_base_relids);
1129 GIC 391 : break;
1130 UIC 0 : case T_CustomScan:
1131 0 : *rels_used = bms_add_members(*rels_used,
1132 0 : ((CustomScan *) plan)->custom_relids);
1133 0 : break;
1134 GIC 400 : case T_ModifyTable:
1135 800 : *rels_used = bms_add_member(*rels_used,
1136 CBC 400 : ((ModifyTable *) plan)->nominalRelation);
1137 GIC 400 : if (((ModifyTable *) plan)->exclRelRTI)
1138 CBC 40 : *rels_used = bms_add_member(*rels_used,
1139 GIC 40 : ((ModifyTable *) plan)->exclRelRTI);
1140 CBC 400 : break;
1141 GIC 1605 : case T_Append:
1142 CBC 3210 : *rels_used = bms_add_members(*rels_used,
1143 GIC 1605 : ((Append *) plan)->apprelids);
1144 1605 : break;
1145 131 : case T_MergeAppend:
1146 262 : *rels_used = bms_add_members(*rels_used,
1147 131 : ((MergeAppend *) plan)->apprelids);
1148 131 : break;
1149 15529 : default:
1150 15529 : break;
1151 : }
1152 :
1153 34169 : return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
1154 : }
1155 :
1156 ECB : /*
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 EUB : *
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 ECB : * 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 CBC 34091 : ExplainNode(PlanState *planstate, List *ancestors,
1179 ECB : const char *relationship, const char *plan_name,
1180 : ExplainState *es)
1181 : {
1182 CBC 34091 : Plan *plan = planstate->plan;
1183 ECB : const char *pname; /* node type name for text output */
1184 : const char *sname; /* node type name for non-text output */
1185 GIC 34091 : const char *strategy = NULL;
1186 CBC 34091 : const char *partialmode = NULL;
1187 GIC 34091 : const char *operation = NULL;
1188 34091 : const char *custom_name = NULL;
1189 34091 : ExplainWorkersState *save_workers_state = es->workers_state;
1190 34091 : 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 34091 : if (planstate->worker_instrument && es->analyze && !es->hide_workers)
1198 507 : es->workers_state = ExplainCreateWorkersState(planstate->worker_instrument->num_workers);
1199 : else
1200 33584 : es->workers_state = NULL;
1201 :
1202 : /* Identify plan node type, and print generic details */
1203 34091 : switch (nodeTag(plan))
1204 : {
1205 1026 : case T_Result:
1206 1026 : pname = sname = "Result";
1207 1026 : break;
1208 99 : case T_ProjectSet:
1209 99 : pname = sname = "ProjectSet";
1210 99 : break;
1211 CBC 400 : case T_ModifyTable:
1212 GIC 400 : sname = "ModifyTable";
1213 400 : switch (((ModifyTable *) plan)->operation)
1214 : {
1215 CBC 110 : case CMD_INSERT:
1216 GIC 110 : pname = operation = "Insert";
1217 110 : break;
1218 CBC 169 : case CMD_UPDATE:
1219 169 : pname = operation = "Update";
1220 169 : break;
1221 76 : case CMD_DELETE:
1222 76 : pname = operation = "Delete";
1223 76 : break;
1224 GIC 45 : case CMD_MERGE:
1225 45 : pname = operation = "Merge";
1226 45 : break;
1227 UIC 0 : default:
1228 0 : pname = "???";
1229 0 : break;
1230 ECB : }
1231 CBC 400 : break;
1232 GIC 1587 : case T_Append:
1233 CBC 1587 : pname = sname = "Append";
1234 GIC 1587 : break;
1235 131 : case T_MergeAppend:
1236 CBC 131 : pname = sname = "Merge Append";
1237 GIC 131 : break;
1238 CBC 27 : case T_RecursiveUnion:
1239 27 : pname = sname = "Recursive Union";
1240 27 : break;
1241 9 : case T_BitmapAnd:
1242 9 : pname = sname = "BitmapAnd";
1243 9 : break;
1244 57 : case T_BitmapOr:
1245 57 : pname = sname = "BitmapOr";
1246 57 : break;
1247 GIC 1055 : case T_NestLoop:
1248 CBC 1055 : pname = sname = "Nested Loop";
1249 1055 : break;
1250 286 : case T_MergeJoin:
1251 286 : pname = "Merge"; /* "Join" gets added by jointype switch */
1252 286 : sname = "Merge Join";
1253 286 : break;
1254 1463 : case T_HashJoin:
1255 1463 : pname = "Hash"; /* "Join" gets added by jointype switch */
1256 1463 : sname = "Hash Join";
1257 1463 : break;
1258 10709 : case T_SeqScan:
1259 10709 : pname = sname = "Seq Scan";
1260 GBC 10709 : break;
1261 33 : case T_SampleScan:
1262 33 : pname = sname = "Sample Scan";
1263 GIC 33 : break;
1264 CBC 297 : case T_Gather:
1265 297 : pname = sname = "Gather";
1266 297 : break;
1267 84 : case T_GatherMerge:
1268 84 : pname = sname = "Gather Merge";
1269 84 : break;
1270 1493 : case T_IndexScan:
1271 1493 : pname = sname = "Index Scan";
1272 1493 : break;
1273 1064 : case T_IndexOnlyScan:
1274 1064 : pname = sname = "Index Only Scan";
1275 1064 : break;
1276 1959 : case T_BitmapIndexScan:
1277 1959 : pname = sname = "Bitmap Index Scan";
1278 1959 : break;
1279 1875 : case T_BitmapHeapScan:
1280 1875 : pname = sname = "Bitmap Heap Scan";
1281 1875 : break;
1282 30 : case T_TidScan:
1283 30 : pname = sname = "Tid Scan";
1284 30 : break;
1285 42 : case T_TidRangeScan:
1286 42 : pname = sname = "Tid Range Scan";
1287 42 : break;
1288 260 : case T_SubqueryScan:
1289 260 : pname = sname = "Subquery Scan";
1290 260 : break;
1291 186 : case T_FunctionScan:
1292 186 : pname = sname = "Function Scan";
1293 186 : break;
1294 15 : case T_TableFuncScan:
1295 15 : pname = sname = "Table Function Scan";
1296 15 : break;
1297 221 : case T_ValuesScan:
1298 221 : pname = sname = "Values Scan";
1299 221 : break;
1300 107 : case T_CteScan:
1301 107 : pname = sname = "CTE Scan";
1302 107 : break;
1303 12 : case T_NamedTuplestoreScan:
1304 12 : pname = sname = "Named Tuplestore Scan";
1305 12 : break;
1306 27 : case T_WorkTableScan:
1307 27 : pname = sname = "WorkTable Scan";
1308 27 : break;
1309 391 : case T_ForeignScan:
1310 391 : sname = "Foreign Scan";
1311 391 : switch (((ForeignScan *) plan)->operation)
1312 ECB : {
1313 CBC 359 : case CMD_SELECT:
1314 359 : pname = "Foreign Scan";
1315 359 : operation = "Select";
1316 359 : break;
1317 LBC 0 : case CMD_INSERT:
1318 0 : pname = "Foreign Insert";
1319 0 : operation = "Insert";
1320 0 : break;
1321 CBC 18 : case CMD_UPDATE:
1322 18 : pname = "Foreign Update";
1323 18 : operation = "Update";
1324 18 : break;
1325 14 : case CMD_DELETE:
1326 14 : pname = "Foreign Delete";
1327 14 : operation = "Delete";
1328 14 : break;
1329 LBC 0 : default:
1330 0 : pname = "???";
1331 0 : break;
1332 ECB : }
1333 CBC 391 : break;
1334 LBC 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 ECB : else
1340 LBC 0 : pname = sname;
1341 0 : break;
1342 CBC 225 : case T_Material:
1343 225 : pname = sname = "Materialize";
1344 225 : break;
1345 GIC 68 : case T_Memoize:
1346 CBC 68 : pname = sname = "Memoize";
1347 68 : break;
1348 1776 : case T_Sort:
1349 1776 : pname = sname = "Sort";
1350 GBC 1776 : break;
1351 130 : case T_IncrementalSort:
1352 130 : pname = sname = "Incremental Sort";
1353 130 : break;
1354 CBC 39 : case T_Group:
1355 39 : pname = sname = "Group";
1356 39 : break;
1357 4512 : case T_Agg:
1358 ECB : {
1359 CBC 4512 : Agg *agg = (Agg *) plan;
1360 ECB :
1361 CBC 4512 : sname = "Aggregate";
1362 GBC 4512 : switch (agg->aggstrategy)
1363 EUB : {
1364 GBC 3495 : case AGG_PLAIN:
1365 GIC 3495 : pname = "Aggregate";
1366 CBC 3495 : strategy = "Plain";
1367 GBC 3495 : break;
1368 205 : case AGG_SORTED:
1369 205 : pname = "GroupAggregate";
1370 205 : strategy = "Sorted";
1371 205 : break;
1372 GIC 774 : case AGG_HASHED:
1373 GBC 774 : pname = "HashAggregate";
1374 774 : strategy = "Hashed";
1375 CBC 774 : break;
1376 38 : case AGG_MIXED:
1377 38 : pname = "MixedAggregate";
1378 38 : strategy = "Mixed";
1379 38 : break;
1380 LBC 0 : default:
1381 0 : pname = "Aggregate ???";
1382 0 : strategy = "???";
1383 0 : break;
1384 ECB : }
1385 :
1386 CBC 4512 : if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit))
1387 ECB : {
1388 CBC 318 : partialmode = "Partial";
1389 318 : pname = psprintf("%s %s", partialmode, pname);
1390 ECB : }
1391 GIC 4194 : else if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
1392 ECB : {
1393 GIC 232 : partialmode = "Finalize";
1394 CBC 232 : pname = psprintf("%s %s", partialmode, pname);
1395 ECB : }
1396 : else
1397 CBC 3962 : partialmode = "Simple";
1398 ECB : }
1399 CBC 4512 : break;
1400 174 : case T_WindowAgg:
1401 174 : pname = sname = "WindowAgg";
1402 174 : break;
1403 114 : case T_Unique:
1404 114 : pname = sname = "Unique";
1405 114 : break;
1406 45 : case T_SetOp:
1407 45 : sname = "SetOp";
1408 45 : switch (((SetOp *) plan)->strategy)
1409 ECB : {
1410 CBC 27 : case SETOP_SORTED:
1411 27 : pname = "SetOp";
1412 27 : strategy = "Sorted";
1413 GBC 27 : break;
1414 18 : case SETOP_HASHED:
1415 18 : pname = "HashSetOp";
1416 18 : strategy = "Hashed";
1417 GIC 18 : break;
1418 UIC 0 : default:
1419 LBC 0 : pname = "SetOp ???";
1420 UIC 0 : strategy = "???";
1421 LBC 0 : break;
1422 ECB : }
1423 GIC 45 : break;
1424 CBC 181 : case T_LockRows:
1425 GIC 181 : pname = sname = "LockRows";
1426 CBC 181 : break;
1427 419 : case T_Limit:
1428 GIC 419 : pname = sname = "Limit";
1429 419 : break;
1430 CBC 1463 : case T_Hash:
1431 GIC 1463 : pname = sname = "Hash";
1432 CBC 1463 : break;
1433 LBC 0 : default:
1434 0 : pname = sname = "???";
1435 0 : break;
1436 ECB : }
1437 :
1438 CBC 34091 : ExplainOpenGroup("Plan",
1439 ECB : relationship ? NULL : "Plan",
1440 : true, es);
1441 :
1442 GIC 34091 : if (es->format == EXPLAIN_FORMAT_TEXT)
1443 ECB : {
1444 CBC 33567 : if (plan_name)
1445 ECB : {
1446 CBC 709 : ExplainIndentText(es);
1447 709 : appendStringInfo(es->str, "%s\n", plan_name);
1448 709 : es->indent++;
1449 ECB : }
1450 CBC 33567 : if (es->indent)
1451 EUB : {
1452 GBC 23833 : ExplainIndentText(es);
1453 23833 : appendStringInfoString(es->str, "-> ");
1454 23833 : es->indent += 2;
1455 : }
1456 CBC 33567 : if (plan->parallel_aware)
1457 621 : appendStringInfoString(es->str, "Parallel ");
1458 33567 : if (plan->async_capable)
1459 51 : appendStringInfoString(es->str, "Async ");
1460 33567 : appendStringInfoString(es->str, pname);
1461 33567 : es->indent++;
1462 ECB : }
1463 : else
1464 : {
1465 CBC 524 : ExplainPropertyText("Node Type", sname, es);
1466 GBC 524 : if (strategy)
1467 81 : ExplainPropertyText("Strategy", strategy, es);
1468 524 : if (partialmode)
1469 GIC 81 : ExplainPropertyText("Partial Mode", partialmode, es);
1470 524 : if (operation)
1471 CBC 3 : ExplainPropertyText("Operation", operation, es);
1472 GIC 524 : if (relationship)
1473 384 : ExplainPropertyText("Parent Relationship", relationship, es);
1474 524 : if (plan_name)
1475 LBC 0 : ExplainPropertyText("Subplan Name", plan_name, es);
1476 GIC 524 : if (custom_name)
1477 LBC 0 : ExplainPropertyText("Custom Plan Provider", custom_name, es);
1478 GIC 524 : ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es);
1479 CBC 524 : ExplainPropertyBool("Async Capable", plan->async_capable, es);
1480 ECB : }
1481 :
1482 GIC 34091 : switch (nodeTag(plan))
1483 ECB : {
1484 GIC 13505 : case T_SeqScan:
1485 ECB : 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 GIC 13505 : ExplainScanTarget((Scan *) plan, es);
1496 13505 : break;
1497 391 : case T_ForeignScan:
1498 ECB : case T_CustomScan:
1499 CBC 391 : if (((Scan *) plan)->scanrelid > 0)
1500 283 : ExplainScanTarget((Scan *) plan, es);
1501 391 : break;
1502 1493 : case T_IndexScan:
1503 ECB : {
1504 CBC 1493 : IndexScan *indexscan = (IndexScan *) plan;
1505 ECB :
1506 CBC 1493 : ExplainIndexScanDetails(indexscan->indexid,
1507 ECB : indexscan->indexorderdir,
1508 EUB : es);
1509 CBC 1493 : ExplainScanTarget((Scan *) indexscan, es);
1510 EUB : }
1511 CBC 1493 : break;
1512 1064 : case T_IndexOnlyScan:
1513 : {
1514 GIC 1064 : IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;
1515 ECB :
1516 GIC 1064 : ExplainIndexScanDetails(indexonlyscan->indexid,
1517 ECB : indexonlyscan->indexorderdir,
1518 : es);
1519 GIC 1064 : ExplainScanTarget((Scan *) indexonlyscan, es);
1520 : }
1521 1064 : break;
1522 1959 : case T_BitmapIndexScan:
1523 : {
1524 1959 : BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
1525 : const char *indexname =
1526 1959 : explain_get_index_name(bitmapindexscan->indexid);
1527 :
1528 CBC 1959 : if (es->format == EXPLAIN_FORMAT_TEXT)
1529 1929 : appendStringInfo(es->str, " on %s",
1530 ECB : quote_identifier(indexname));
1531 : else
1532 CBC 30 : ExplainPropertyText("Index Name", indexname, es);
1533 ECB : }
1534 CBC 1959 : break;
1535 400 : case T_ModifyTable:
1536 GIC 400 : ExplainModifyTarget((ModifyTable *) plan, es);
1537 CBC 400 : break;
1538 GIC 2804 : case T_NestLoop:
1539 ECB : case T_MergeJoin:
1540 : case T_HashJoin:
1541 : {
1542 : const char *jointype;
1543 :
1544 CBC 2804 : switch (((Join *) plan)->jointype)
1545 ECB : {
1546 GIC 1557 : case JOIN_INNER:
1547 CBC 1557 : jointype = "Inner";
1548 GIC 1557 : break;
1549 CBC 515 : case JOIN_LEFT:
1550 GIC 515 : jointype = "Left";
1551 515 : break;
1552 CBC 251 : case JOIN_FULL:
1553 GIC 251 : jointype = "Full";
1554 CBC 251 : break;
1555 270 : case JOIN_RIGHT:
1556 GIC 270 : jointype = "Right";
1557 CBC 270 : break;
1558 GIC 120 : case JOIN_SEMI:
1559 CBC 120 : jointype = "Semi";
1560 GIC 120 : break;
1561 CBC 34 : case JOIN_ANTI:
1562 34 : jointype = "Anti";
1563 GIC 34 : break;
1564 GNC 57 : case JOIN_RIGHT_ANTI:
1565 57 : jointype = "Right Anti";
1566 57 : break;
1567 UIC 0 : default:
1568 LBC 0 : jointype = "???";
1569 UIC 0 : break;
1570 ECB : }
1571 CBC 2804 : if (es->format == EXPLAIN_FORMAT_TEXT)
1572 ECB : {
1573 : /*
1574 : * For historical reasons, the join type is interpolated
1575 : * into the node type name...
1576 : */
1577 GIC 2738 : if (((Join *) plan)->jointype != JOIN_INNER)
1578 1232 : appendStringInfo(es->str, " %s Join", jointype);
1579 1506 : else if (!IsA(plan, NestLoop))
1580 CBC 803 : appendStringInfoString(es->str, " Join");
1581 : }
1582 ECB : else
1583 CBC 66 : ExplainPropertyText("Join Type", jointype, es);
1584 ECB : }
1585 CBC 2804 : break;
1586 45 : case T_SetOp:
1587 ECB : {
1588 : const char *setopcmd;
1589 :
1590 CBC 45 : switch (((SetOp *) plan)->cmd)
1591 ECB : {
1592 CBC 24 : case SETOPCMD_INTERSECT:
1593 24 : setopcmd = "Intersect";
1594 24 : break;
1595 LBC 0 : case SETOPCMD_INTERSECT_ALL:
1596 0 : setopcmd = "Intersect All";
1597 0 : break;
1598 CBC 21 : case SETOPCMD_EXCEPT:
1599 21 : setopcmd = "Except";
1600 21 : break;
1601 LBC 0 : case SETOPCMD_EXCEPT_ALL:
1602 0 : setopcmd = "Except All";
1603 UBC 0 : break;
1604 0 : default:
1605 0 : setopcmd = "???";
1606 UIC 0 : break;
1607 ECB : }
1608 GIC 45 : if (es->format == EXPLAIN_FORMAT_TEXT)
1609 45 : appendStringInfo(es->str, " %s", setopcmd);
1610 : else
1611 UIC 0 : ExplainPropertyText("Command", setopcmd, es);
1612 : }
1613 CBC 45 : break;
1614 12430 : default:
1615 12430 : break;
1616 ECB : }
1617 :
1618 GIC 34091 : if (es->costs)
1619 ECB : {
1620 GIC 11056 : if (es->format == EXPLAIN_FORMAT_TEXT)
1621 ECB : {
1622 CBC 10586 : 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 ECB : else
1627 : {
1628 CBC 470 : ExplainPropertyFloat("Startup Cost", NULL, plan->startup_cost,
1629 ECB : 2, es);
1630 CBC 470 : ExplainPropertyFloat("Total Cost", NULL, plan->total_cost,
1631 EUB : 2, es);
1632 GBC 470 : ExplainPropertyFloat("Plan Rows", NULL, plan->plan_rows,
1633 EUB : 0, es);
1634 CBC 470 : ExplainPropertyInteger("Plan Width", NULL, plan->plan_width,
1635 ECB : es);
1636 : }
1637 EUB : }
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 ECB : * 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 EUB : * auto_explain has to contend with.
1648 : */
1649 CBC 34091 : if (planstate->instrument)
1650 3759 : InstrEndLoop(planstate->instrument);
1651 ECB :
1652 GIC 34091 : if (es->analyze &&
1653 3753 : planstate->instrument && planstate->instrument->nloops > 0)
1654 CBC 3382 : {
1655 GIC 3382 : double nloops = planstate->instrument->nloops;
1656 CBC 3382 : double startup_ms = 1000.0 * planstate->instrument->startup / nloops;
1657 GIC 3382 : double total_ms = 1000.0 * planstate->instrument->total / nloops;
1658 CBC 3382 : double rows = planstate->instrument->ntuples / nloops;
1659 :
1660 GIC 3382 : if (es->format == EXPLAIN_FORMAT_TEXT)
1661 : {
1662 2870 : if (es->timing)
1663 1666 : appendStringInfo(es->str,
1664 ECB : " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
1665 : startup_ms, total_ms, rows, nloops);
1666 : else
1667 GIC 1204 : appendStringInfo(es->str,
1668 ECB : " (actual rows=%.0f loops=%.0f)",
1669 : rows, nloops);
1670 : }
1671 : else
1672 : {
1673 GIC 512 : if (es->timing)
1674 : {
1675 464 : ExplainPropertyFloat("Actual Startup Time", "ms", startup_ms,
1676 : 3, es);
1677 464 : ExplainPropertyFloat("Actual Total Time", "ms", total_ms,
1678 : 3, es);
1679 : }
1680 512 : ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);
1681 512 : ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);
1682 : }
1683 : }
1684 30709 : else if (es->analyze)
1685 ECB : {
1686 CBC 371 : if (es->format == EXPLAIN_FORMAT_TEXT)
1687 GIC 371 : appendStringInfoString(es->str, " (never executed)");
1688 ECB : else
1689 : {
1690 LBC 0 : if (es->timing)
1691 ECB : {
1692 LBC 0 : ExplainPropertyFloat("Actual Startup Time", "ms", 0.0, 3, es);
1693 0 : ExplainPropertyFloat("Actual Total Time", "ms", 0.0, 3, es);
1694 ECB : }
1695 UIC 0 : ExplainPropertyFloat("Actual Rows", NULL, 0.0, 0, es);
1696 LBC 0 : ExplainPropertyFloat("Actual Loops", NULL, 0.0, 0, es);
1697 : }
1698 ECB : }
1699 :
1700 : /* in text format, first line ends here */
1701 GIC 34091 : if (es->format == EXPLAIN_FORMAT_TEXT)
1702 33567 : appendStringInfoChar(es->str, '\n');
1703 ECB :
1704 : /* prepare per-worker general execution details */
1705 GIC 34091 : if (es->workers_state && es->verbose)
1706 : {
1707 6 : WorkerInstrumentation *w = planstate->worker_instrument;
1708 :
1709 CBC 30 : for (int n = 0; n < w->num_workers; n++)
1710 : {
1711 24 : Instrumentation *instrument = &w->instrument[n];
1712 GIC 24 : double nloops = instrument->nloops;
1713 ECB : double startup_ms;
1714 : double total_ms;
1715 : double rows;
1716 :
1717 CBC 24 : if (nloops <= 0)
1718 UIC 0 : continue;
1719 GIC 24 : startup_ms = 1000.0 * instrument->startup / nloops;
1720 CBC 24 : total_ms = 1000.0 * instrument->total / nloops;
1721 GIC 24 : rows = instrument->ntuples / nloops;
1722 ECB :
1723 CBC 24 : ExplainOpenWorker(n, es);
1724 :
1725 GIC 24 : if (es->format == EXPLAIN_FORMAT_TEXT)
1726 EUB : {
1727 UIC 0 : ExplainIndentText(es);
1728 UBC 0 : if (es->timing)
1729 0 : appendStringInfo(es->str,
1730 : "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",
1731 EUB : startup_ms, total_ms, rows, nloops);
1732 : else
1733 UIC 0 : appendStringInfo(es->str,
1734 : "actual rows=%.0f loops=%.0f\n",
1735 : rows, nloops);
1736 : }
1737 ECB : else
1738 : {
1739 GIC 24 : if (es->timing)
1740 : {
1741 CBC 24 : ExplainPropertyFloat("Actual Startup Time", "ms",
1742 : startup_ms, 3, es);
1743 24 : ExplainPropertyFloat("Actual Total Time", "ms",
1744 : total_ms, 3, es);
1745 ECB : }
1746 GIC 24 : ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);
1747 CBC 24 : ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);
1748 ECB : }
1749 :
1750 GIC 24 : ExplainCloseWorker(n, es);
1751 : }
1752 : }
1753 ECB :
1754 EUB : /* target list */
1755 CBC 34091 : if (es->verbose)
1756 3313 : show_plan_tlist(planstate, ancestors, es);
1757 ECB :
1758 : /* unique join */
1759 CBC 34091 : switch (nodeTag(plan))
1760 : {
1761 2804 : case T_NestLoop:
1762 : case T_MergeJoin:
1763 EUB : case T_HashJoin:
1764 : /* try not to be too chatty about this in text mode */
1765 GBC 2804 : if (es->format != EXPLAIN_FORMAT_TEXT ||
1766 GIC 2738 : (es->verbose && ((Join *) plan)->inner_unique))
1767 113 : ExplainPropertyBool("Inner Unique",
1768 113 : ((Join *) plan)->inner_unique,
1769 EUB : es);
1770 GIC 2804 : break;
1771 31287 : default:
1772 31287 : break;
1773 : }
1774 :
1775 ECB : /* quals, sort keys, etc */
1776 GIC 34091 : switch (nodeTag(plan))
1777 ECB : {
1778 GIC 1493 : case T_IndexScan:
1779 CBC 1493 : show_scan_qual(((IndexScan *) plan)->indexqualorig,
1780 : "Index Cond", planstate, ancestors, es);
1781 GIC 1493 : if (((IndexScan *) plan)->indexqualorig)
1782 CBC 1080 : show_instrumentation_count("Rows Removed by Index Recheck", 2,
1783 ECB : planstate, es);
1784 GIC 1493 : show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
1785 : "Order By", planstate, ancestors, es);
1786 CBC 1493 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1787 GIC 1493 : if (plan->qual)
1788 210 : show_instrumentation_count("Rows Removed by Filter", 1,
1789 : planstate, es);
1790 1493 : break;
1791 CBC 1064 : case T_IndexOnlyScan:
1792 1064 : show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
1793 : "Index Cond", planstate, ancestors, es);
1794 GIC 1064 : if (((IndexOnlyScan *) plan)->recheckqual)
1795 CBC 685 : show_instrumentation_count("Rows Removed by Index Recheck", 2,
1796 : planstate, es);
1797 1064 : show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
1798 : "Order By", planstate, ancestors, es);
1799 GIC 1064 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1800 1064 : if (plan->qual)
1801 CBC 57 : show_instrumentation_count("Rows Removed by Filter", 1,
1802 ECB : planstate, es);
1803 CBC 1064 : if (es->analyze)
1804 62 : ExplainPropertyFloat("Heap Fetches", NULL,
1805 GIC 62 : planstate->instrument->ntuples2, 0, es);
1806 CBC 1064 : break;
1807 1959 : case T_BitmapIndexScan:
1808 1959 : show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
1809 : "Index Cond", planstate, ancestors, es);
1810 GIC 1959 : break;
1811 1875 : case T_BitmapHeapScan:
1812 CBC 1875 : show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
1813 : "Recheck Cond", planstate, ancestors, es);
1814 1875 : if (((BitmapHeapScan *) plan)->bitmapqualorig)
1815 1845 : show_instrumentation_count("Rows Removed by Index Recheck", 2,
1816 : planstate, es);
1817 1875 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1818 1875 : if (plan->qual)
1819 GIC 156 : show_instrumentation_count("Rows Removed by Filter", 1,
1820 ECB : planstate, es);
1821 GIC 1875 : if (es->analyze)
1822 CBC 222 : show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
1823 1875 : break;
1824 33 : case T_SampleScan:
1825 GIC 33 : show_tablesample(((SampleScan *) plan)->tablesample,
1826 ECB : planstate, ancestors, es);
1827 : /* fall through to print additional fields the same as SeqScan */
1828 : /* FALLTHROUGH */
1829 GIC 11369 : case T_SeqScan:
1830 ECB : case T_ValuesScan:
1831 : case T_CteScan:
1832 : case T_NamedTuplestoreScan:
1833 : case T_WorkTableScan:
1834 : case T_SubqueryScan:
1835 CBC 11369 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1836 11369 : if (plan->qual)
1837 6283 : show_instrumentation_count("Rows Removed by Filter", 1,
1838 : planstate, es);
1839 11369 : break;
1840 297 : case T_Gather:
1841 ECB : {
1842 CBC 297 : Gather *gather = (Gather *) plan;
1843 ECB :
1844 CBC 297 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1845 GIC 297 : if (plan->qual)
1846 LBC 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1847 ECB : planstate, es);
1848 CBC 297 : ExplainPropertyInteger("Workers Planned", NULL,
1849 GIC 297 : gather->num_workers, es);
1850 ECB :
1851 : /* Show params evaluated at gather node */
1852 GIC 297 : if (gather->initParam)
1853 CBC 21 : show_eval_params(gather->initParam, es);
1854 ECB :
1855 CBC 297 : if (es->analyze)
1856 : {
1857 ECB : int nworkers;
1858 :
1859 CBC 87 : nworkers = ((GatherState *) planstate)->nworkers_launched;
1860 87 : ExplainPropertyInteger("Workers Launched", NULL,
1861 ECB : nworkers, es);
1862 : }
1863 :
1864 GIC 297 : if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)
1865 CBC 45 : ExplainPropertyBool("Single Copy", gather->single_copy, es);
1866 : }
1867 GIC 297 : break;
1868 84 : case T_GatherMerge:
1869 : {
1870 84 : GatherMerge *gm = (GatherMerge *) plan;
1871 ECB :
1872 CBC 84 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1873 84 : if (plan->qual)
1874 UIC 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1875 ECB : planstate, es);
1876 CBC 84 : ExplainPropertyInteger("Workers Planned", NULL,
1877 GIC 84 : gm->num_workers, es);
1878 ECB :
1879 : /* Show params evaluated at gather-merge node */
1880 CBC 84 : if (gm->initParam)
1881 LBC 0 : show_eval_params(gm->initParam, es);
1882 EUB :
1883 GIC 84 : if (es->analyze)
1884 ECB : {
1885 : int nworkers;
1886 :
1887 GIC 6 : nworkers = ((GatherMergeState *) planstate)->nworkers_launched;
1888 CBC 6 : ExplainPropertyInteger("Workers Launched", NULL,
1889 ECB : nworkers, es);
1890 : }
1891 : }
1892 GIC 84 : break;
1893 186 : case T_FunctionScan:
1894 186 : if (es->verbose)
1895 ECB : {
1896 CBC 70 : List *fexprs = NIL;
1897 : ListCell *lc;
1898 :
1899 GIC 140 : foreach(lc, ((FunctionScan *) plan)->functions)
1900 ECB : {
1901 CBC 70 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
1902 :
1903 70 : fexprs = lappend(fexprs, rtfunc->funcexpr);
1904 ECB : }
1905 : /* We rely on show_expression to insert commas as needed */
1906 CBC 70 : show_expression((Node *) fexprs,
1907 : "Function Call", planstate, ancestors,
1908 70 : es->verbose, es);
1909 ECB : }
1910 GBC 186 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1911 GIC 186 : if (plan->qual)
1912 CBC 11 : show_instrumentation_count("Rows Removed by Filter", 1,
1913 ECB : planstate, es);
1914 GIC 186 : break;
1915 15 : case T_TableFuncScan:
1916 CBC 15 : if (es->verbose)
1917 EUB : {
1918 GIC 12 : TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;
1919 ECB :
1920 GIC 12 : show_expression((Node *) tablefunc,
1921 : "Table Function Call", planstate, ancestors,
1922 12 : es->verbose, es);
1923 ECB : }
1924 CBC 15 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1925 GIC 15 : if (plan->qual)
1926 6 : show_instrumentation_count("Rows Removed by Filter", 1,
1927 : planstate, es);
1928 CBC 15 : break;
1929 30 : case T_TidScan:
1930 ECB : {
1931 : /*
1932 : * The tidquals list has OR semantics, so be sure to show it
1933 : * as an OR condition.
1934 : */
1935 CBC 30 : List *tidquals = ((TidScan *) plan)->tidquals;
1936 :
1937 30 : if (list_length(tidquals) > 1)
1938 GIC 6 : tidquals = list_make1(make_orclause(tidquals));
1939 CBC 30 : show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
1940 GIC 30 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1941 30 : if (plan->qual)
1942 CBC 6 : show_instrumentation_count("Rows Removed by Filter", 1,
1943 : planstate, es);
1944 ECB : }
1945 GIC 30 : break;
1946 CBC 42 : case T_TidRangeScan:
1947 ECB : {
1948 : /*
1949 : * The tidrangequals list has AND semantics, so be sure to
1950 : * show it as an AND condition.
1951 : */
1952 CBC 42 : List *tidquals = ((TidRangeScan *) plan)->tidrangequals;
1953 :
1954 42 : if (list_length(tidquals) > 1)
1955 GIC 6 : tidquals = list_make1(make_andclause(tidquals));
1956 CBC 42 : show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
1957 GIC 42 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1958 CBC 42 : if (plan->qual)
1959 UIC 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1960 ECB : planstate, es);
1961 : }
1962 CBC 42 : break;
1963 GIC 391 : case T_ForeignScan:
1964 CBC 391 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1965 391 : if (plan->qual)
1966 GIC 52 : show_instrumentation_count("Rows Removed by Filter", 1,
1967 : planstate, es);
1968 391 : show_foreignscan_info((ForeignScanState *) planstate, es);
1969 391 : break;
1970 UIC 0 : case T_CustomScan:
1971 ECB : {
1972 UIC 0 : CustomScanState *css = (CustomScanState *) planstate;
1973 ECB :
1974 LBC 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 ECB : planstate, es);
1978 LBC 0 : if (css->methods->ExplainCustomScan)
1979 UIC 0 : css->methods->ExplainCustomScan(css, ancestors, es);
1980 : }
1981 LBC 0 : break;
1982 CBC 1055 : case T_NestLoop:
1983 GIC 1055 : show_upper_qual(((NestLoop *) plan)->join.joinqual,
1984 : "Join Filter", planstate, ancestors, es);
1985 1055 : if (((NestLoop *) plan)->join.joinqual)
1986 298 : show_instrumentation_count("Rows Removed by Join Filter", 1,
1987 : planstate, es);
1988 CBC 1055 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1989 GIC 1055 : if (plan->qual)
1990 CBC 30 : show_instrumentation_count("Rows Removed by Filter", 2,
1991 ECB : planstate, es);
1992 CBC 1055 : break;
1993 286 : case T_MergeJoin:
1994 286 : show_upper_qual(((MergeJoin *) plan)->mergeclauses,
1995 EUB : "Merge Cond", planstate, ancestors, es);
1996 GIC 286 : show_upper_qual(((MergeJoin *) plan)->join.joinqual,
1997 : "Join Filter", planstate, ancestors, es);
1998 CBC 286 : if (((MergeJoin *) plan)->join.joinqual)
1999 13 : show_instrumentation_count("Rows Removed by Join Filter", 1,
2000 ECB : planstate, es);
2001 CBC 286 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
2002 286 : if (plan->qual)
2003 GIC 12 : show_instrumentation_count("Rows Removed by Filter", 2,
2004 ECB : planstate, es);
2005 CBC 286 : break;
2006 GBC 1463 : case T_HashJoin:
2007 GIC 1463 : show_upper_qual(((HashJoin *) plan)->hashclauses,
2008 EUB : "Hash Cond", planstate, ancestors, es);
2009 GIC 1463 : show_upper_qual(((HashJoin *) plan)->join.joinqual,
2010 EUB : "Join Filter", planstate, ancestors, es);
2011 GBC 1463 : if (((HashJoin *) plan)->join.joinqual)
2012 9 : show_instrumentation_count("Rows Removed by Join Filter", 1,
2013 : planstate, es);
2014 1463 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
2015 1463 : if (plan->qual)
2016 GIC 128 : show_instrumentation_count("Rows Removed by Filter", 2,
2017 EUB : planstate, es);
2018 CBC 1463 : break;
2019 4512 : case T_Agg:
2020 GIC 4512 : show_agg_keys(castNode(AggState, planstate), ancestors, es);
2021 CBC 4512 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
2022 4512 : show_hashagg_info((AggState *) planstate, es);
2023 GIC 4512 : if (plan->qual)
2024 CBC 157 : show_instrumentation_count("Rows Removed by Filter", 1,
2025 ECB : planstate, es);
2026 CBC 4512 : break;
2027 GIC 174 : case T_WindowAgg:
2028 CBC 174 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
2029 174 : if (plan->qual)
2030 3 : show_instrumentation_count("Rows Removed by Filter", 1,
2031 : planstate, es);
2032 174 : show_upper_qual(((WindowAgg *) plan)->runConditionOrig,
2033 : "Run Condition", planstate, ancestors, es);
2034 174 : break;
2035 39 : case T_Group:
2036 GIC 39 : show_group_keys(castNode(GroupState, planstate), ancestors, es);
2037 CBC 39 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
2038 39 : if (plan->qual)
2039 LBC 0 : show_instrumentation_count("Rows Removed by Filter", 1,
2040 : planstate, es);
2041 CBC 39 : break;
2042 1776 : case T_Sort:
2043 1776 : show_sort_keys(castNode(SortState, planstate), ancestors, es);
2044 GIC 1776 : show_sort_info(castNode(SortState, planstate), es);
2045 CBC 1776 : break;
2046 GIC 130 : case T_IncrementalSort:
2047 CBC 130 : show_incremental_sort_keys(castNode(IncrementalSortState, planstate),
2048 ECB : ancestors, es);
2049 GIC 130 : show_incremental_sort_info(castNode(IncrementalSortState, planstate),
2050 ECB : es);
2051 CBC 130 : break;
2052 131 : case T_MergeAppend:
2053 GIC 131 : show_merge_append_keys(castNode(MergeAppendState, planstate),
2054 ECB : ancestors, es);
2055 CBC 131 : break;
2056 1026 : case T_Result:
2057 1026 : show_upper_qual((List *) ((Result *) plan)->resconstantqual,
2058 ECB : "One-Time Filter", planstate, ancestors, es);
2059 CBC 1026 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
2060 1026 : if (plan->qual)
2061 UIC 0 : show_instrumentation_count("Rows Removed by Filter", 1,
2062 ECB : planstate, es);
2063 CBC 1026 : break;
2064 400 : case T_ModifyTable:
2065 400 : show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,
2066 ECB : es);
2067 GIC 400 : break;
2068 CBC 1463 : case T_Hash:
2069 GIC 1463 : show_hash_info(castNode(HashState, planstate), es);
2070 CBC 1463 : break;
2071 68 : case T_Memoize:
2072 68 : show_memoize_info(castNode(MemoizeState, planstate), ancestors,
2073 ECB : es);
2074 CBC 68 : break;
2075 GBC 2763 : default:
2076 GIC 2763 : break;
2077 ECB : }
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 CBC 34091 : if (es->workers_state && es->costs && es->verbose)
2084 : {
2085 6 : SharedJitInstrumentation *w = planstate->worker_jit_instrument;
2086 :
2087 6 : if (w)
2088 ECB : {
2089 LBC 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 ECB : &w->jit_instr[n]);
2094 UIC 0 : ExplainCloseWorker(n, es);
2095 ECB : }
2096 : }
2097 EUB : }
2098 :
2099 ECB : /* Show buffer/WAL usage */
2100 CBC 34091 : if (es->buffers && planstate->instrument)
2101 47 : show_buffer_usage(es, &planstate->instrument->bufusage, false);
2102 GIC 34091 : if (es->wal && planstate->instrument)
2103 LBC 0 : show_wal_usage(es, &planstate->instrument->walusage);
2104 ECB :
2105 : /* Prepare per-worker buffer/WAL usage */
2106 CBC 34091 : if (es->workers_state && (es->buffers || es->wal) && es->verbose)
2107 ECB : {
2108 CBC 6 : WorkerInstrumentation *w = planstate->worker_instrument;
2109 :
2110 30 : for (int n = 0; n < w->num_workers; n++)
2111 ECB : {
2112 CBC 24 : Instrumentation *instrument = &w->instrument[n];
2113 GIC 24 : double nloops = instrument->nloops;
2114 :
2115 24 : if (nloops <= 0)
2116 UIC 0 : continue;
2117 :
2118 GIC 24 : ExplainOpenWorker(n, es);
2119 CBC 24 : if (es->buffers)
2120 GIC 24 : show_buffer_usage(es, &instrument->bufusage, false);
2121 CBC 24 : if (es->wal)
2122 UIC 0 : show_wal_usage(es, &instrument->walusage);
2123 CBC 24 : ExplainCloseWorker(n, es);
2124 : }
2125 EUB : }
2126 :
2127 : /* Show per-worker details for this plan node, then pop that stack */
2128 GBC 34091 : if (es->workers_state)
2129 GIC 507 : ExplainFlushWorkersState(es);
2130 GBC 34091 : 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 ECB : * 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 EUB : */
2140 GIC 34091 : switch (nodeTag(plan))
2141 : {
2142 CBC 1587 : case T_Append:
2143 GIC 1587 : ExplainMissingMembers(((AppendState *) planstate)->as_nplans,
2144 CBC 1587 : list_length(((Append *) plan)->appendplans),
2145 : es);
2146 1587 : break;
2147 GIC 131 : case T_MergeAppend:
2148 CBC 131 : ExplainMissingMembers(((MergeAppendState *) planstate)->ms_nplans,
2149 131 : list_length(((MergeAppend *) plan)->mergeplans),
2150 : es);
2151 131 : break;
2152 GBC 32373 : default:
2153 GIC 32373 : break;
2154 ECB : }
2155 :
2156 : /* Get ready to display the child plans */
2157 CBC 101822 : haschildren = planstate->initPlan ||
2158 GBC 33640 : outerPlanState(planstate) ||
2159 CBC 18962 : innerPlanState(planstate) ||
2160 GIC 18962 : IsA(plan, Append) ||
2161 17426 : IsA(plan, MergeAppend) ||
2162 17298 : IsA(plan, BitmapAnd) ||
2163 17289 : IsA(plan, BitmapOr) ||
2164 CBC 17232 : IsA(plan, SubqueryScan) ||
2165 16972 : (IsA(planstate, CustomScanState) &&
2166 67731 : ((CustomScanState *) planstate)->custom_ps != NIL) ||
2167 GIC 16972 : planstate->subPlan;
2168 34091 : if (haschildren)
2169 : {
2170 17273 : ExplainOpenGroup("Plans", "Plans", false, es);
2171 : /* Pass current Plan as head of ancestors list for children */
2172 17273 : ancestors = lcons(plan, ancestors);
2173 : }
2174 :
2175 : /* initPlan-s */
2176 CBC 34091 : if (planstate->initPlan)
2177 GIC 451 : ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
2178 ECB :
2179 : /* lefttree */
2180 CBC 34091 : if (outerPlanState(planstate))
2181 GIC 14843 : ExplainNode(outerPlanState(planstate), ancestors,
2182 ECB : "Outer", NULL, es);
2183 :
2184 : /* righttree */
2185 CBC 34091 : if (innerPlanState(planstate))
2186 GIC 2831 : ExplainNode(innerPlanState(planstate), ancestors,
2187 ECB : "Inner", NULL, es);
2188 :
2189 : /* special child plans */
2190 GIC 34091 : switch (nodeTag(plan))
2191 : {
2192 1587 : case T_Append:
2193 CBC 1587 : ExplainMemberNodes(((AppendState *) planstate)->appendplans,
2194 ECB : ((AppendState *) planstate)->as_nplans,
2195 : ancestors, es);
2196 CBC 1587 : break;
2197 131 : case T_MergeAppend:
2198 131 : ExplainMemberNodes(((MergeAppendState *) planstate)->mergeplans,
2199 ECB : ((MergeAppendState *) planstate)->ms_nplans,
2200 : ancestors, es);
2201 CBC 131 : break;
2202 9 : case T_BitmapAnd:
2203 9 : ExplainMemberNodes(((BitmapAndState *) planstate)->bitmapplans,
2204 ECB : ((BitmapAndState *) planstate)->nplans,
2205 : ancestors, es);
2206 CBC 9 : break;
2207 GIC 57 : case T_BitmapOr:
2208 CBC 57 : ExplainMemberNodes(((BitmapOrState *) planstate)->bitmapplans,
2209 : ((BitmapOrState *) planstate)->nplans,
2210 : ancestors, es);
2211 GIC 57 : break;
2212 CBC 260 : case T_SubqueryScan:
2213 260 : ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
2214 : "Subquery", NULL, es);
2215 GIC 260 : break;
2216 LBC 0 : case T_CustomScan:
2217 0 : ExplainCustomChildren((CustomScanState *) planstate,
2218 : ancestors, es);
2219 UIC 0 : break;
2220 GIC 32047 : default:
2221 CBC 32047 : break;
2222 ECB : }
2223 :
2224 : /* subPlan-s */
2225 GIC 34091 : if (planstate->subPlan)
2226 CBC 218 : ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
2227 :
2228 ECB : /* end of child plans */
2229 CBC 34091 : if (haschildren)
2230 : {
2231 GIC 17273 : ancestors = list_delete_first(ancestors);
2232 CBC 17273 : ExplainCloseGroup("Plans", "Plans", false, es);
2233 ECB : }
2234 :
2235 : /* in text format, undo whatever indentation we added */
2236 GIC 34091 : if (es->format == EXPLAIN_FORMAT_TEXT)
2237 CBC 33567 : es->indent = save_indent;
2238 ECB :
2239 CBC 34091 : ExplainCloseGroup("Plan",
2240 : relationship ? NULL : "Plan",
2241 : true, es);
2242 34091 : }
2243 ECB :
2244 : /*
2245 : * Show the targetlist of a plan node
2246 : */
2247 : static void
2248 CBC 3313 : show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
2249 ECB : {
2250 GIC 3313 : Plan *plan = planstate->plan;
2251 ECB : List *context;
2252 GBC 3313 : List *result = NIL;
2253 EUB : bool useprefix;
2254 : ListCell *lc;
2255 :
2256 ECB : /* No work if empty tlist (this occurs eg in bitmap indexscans) */
2257 CBC 3313 : if (plan->targetlist == NIL)
2258 GIC 216 : return;
2259 : /* The tlist of an Append isn't real helpful, so suppress it */
2260 3097 : if (IsA(plan, Append))
2261 CBC 103 : return;
2262 ECB : /* Likewise for MergeAppend and RecursiveUnion */
2263 GIC 2994 : if (IsA(plan, MergeAppend))
2264 11 : return;
2265 CBC 2983 : if (IsA(plan, RecursiveUnion))
2266 GIC 24 : return;
2267 ECB :
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 CBC 2959 : if (IsA(plan, ForeignScan) &&
2279 GIC 351 : ((ForeignScan *) plan)->operation != CMD_SELECT)
2280 32 : return;
2281 :
2282 : /* Set up deparsing context */
2283 2927 : context = set_deparse_context_plan(es->deparse_cxt,
2284 ECB : plan,
2285 : ancestors);
2286 CBC 2927 : useprefix = list_length(es->rtable) > 1;
2287 :
2288 ECB : /* Deparse each result column (we now include resjunk ones) */
2289 GIC 10523 : foreach(lc, plan->targetlist)
2290 : {
2291 7596 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
2292 :
2293 CBC 7596 : result = lappend(result,
2294 7596 : deparse_expression((Node *) tle->expr, context,
2295 : useprefix, false));
2296 ECB : }
2297 :
2298 : /* Print results */
2299 CBC 2927 : ExplainPropertyList("Output", result, es);
2300 ECB : }
2301 :
2302 : /*
2303 : * Show a generic expression
2304 : */
2305 : static void
2306 GIC 15299 : 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 CBC 15299 : context = set_deparse_context_plan(es->deparse_cxt,
2315 15299 : planstate->plan,
2316 ECB : ancestors);
2317 :
2318 : /* Deparse the expression */
2319 CBC 15299 : exprstr = deparse_expression(node, context, useprefix, false);
2320 :
2321 : /* And add to es->str */
2322 15299 : ExplainPropertyText(qlabel, exprstr, es);
2323 GIC 15299 : }
2324 :
2325 ECB : /*
2326 : * Show a qualifier expression (which is a List with implicit AND semantics)
2327 : */
2328 : static void
2329 CBC 40201 : show_qual(List *qual, const char *qlabel,
2330 ECB : PlanState *planstate, List *ancestors,
2331 : bool useprefix, ExplainState *es)
2332 : {
2333 : Node *node;
2334 :
2335 : /* No work if empty qual */
2336 GIC 40201 : if (qual == NIL)
2337 24984 : return;
2338 :
2339 : /* Convert AND list to explicit AND */
2340 15217 : node = (Node *) make_ands_explicit(qual);
2341 :
2342 ECB : /* And show it */
2343 GIC 15217 : 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 CBC 25866 : show_scan_qual(List *qual, const char *qlabel,
2351 ECB : PlanState *planstate, List *ancestors,
2352 : ExplainState *es)
2353 : {
2354 : bool useprefix;
2355 :
2356 GIC 25866 : useprefix = (IsA(planstate->plan, SubqueryScan) || es->verbose);
2357 25866 : show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
2358 CBC 25866 : }
2359 ECB :
2360 : /*
2361 : * Show a qualifier expression for an upper-level plan node
2362 : */
2363 : static void
2364 GIC 14335 : show_upper_qual(List *qual, const char *qlabel,
2365 ECB : PlanState *planstate, List *ancestors,
2366 : ExplainState *es)
2367 : {
2368 : bool useprefix;
2369 :
2370 GIC 14335 : useprefix = (list_length(es->rtable) > 1 || es->verbose);
2371 14335 : show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
2372 CBC 14335 : }
2373 ECB :
2374 : /*
2375 : * Show the sort keys for a Sort node.
2376 : */
2377 : static void
2378 GIC 1776 : show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
2379 ECB : {
2380 GIC 1776 : Sort *plan = (Sort *) sortstate->ss.ps.plan;
2381 :
2382 1776 : show_sort_group_keys((PlanState *) sortstate, "Sort Key",
2383 : plan->numCols, 0, plan->sortColIdx,
2384 : plan->sortOperators, plan->collations,
2385 : plan->nullsFirst,
2386 ECB : ancestors, es);
2387 GIC 1776 : }
2388 :
2389 : /*
2390 : * Show the sort keys for a IncrementalSort node.
2391 : */
2392 ECB : static void
2393 CBC 130 : show_incremental_sort_keys(IncrementalSortState *incrsortstate,
2394 ECB : List *ancestors, ExplainState *es)
2395 : {
2396 GIC 130 : IncrementalSort *plan = (IncrementalSort *) incrsortstate->ss.ps.plan;
2397 :
2398 130 : show_sort_group_keys((PlanState *) incrsortstate, "Sort Key",
2399 : plan->sort.numCols, plan->nPresortedCols,
2400 ECB : plan->sort.sortColIdx,
2401 : plan->sort.sortOperators, plan->sort.collations,
2402 : plan->sort.nullsFirst,
2403 : ancestors, es);
2404 GIC 130 : }
2405 :
2406 ECB : /*
2407 : * Likewise, for a MergeAppend node.
2408 : */
2409 : static void
2410 GIC 131 : show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
2411 : ExplainState *es)
2412 : {
2413 131 : MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
2414 ECB :
2415 GIC 131 : show_sort_group_keys((PlanState *) mstate, "Sort Key",
2416 ECB : plan->numCols, 0, plan->sortColIdx,
2417 : plan->sortOperators, plan->collations,
2418 : plan->nullsFirst,
2419 : ancestors, es);
2420 GIC 131 : }
2421 :
2422 : /*
2423 ECB : * Show the grouping keys for an Agg node.
2424 : */
2425 : static void
2426 GIC 4512 : show_agg_keys(AggState *astate, List *ancestors,
2427 : ExplainState *es)
2428 : {
2429 CBC 4512 : Agg *plan = (Agg *) astate->ss.ps.plan;
2430 :
2431 GIC 4512 : if (plan->numCols > 0 || plan->groupingSets)
2432 ECB : {
2433 : /* The key columns refer to the tlist of the child plan */
2434 CBC 1009 : ancestors = lcons(plan, ancestors);
2435 :
2436 GIC 1009 : if (plan->groupingSets)
2437 84 : show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
2438 : else
2439 925 : show_sort_group_keys(outerPlanState(astate), "Group Key",
2440 ECB : plan->numCols, 0, plan->grpColIdx,
2441 : NULL, NULL, NULL,
2442 : ancestors, es);
2443 :
2444 GIC 1009 : ancestors = list_delete_first(ancestors);
2445 : }
2446 CBC 4512 : }
2447 :
2448 : static void
2449 84 : show_grouping_sets(PlanState *planstate, Agg *agg,
2450 : List *ancestors, ExplainState *es)
2451 ECB : {
2452 : List *context;
2453 : bool useprefix;
2454 : ListCell *lc;
2455 :
2456 : /* Set up deparsing context */
2457 GIC 84 : context = set_deparse_context_plan(es->deparse_cxt,
2458 84 : planstate->plan,
2459 : ancestors);
2460 84 : useprefix = (list_length(es->rtable) > 1 || es->verbose);
2461 :
2462 CBC 84 : ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
2463 :
2464 GIC 84 : show_grouping_set_keys(planstate, agg, NULL,
2465 ECB : context, useprefix, ancestors, es);
2466 :
2467 CBC 234 : foreach(lc, agg->chain)
2468 : {
2469 GIC 150 : Agg *aggnode = lfirst(lc);
2470 CBC 150 : Sort *sortnode = (Sort *) aggnode->plan.lefttree;
2471 :
2472 150 : show_grouping_set_keys(planstate, aggnode, sortnode,
2473 ECB : context, useprefix, ancestors, es);
2474 : }
2475 :
2476 GIC 84 : ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
2477 84 : }
2478 :
2479 : static void
2480 CBC 234 : show_grouping_set_keys(PlanState *planstate,
2481 : Agg *aggnode, Sort *sortnode,
2482 ECB : List *context, bool useprefix,
2483 : List *ancestors, ExplainState *es)
2484 : {
2485 CBC 234 : Plan *plan = planstate->plan;
2486 : char *exprstr;
2487 : ListCell *lc;
2488 GIC 234 : List *gsets = aggnode->groupingSets;
2489 234 : AttrNumber *keycols = aggnode->grpColIdx;
2490 : const char *keyname;
2491 : const char *keysetname;
2492 :
2493 CBC 234 : if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED)
2494 ECB : {
2495 GIC 139 : keyname = "Hash Key";
2496 CBC 139 : keysetname = "Hash Keys";
2497 : }
2498 ECB : else
2499 : {
2500 CBC 95 : keyname = "Group Key";
2501 GIC 95 : keysetname = "Group Keys";
2502 : }
2503 ECB :
2504 GIC 234 : ExplainOpenGroup("Grouping Set", NULL, true, es);
2505 ECB :
2506 CBC 234 : if (sortnode)
2507 : {
2508 27 : show_sort_group_keys(planstate, "Sort Key",
2509 : sortnode->numCols, 0, sortnode->sortColIdx,
2510 : sortnode->sortOperators, sortnode->collations,
2511 : sortnode->nullsFirst,
2512 ECB : ancestors, es);
2513 CBC 27 : if (es->format == EXPLAIN_FORMAT_TEXT)
2514 GIC 27 : es->indent++;
2515 : }
2516 ECB :
2517 GIC 234 : ExplainOpenGroup(keysetname, keysetname, false, es);
2518 :
2519 516 : foreach(lc, gsets)
2520 : {
2521 CBC 282 : List *result = NIL;
2522 : ListCell *lc2;
2523 :
2524 586 : foreach(lc2, (List *) lfirst(lc))
2525 ECB : {
2526 GIC 304 : Index i = lfirst_int(lc2);
2527 304 : AttrNumber keyresno = keycols[i];
2528 304 : TargetEntry *target = get_tle_by_resno(plan->targetlist,
2529 ECB : keyresno);
2530 :
2531 CBC 304 : if (!target)
2532 LBC 0 : elog(ERROR, "no tlist entry for key %d", keyresno);
2533 : /* Deparse the expression, showing any top-level cast */
2534 GIC 304 : exprstr = deparse_expression((Node *) target->expr, context,
2535 : useprefix, true);
2536 ECB :
2537 CBC 304 : result = lappend(result, exprstr);
2538 : }
2539 :
2540 282 : if (!result && es->format == EXPLAIN_FORMAT_TEXT)
2541 GIC 56 : ExplainPropertyText(keyname, "()", es);
2542 ECB : else
2543 GIC 226 : ExplainPropertyListNested(keyname, result, es);
2544 ECB : }
2545 :
2546 GIC 234 : ExplainCloseGroup(keysetname, keysetname, false, es);
2547 :
2548 234 : if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
2549 CBC 27 : es->indent--;
2550 ECB :
2551 GIC 234 : ExplainCloseGroup("Grouping Set", NULL, true, es);
2552 234 : }
2553 ECB :
2554 : /*
2555 : * Show the grouping keys for a Group node.
2556 : */
2557 : static void
2558 GIC 39 : show_group_keys(GroupState *gstate, List *ancestors,
2559 : ExplainState *es)
2560 ECB : {
2561 GIC 39 : Group *plan = (Group *) gstate->ss.ps.plan;
2562 ECB :
2563 : /* The key columns refer to the tlist of the child plan */
2564 CBC 39 : ancestors = lcons(plan, ancestors);
2565 GIC 39 : show_sort_group_keys(outerPlanState(gstate), "Group Key",
2566 : plan->numCols, 0, plan->grpColIdx,
2567 ECB : NULL, NULL, NULL,
2568 EUB : ancestors, es);
2569 GIC 39 : ancestors = list_delete_first(ancestors);
2570 CBC 39 : }
2571 :
2572 : /*
2573 ECB : * 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 GIC 3028 : show_sort_group_keys(PlanState *planstate, const char *qlabel,
2579 ECB : int nkeys, int nPresortedKeys, AttrNumber *keycols,
2580 : Oid *sortOperators, Oid *collations, bool *nullsFirst,
2581 : List *ancestors, ExplainState *es)
2582 : {
2583 GIC 3028 : Plan *plan = planstate->plan;
2584 ECB : List *context;
2585 CBC 3028 : List *result = NIL;
2586 GIC 3028 : List *resultPresorted = NIL;
2587 ECB : StringInfoData sortkeybuf;
2588 : bool useprefix;
2589 : int keyno;
2590 :
2591 GIC 3028 : if (nkeys <= 0)
2592 UIC 0 : return;
2593 :
2594 CBC 3028 : initStringInfo(&sortkeybuf);
2595 :
2596 : /* Set up deparsing context */
2597 3028 : context = set_deparse_context_plan(es->deparse_cxt,
2598 : plan,
2599 : ancestors);
2600 3028 : useprefix = (list_length(es->rtable) > 1 || es->verbose);
2601 ECB :
2602 GIC 7632 : for (keyno = 0; keyno < nkeys; keyno++)
2603 : {
2604 : /* find key expression in tlist */
2605 CBC 4604 : AttrNumber keyresno = keycols[keyno];
2606 4604 : TargetEntry *target = get_tle_by_resno(plan->targetlist,
2607 : keyresno);
2608 : char *exprstr;
2609 :
2610 GIC 4604 : if (!target)
2611 UIC 0 : elog(ERROR, "no tlist entry for key %d", keyresno);
2612 : /* Deparse the expression, showing any top-level cast */
2613 GIC 4604 : exprstr = deparse_expression((Node *) target->expr, context,
2614 ECB : useprefix, true);
2615 GIC 4604 : resetStringInfo(&sortkeybuf);
2616 4604 : appendStringInfoString(&sortkeybuf, exprstr);
2617 : /* Append sort order information, if relevant */
2618 4604 : if (sortOperators != NULL)
2619 CBC 2970 : show_sortorder_options(&sortkeybuf,
2620 GIC 2970 : (Node *) target->expr,
2621 CBC 2970 : sortOperators[keyno],
2622 2970 : collations[keyno],
2623 GIC 2970 : nullsFirst[keyno]);
2624 : /* Emit one property-list item per sort key */
2625 4604 : result = lappend(result, pstrdup(sortkeybuf.data));
2626 4604 : if (keyno < nPresortedKeys)
2627 CBC 139 : resultPresorted = lappend(resultPresorted, exprstr);
2628 EUB : }
2629 :
2630 CBC 3028 : ExplainPropertyList(qlabel, result, es);
2631 GIC 3028 : if (nPresortedKeys > 0)
2632 130 : ExplainPropertyList("Presorted Key", resultPresorted, es);
2633 ECB : }
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 GIC 2970 : show_sortorder_options(StringInfo buf, Node *sortexpr,
2641 ECB : Oid sortOperator, Oid collation, bool nullsFirst)
2642 : {
2643 GIC 2970 : Oid sortcoltype = exprType(sortexpr);
2644 2970 : bool reverse = false;
2645 : TypeCacheEntry *typentry;
2646 ECB :
2647 GBC 2970 : typentry = lookup_type_cache(sortcoltype,
2648 : TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
2649 ECB :
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 CBC 2970 : if (OidIsValid(collation) && collation != get_typcollation(sortcoltype))
2658 ECB : {
2659 CBC 25 : char *collname = get_collation_name(collation);
2660 :
2661 25 : if (collname == NULL)
2662 LBC 0 : elog(ERROR, "cache lookup failed for collation %u", collation);
2663 CBC 25 : appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
2664 : }
2665 :
2666 ECB : /* Print direction if not ASC, or USING if non-default sort operator */
2667 CBC 2970 : if (sortOperator == typentry->gt_opr)
2668 ECB : {
2669 GIC 119 : appendStringInfoString(buf, " DESC");
2670 119 : reverse = true;
2671 : }
2672 2851 : else if (sortOperator != typentry->lt_opr)
2673 : {
2674 14 : char *opname = get_opname(sortOperator);
2675 :
2676 CBC 14 : if (opname == NULL)
2677 UIC 0 : elog(ERROR, "cache lookup failed for operator %u", sortOperator);
2678 GIC 14 : appendStringInfo(buf, " USING %s", opname);
2679 ECB : /* Determine whether operator would be considered ASC or DESC */
2680 CBC 14 : (void) get_equality_op_for_ordering_op(sortOperator, &reverse);
2681 : }
2682 :
2683 ECB : /* Add NULLS FIRST/LAST only if it wouldn't be default */
2684 GIC 2970 : if (nullsFirst && !reverse)
2685 : {
2686 3 : appendStringInfoString(buf, " NULLS FIRST");
2687 : }
2688 2967 : else if (!nullsFirst && reverse)
2689 : {
2690 UIC 0 : appendStringInfoString(buf, " NULLS LAST");
2691 : }
2692 GIC 2970 : }
2693 ECB :
2694 : /*
2695 : * Show TABLESAMPLE properties
2696 : */
2697 : static void
2698 GBC 33 : show_tablesample(TableSampleClause *tsc, PlanState *planstate,
2699 ECB : List *ancestors, ExplainState *es)
2700 : {
2701 : List *context;
2702 : bool useprefix;
2703 : char *method_name;
2704 GIC 33 : List *params = NIL;
2705 ECB : char *repeatable;
2706 : ListCell *lc;
2707 :
2708 : /* Set up deparsing context */
2709 GIC 33 : context = set_deparse_context_plan(es->deparse_cxt,
2710 CBC 33 : planstate->plan,
2711 : ancestors);
2712 33 : useprefix = list_length(es->rtable) > 1;
2713 EUB :
2714 ECB : /* Get the tablesample method name */
2715 GIC 33 : method_name = get_func_name(tsc->tsmhandler);
2716 ECB :
2717 : /* Deparse parameter expressions */
2718 GIC 66 : foreach(lc, tsc->args)
2719 : {
2720 CBC 33 : Node *arg = (Node *) lfirst(lc);
2721 :
2722 33 : params = lappend(params,
2723 GIC 33 : deparse_expression(arg, context,
2724 ECB : useprefix, false));
2725 : }
2726 GBC 33 : if (tsc->repeatable)
2727 GIC 6 : repeatable = deparse_expression((Node *) tsc->repeatable, context,
2728 ECB : useprefix, false);
2729 : else
2730 GIC 27 : repeatable = NULL;
2731 :
2732 : /* Print results */
2733 33 : if (es->format == EXPLAIN_FORMAT_TEXT)
2734 ECB : {
2735 GIC 33 : bool first = true;
2736 :
2737 33 : ExplainIndentText(es);
2738 33 : appendStringInfo(es->str, "Sampling: %s (", method_name);
2739 66 : foreach(lc, params)
2740 ECB : {
2741 GIC 33 : if (!first)
2742 UIC 0 : appendStringInfoString(es->str, ", ");
2743 GIC 33 : appendStringInfoString(es->str, (const char *) lfirst(lc));
2744 33 : first = false;
2745 ECB : }
2746 CBC 33 : appendStringInfoChar(es->str, ')');
2747 GIC 33 : if (repeatable)
2748 CBC 6 : appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);
2749 GIC 33 : appendStringInfoChar(es->str, '\n');
2750 : }
2751 ECB : else
2752 : {
2753 UIC 0 : ExplainPropertyText("Sampling Method", method_name, es);
2754 LBC 0 : ExplainPropertyList("Sampling Parameters", params, es);
2755 UIC 0 : if (repeatable)
2756 LBC 0 : ExplainPropertyText("Repeatable Seed", repeatable, es);
2757 : }
2758 CBC 33 : }
2759 ECB :
2760 : /*
2761 : * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
2762 : */
2763 : static void
2764 GIC 1776 : show_sort_info(SortState *sortstate, ExplainState *es)
2765 : {
2766 CBC 1776 : if (!es->analyze)
2767 GIC 1713 : return;
2768 :
2769 CBC 63 : if (sortstate->sort_Done && sortstate->tuplesortstate != NULL)
2770 : {
2771 60 : Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
2772 : TuplesortInstrumentation stats;
2773 ECB : const char *sortMethod;
2774 : const char *spaceType;
2775 : int64 spaceUsed;
2776 :
2777 CBC 60 : tuplesort_get_stats(state, &stats);
2778 GBC 60 : sortMethod = tuplesort_method_name(stats.sortMethod);
2779 CBC 60 : spaceType = tuplesort_space_type_name(stats.spaceType);
2780 60 : spaceUsed = stats.spaceUsed;
2781 :
2782 60 : if (es->format == EXPLAIN_FORMAT_TEXT)
2783 ECB : {
2784 CBC 45 : ExplainIndentText(es);
2785 45 : appendStringInfo(es->str, "Sort Method: %s %s: " INT64_FORMAT "kB\n",
2786 : sortMethod, spaceType, spaceUsed);
2787 : }
2788 : else
2789 EUB : {
2790 GBC 15 : ExplainPropertyText("Sort Method", sortMethod, es);
2791 15 : ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);
2792 15 : ExplainPropertyText("Sort Space Type", spaceType, es);
2793 : }
2794 ECB : }
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 CBC 63 : if (sortstate->shared_info != NULL)
2806 : {
2807 ECB : int n;
2808 :
2809 GIC 30 : for (n = 0; n < sortstate->shared_info->num_workers; n++)
2810 : {
2811 : TuplesortInstrumentation *sinstrument;
2812 : const char *sortMethod;
2813 ECB : const char *spaceType;
2814 : int64 spaceUsed;
2815 :
2816 CBC 24 : sinstrument = &sortstate->shared_info->sinstrument[n];
2817 GIC 24 : if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
2818 LBC 0 : continue; /* ignore any unfilled slots */
2819 GIC 24 : sortMethod = tuplesort_method_name(sinstrument->sortMethod);
2820 CBC 24 : spaceType = tuplesort_space_type_name(sinstrument->spaceType);
2821 24 : spaceUsed = sinstrument->spaceUsed;
2822 :
2823 GIC 24 : if (es->workers_state)
2824 24 : ExplainOpenWorker(n, es);
2825 :
2826 CBC 24 : if (es->format == EXPLAIN_FORMAT_TEXT)
2827 ECB : {
2828 CBC 12 : ExplainIndentText(es);
2829 GIC 12 : appendStringInfo(es->str,
2830 : "Sort Method: %s %s: " INT64_FORMAT "kB\n",
2831 : sortMethod, spaceType, spaceUsed);
2832 : }
2833 : else
2834 : {
2835 12 : ExplainPropertyText("Sort Method", sortMethod, es);
2836 12 : ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);
2837 12 : ExplainPropertyText("Sort Space Type", spaceType, es);
2838 : }
2839 :
2840 24 : if (es->workers_state)
2841 CBC 24 : ExplainCloseWorker(n, es);
2842 : }
2843 : }
2844 : }
2845 ECB :
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 EUB : static void
2855 CBC 27 : show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo,
2856 ECB : const char *groupLabel, bool indent, ExplainState *es)
2857 : {
2858 : ListCell *methodCell;
2859 CBC 27 : List *methodNames = NIL;
2860 ECB :
2861 : /* Generate a list of sort methods used across all groups. */
2862 CBC 135 : for (int bit = 0; bit < NUM_TUPLESORTMETHODS; bit++)
2863 : {
2864 108 : TuplesortMethod sortMethod = (1 << bit);
2865 ECB :
2866 GIC 108 : if (groupInfo->sortMethods & sortMethod)
2867 : {
2868 45 : const char *methodName = tuplesort_method_name(sortMethod);
2869 :
2870 45 : methodNames = lappend(methodNames, unconstify(char *, methodName));
2871 ECB : }
2872 : }
2873 :
2874 GIC 27 : if (es->format == EXPLAIN_FORMAT_TEXT)
2875 : {
2876 CBC 9 : if (indent)
2877 9 : appendStringInfoSpaces(es->str, es->indent * 2);
2878 GIC 9 : appendStringInfo(es->str, "%s Groups: " INT64_FORMAT " Sort Method", groupLabel,
2879 : groupInfo->groupCount);
2880 : /* plural/singular based on methodNames size */
2881 9 : if (list_length(methodNames) > 1)
2882 6 : appendStringInfoString(es->str, "s: ");
2883 : else
2884 3 : appendStringInfoString(es->str, ": ");
2885 24 : foreach(methodCell, methodNames)
2886 : {
2887 15 : appendStringInfoString(es->str, (char *) methodCell->ptr_value);
2888 15 : if (foreach_current_index(methodCell) < list_length(methodNames) - 1)
2889 6 : appendStringInfoString(es->str, ", ");
2890 : }
2891 ECB :
2892 GIC 9 : if (groupInfo->maxMemorySpaceUsed > 0)
2893 : {
2894 9 : int64 avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
2895 ECB : const char *spaceTypeName;
2896 :
2897 GIC 9 : spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
2898 CBC 9 : appendStringInfo(es->str, " Average %s: " INT64_FORMAT "kB Peak %s: " INT64_FORMAT "kB",
2899 : spaceTypeName, avgSpace,
2900 ECB : spaceTypeName, groupInfo->maxMemorySpaceUsed);
2901 : }
2902 :
2903 GIC 9 : if (groupInfo->maxDiskSpaceUsed > 0)
2904 ECB : {
2905 UIC 0 : int64 avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
2906 ECB :
2907 : const char *spaceTypeName;
2908 :
2909 UIC 0 : spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK);
2910 LBC 0 : appendStringInfo(es->str, " Average %s: " INT64_FORMAT "kB Peak %s: " INT64_FORMAT "kB",
2911 : spaceTypeName, avgSpace,
2912 ECB : spaceTypeName, groupInfo->maxDiskSpaceUsed);
2913 : }
2914 : }
2915 : else
2916 : {
2917 : StringInfoData groupName;
2918 :
2919 GIC 18 : initStringInfo(&groupName);
2920 CBC 18 : appendStringInfo(&groupName, "%s Groups", groupLabel);
2921 18 : ExplainOpenGroup("Incremental Sort Groups", groupName.data, true, es);
2922 GIC 18 : ExplainPropertyInteger("Group Count", NULL, groupInfo->groupCount, es);
2923 ECB :
2924 CBC 18 : ExplainPropertyList("Sort Methods Used", methodNames, es);
2925 ECB :
2926 GIC 18 : if (groupInfo->maxMemorySpaceUsed > 0)
2927 : {
2928 CBC 18 : int64 avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
2929 : const char *spaceTypeName;
2930 ECB : StringInfoData memoryName;
2931 :
2932 GIC 18 : spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
2933 CBC 18 : initStringInfo(&memoryName);
2934 18 : appendStringInfo(&memoryName, "Sort Space %s", spaceTypeName);
2935 GIC 18 : ExplainOpenGroup("Sort Space", memoryName.data, true, es);
2936 :
2937 18 : ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
2938 18 : ExplainPropertyInteger("Peak Sort Space Used", "kB",
2939 ECB : groupInfo->maxMemorySpaceUsed, es);
2940 :
2941 GBC 18 : ExplainCloseGroup("Sort Space", memoryName.data, true, es);
2942 : }
2943 GIC 18 : if (groupInfo->maxDiskSpaceUsed > 0)
2944 : {
2945 UBC 0 : int64 avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
2946 EUB : const char *spaceTypeName;
2947 : StringInfoData diskName;
2948 :
2949 UIC 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 LBC 0 : ExplainPropertyInteger("Peak Sort Space Used", "kB",
2956 ECB : groupInfo->maxDiskSpaceUsed, es);
2957 :
2958 LBC 0 : ExplainCloseGroup("Sort Space", diskName.data, true, es);
2959 : }
2960 ECB :
2961 GIC 18 : ExplainCloseGroup("Incremental Sort Groups", groupName.data, true, es);
2962 ECB : }
2963 GIC 27 : }
2964 ECB :
2965 : /*
2966 : * If it's EXPLAIN ANALYZE, show tuplesort stats for an incremental sort node
2967 : */
2968 : static void
2969 CBC 130 : show_incremental_sort_info(IncrementalSortState *incrsortstate,
2970 ECB : ExplainState *es)
2971 : {
2972 : IncrementalSortGroupInfo *fullsortGroupInfo;
2973 : IncrementalSortGroupInfo *prefixsortGroupInfo;
2974 :
2975 GIC 130 : fullsortGroupInfo = &incrsortstate->incsort_info.fullsortGroupInfo;
2976 :
2977 CBC 130 : if (!es->analyze)
2978 GIC 112 : return;
2979 ECB :
2980 : /*
2981 EUB : * 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 GIC 18 : if (fullsortGroupInfo->groupCount > 0)
2990 EUB : {
2991 GBC 18 : show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", true, es);
2992 GIC 18 : prefixsortGroupInfo = &incrsortstate->incsort_info.prefixsortGroupInfo;
2993 18 : if (prefixsortGroupInfo->groupCount > 0)
2994 EUB : {
2995 GIC 9 : if (es->format == EXPLAIN_FORMAT_TEXT)
2996 3 : appendStringInfoChar(es->str, '\n');
2997 CBC 9 : show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es);
2998 : }
2999 18 : if (es->format == EXPLAIN_FORMAT_TEXT)
3000 GIC 6 : appendStringInfoChar(es->str, '\n');
3001 : }
3002 :
3003 18 : if (incrsortstate->shared_info != NULL)
3004 : {
3005 ECB : int n;
3006 : bool indent_first_line;
3007 :
3008 UIC 0 : for (n = 0; n < incrsortstate->shared_info->num_workers; n++)
3009 : {
3010 0 : IncrementalSortInfo *incsort_info =
3011 LBC 0 : &incrsortstate->shared_info->sinfo[n];
3012 :
3013 ECB : /*
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 UIC 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 ECB : */
3026 UIC 0 : if (fullsortGroupInfo->groupCount == 0)
3027 LBC 0 : continue;
3028 ECB :
3029 LBC 0 : if (es->workers_state)
3030 UIC 0 : ExplainOpenWorker(n, es);
3031 ECB :
3032 LBC 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 UIC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
3039 LBC 0 : appendStringInfoChar(es->str, '\n');
3040 UIC 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 EUB :
3045 UIC 0 : if (es->workers_state)
3046 UBC 0 : ExplainCloseWorker(n, es);
3047 EUB : }
3048 : }
3049 : }
3050 :
3051 : /*
3052 : * Show information on hash buckets/batches.
3053 : */
3054 : static void
3055 GIC 1463 : show_hash_info(HashState *hashstate, ExplainState *es)
3056 : {
3057 1463 : 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 EUB : * 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 GIC 1463 : if (hashstate->hinstrument)
3068 GBC 54 : memcpy(&hinstrument, hashstate->hinstrument,
3069 EUB : 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 GIC 1463 : if (hashstate->shared_info)
3081 EUB : {
3082 GBC 42 : SharedHashInfo *shared_info = hashstate->shared_info;
3083 : int i;
3084 :
3085 GIC 120 : for (i = 0; i < shared_info->num_workers; ++i)
3086 : {
3087 78 : HashInstrumentation *worker_hi = &shared_info->hinstrument[i];
3088 :
3089 78 : hinstrument.nbuckets = Max(hinstrument.nbuckets,
3090 : worker_hi->nbuckets);
3091 CBC 78 : hinstrument.nbuckets_original = Max(hinstrument.nbuckets_original,
3092 : worker_hi->nbuckets_original);
3093 78 : hinstrument.nbatch = Max(hinstrument.nbatch,
3094 : worker_hi->nbatch);
3095 GIC 78 : hinstrument.nbatch_original = Max(hinstrument.nbatch_original,
3096 : worker_hi->nbatch_original);
3097 78 : hinstrument.space_peak = Max(hinstrument.space_peak,
3098 : worker_hi->space_peak);
3099 : }
3100 : }
3101 :
3102 1463 : if (hinstrument.nbatch > 0)
3103 ECB : {
3104 CBC 54 : long spacePeakKb = (hinstrument.space_peak + 1023) / 1024;
3105 :
3106 GIC 54 : if (es->format != EXPLAIN_FORMAT_TEXT)
3107 : {
3108 54 : ExplainPropertyInteger("Hash Buckets", NULL,
3109 54 : hinstrument.nbuckets, es);
3110 54 : ExplainPropertyInteger("Original Hash Buckets", NULL,
3111 54 : hinstrument.nbuckets_original, es);
3112 54 : ExplainPropertyInteger("Hash Batches", NULL,
3113 54 : hinstrument.nbatch, es);
3114 54 : ExplainPropertyInteger("Original Hash Batches", NULL,
3115 54 : hinstrument.nbatch_original, es);
3116 CBC 54 : ExplainPropertyInteger("Peak Memory Usage", "kB",
3117 : spacePeakKb, es);
3118 ECB : }
3119 UIC 0 : else if (hinstrument.nbatch_original != hinstrument.nbatch ||
3120 0 : hinstrument.nbuckets_original != hinstrument.nbuckets)
3121 ECB : {
3122 UIC 0 : ExplainIndentText(es);
3123 LBC 0 : appendStringInfo(es->str,
3124 : "Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n",
3125 ECB : hinstrument.nbuckets,
3126 : hinstrument.nbuckets_original,
3127 : hinstrument.nbatch,
3128 : hinstrument.nbatch_original,
3129 : spacePeakKb);
3130 : }
3131 : else
3132 : {
3133 LBC 0 : ExplainIndentText(es);
3134 UIC 0 : appendStringInfo(es->str,
3135 : "Buckets: %d Batches: %d Memory Usage: %ldkB\n",
3136 : hinstrument.nbuckets, hinstrument.nbatch,
3137 : spacePeakKb);
3138 ECB : }
3139 : }
3140 CBC 1463 : }
3141 :
3142 ECB : /*
3143 : * Show information on memoize hits/misses/evictions and memory usage.
3144 : */
3145 : static void
3146 CBC 68 : show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es)
3147 ECB : {
3148 CBC 68 : Plan *plan = ((PlanState *) mstate)->plan;
3149 ECB : ListCell *lc;
3150 : List *context;
3151 : StringInfoData keystr;
3152 CBC 68 : char *separator = "";
3153 : bool useprefix;
3154 : int64 memPeakKb;
3155 EUB :
3156 GBC 68 : initStringInfo(&keystr);
3157 :
3158 EUB : /*
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 GIC 68 : useprefix = list_length(es->rtable) > 1 || es->verbose;
3163 :
3164 : /* Set up deparsing context */
3165 68 : context = set_deparse_context_plan(es->deparse_cxt,
3166 : plan,
3167 : ancestors);
3168 :
3169 GBC 139 : foreach(lc, ((Memoize *) plan)->param_exprs)
3170 EUB : {
3171 GIC 71 : Node *expr = (Node *) lfirst(lc);
3172 :
3173 71 : appendStringInfoString(&keystr, separator);
3174 :
3175 71 : appendStringInfoString(&keystr, deparse_expression(expr, context,
3176 ECB : useprefix, false));
3177 GIC 71 : separator = ", ";
3178 : }
3179 :
3180 68 : if (es->format != EXPLAIN_FORMAT_TEXT)
3181 : {
3182 LBC 0 : ExplainPropertyText("Cache Key", keystr.data, es);
3183 UIC 0 : ExplainPropertyText("Cache Mode", mstate->binary_mode ? "binary" : "logical", es);
3184 ECB : }
3185 : else
3186 : {
3187 GIC 68 : ExplainIndentText(es);
3188 CBC 68 : appendStringInfo(es->str, "Cache Key: %s\n", keystr.data);
3189 GIC 68 : ExplainIndentText(es);
3190 68 : appendStringInfo(es->str, "Cache Mode: %s\n", mstate->binary_mode ? "binary" : "logical");
3191 : }
3192 ECB :
3193 GIC 68 : pfree(keystr.data);
3194 :
3195 68 : if (!es->analyze)
3196 68 : return;
3197 :
3198 CBC 30 : if (mstate->stats.cache_misses > 0)
3199 : {
3200 : /*
3201 ECB : * mem_peak is only set when we freed memory, so we must use mem_used
3202 : * when mem_peak is 0.
3203 : */
3204 GIC 30 : if (mstate->stats.mem_peak > 0)
3205 CBC 3 : memPeakKb = (mstate->stats.mem_peak + 1023) / 1024;
3206 : else
3207 27 : memPeakKb = (mstate->mem_used + 1023) / 1024;
3208 :
3209 30 : if (es->format != EXPLAIN_FORMAT_TEXT)
3210 : {
3211 LBC 0 : ExplainPropertyInteger("Cache Hits", NULL, mstate->stats.cache_hits, es);
3212 UIC 0 : ExplainPropertyInteger("Cache Misses", NULL, mstate->stats.cache_misses, es);
3213 LBC 0 : ExplainPropertyInteger("Cache Evictions", NULL, mstate->stats.cache_evictions, es);
3214 UIC 0 : ExplainPropertyInteger("Cache Overflows", NULL, mstate->stats.cache_overflows, es);
3215 0 : ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, es);
3216 ECB : }
3217 : else
3218 EUB : {
3219 GBC 30 : ExplainIndentText(es);
3220 GIC 30 : 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 ECB : mstate->stats.cache_misses,
3224 : mstate->stats.cache_evictions,
3225 : mstate->stats.cache_overflows,
3226 : memPeakKb);
3227 : }
3228 : }
3229 :
3230 GIC 30 : if (mstate->shared_info == NULL)
3231 CBC 30 : return;
3232 ECB :
3233 : /* Show details from parallel workers */
3234 LBC 0 : for (int n = 0; n < mstate->shared_info->num_workers; n++)
3235 : {
3236 : MemoizeInstrumentation *si;
3237 :
3238 UIC 0 : si = &mstate->shared_info->sinstrument[n];
3239 :
3240 ECB : /*
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 UIC 0 : if (si->cache_misses == 0)
3245 LBC 0 : continue;
3246 :
3247 UBC 0 : if (es->workers_state)
3248 0 : ExplainOpenWorker(n, es);
3249 EUB :
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 ECB : */
3256 LBC 0 : memPeakKb = (si->mem_peak + 1023) / 1024;
3257 :
3258 UIC 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 ECB : }
3267 : else
3268 : {
3269 UIC 0 : ExplainPropertyInteger("Cache Hits", NULL,
3270 UBC 0 : si->cache_hits, es);
3271 UIC 0 : ExplainPropertyInteger("Cache Misses", NULL,
3272 0 : si->cache_misses, es);
3273 0 : ExplainPropertyInteger("Cache Evictions", NULL,
3274 UBC 0 : si->cache_evictions, es);
3275 UIC 0 : ExplainPropertyInteger("Cache Overflows", NULL,
3276 0 : si->cache_overflows, es);
3277 0 : ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb,
3278 : es);
3279 : }
3280 EUB :
3281 UBC 0 : if (es->workers_state)
3282 UIC 0 : ExplainCloseWorker(n, es);
3283 EUB : }
3284 : }
3285 :
3286 : /*
3287 : * Show information on hash aggregate memory usage and batches.
3288 : */
3289 : static void
3290 GIC 4512 : show_hashagg_info(AggState *aggstate, ExplainState *es)
3291 : {
3292 GBC 4512 : Agg *agg = (Agg *) aggstate->ss.ps.plan;
3293 GIC 4512 : int64 memPeakKb = (aggstate->hash_mem_peak + 1023) / 1024;
3294 EUB :
3295 GIC 4512 : if (agg->aggstrategy != AGG_HASHED &&
3296 GBC 3738 : agg->aggstrategy != AGG_MIXED)
3297 3700 : return;
3298 :
3299 GIC 812 : if (es->format != EXPLAIN_FORMAT_TEXT)
3300 : {
3301 UIC 0 : if (es->costs)
3302 0 : ExplainPropertyInteger("Planned Partitions", NULL,
3303 0 : aggstate->hash_planned_partitions, es);
3304 EUB :
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 UBC 0 : if (es->analyze && aggstate->hash_mem_peak > 0)
3311 EUB : {
3312 UBC 0 : ExplainPropertyInteger("HashAgg Batches", NULL,
3313 UIC 0 : aggstate->hash_batches_used, es);
3314 0 : ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, es);
3315 0 : ExplainPropertyInteger("Disk Usage", "kB",
3316 UBC 0 : aggstate->hash_disk_used, es);
3317 EUB : }
3318 : }
3319 : else
3320 : {
3321 GIC 812 : bool gotone = false;
3322 :
3323 812 : if (es->costs && aggstate->hash_planned_partitions > 0)
3324 : {
3325 LBC 0 : ExplainIndentText(es);
3326 UIC 0 : appendStringInfo(es->str, "Planned Partitions: %d",
3327 ECB : aggstate->hash_planned_partitions);
3328 LBC 0 : gotone = true;
3329 : }
3330 ECB :
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 GBC 812 : if (es->analyze && aggstate->hash_mem_peak > 0)
3337 EUB : {
3338 GBC 288 : if (!gotone)
3339 GIC 288 : ExplainIndentText(es);
3340 : else
3341 UNC 0 : appendStringInfoSpaces(es->str, 2);
3342 :
3343 GIC 288 : appendStringInfo(es->str, "Batches: %d Memory Usage: " INT64_FORMAT "kB",
3344 : aggstate->hash_batches_used, memPeakKb);
3345 GBC 288 : gotone = true;
3346 :
3347 EUB : /* Only display disk usage if we spilled to disk */
3348 GBC 288 : if (aggstate->hash_batches_used > 1)
3349 EUB : {
3350 UBC 0 : appendStringInfo(es->str, " Disk Usage: " UINT64_FORMAT "kB",
3351 EUB : aggstate->hash_disk_used);
3352 : }
3353 : }
3354 :
3355 GIC 812 : if (gotone)
3356 CBC 288 : appendStringInfoChar(es->str, '\n');
3357 : }
3358 ECB :
3359 : /* Display stats for each parallel worker */
3360 GBC 812 : if (es->analyze && aggstate->shared_info != NULL)
3361 EUB : {
3362 UIC 0 : for (int n = 0; n < aggstate->shared_info->num_workers; n++)
3363 EUB : {
3364 : AggregateInstrumentation *sinstrument;
3365 : uint64 hash_disk_used;
3366 : int hash_batches_used;
3367 :
3368 UIC 0 : sinstrument = &aggstate->shared_info->sinstrument[n];
3369 : /* Skip workers that didn't do anything */
3370 0 : if (sinstrument->hash_mem_peak == 0)
3371 LBC 0 : continue;
3372 UIC 0 : hash_disk_used = sinstrument->hash_disk_used;
3373 LBC 0 : hash_batches_used = sinstrument->hash_batches_used;
3374 0 : memPeakKb = (sinstrument->hash_mem_peak + 1023) / 1024;
3375 :
3376 UBC 0 : if (es->workers_state)
3377 UIC 0 : ExplainOpenWorker(n, es);
3378 ECB :
3379 UIC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
3380 ECB : {
3381 UIC 0 : ExplainIndentText(es);
3382 :
3383 LBC 0 : appendStringInfo(es->str, "Batches: %d Memory Usage: " INT64_FORMAT "kB",
3384 : hash_batches_used, memPeakKb);
3385 EUB :
3386 : /* Only display disk usage if we spilled to disk */
3387 UIC 0 : if (hash_batches_used > 1)
3388 0 : appendStringInfo(es->str, " Disk Usage: " UINT64_FORMAT "kB",
3389 : hash_disk_used);
3390 LBC 0 : appendStringInfoChar(es->str, '\n');
3391 ECB : }
3392 : else
3393 : {
3394 UIC 0 : ExplainPropertyInteger("HashAgg Batches", NULL,
3395 ECB : hash_batches_used, es);
3396 UIC 0 : ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb,
3397 EUB : es);
3398 UIC 0 : ExplainPropertyInteger("Disk Usage", "kB", hash_disk_used, es);
3399 : }
3400 :
3401 0 : if (es->workers_state)
3402 0 : ExplainCloseWorker(n, es);
3403 EUB : }
3404 : }
3405 : }
3406 :
3407 : /*
3408 : * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
3409 : */
3410 : static void
3411 GBC 222 : show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
3412 EUB : {
3413 GIC 222 : if (es->format != EXPLAIN_FORMAT_TEXT)
3414 EUB : {
3415 GIC 30 : ExplainPropertyInteger("Exact Heap Blocks", NULL,
3416 EUB : planstate->exact_pages, es);
3417 GIC 30 : ExplainPropertyInteger("Lossy Heap Blocks", NULL,
3418 EUB : planstate->lossy_pages, es);
3419 : }
3420 : else
3421 : {
3422 GBC 192 : if (planstate->exact_pages > 0 || planstate->lossy_pages > 0)
3423 EUB : {
3424 GIC 135 : ExplainIndentText(es);
3425 GBC 135 : appendStringInfoString(es->str, "Heap Blocks:");
3426 GIC 135 : if (planstate->exact_pages > 0)
3427 135 : appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);
3428 135 : if (planstate->lossy_pages > 0)
3429 UBC 0 : appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);
3430 GIC 135 : appendStringInfoChar(es->str, '\n');
3431 EUB : }
3432 : }
3433 GBC 222 : }
3434 :
3435 : /*
3436 EUB : * 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 GIC 11068 : show_instrumentation_count(const char *qlabel, int which,
3442 : PlanState *planstate, ExplainState *es)
3443 : {
3444 : double nfiltered;
3445 : double nloops;
3446 ECB :
3447 GIC 11068 : if (!es->analyze || !planstate->instrument)
3448 CBC 9306 : return;
3449 :
3450 1762 : if (which == 2)
3451 GIC 529 : nfiltered = planstate->instrument->nfiltered2;
3452 ECB : else
3453 GIC 1233 : nfiltered = planstate->instrument->nfiltered1;
3454 1762 : nloops = planstate->instrument->nloops;
3455 :
3456 : /* In text mode, suppress zero counts; they're not interesting enough */
3457 CBC 1762 : if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
3458 : {
3459 826 : if (nloops > 0)
3460 826 : ExplainPropertyFloat(qlabel, NULL, nfiltered / nloops, 0, es);
3461 ECB : else
3462 LBC 0 : ExplainPropertyFloat(qlabel, NULL, 0.0, 0, es);
3463 ECB : }
3464 EUB : }
3465 ECB :
3466 : /*
3467 : * Show extra information for a ForeignScan node.
3468 : */
3469 : static void
3470 GIC 391 : show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
3471 : {
3472 391 : FdwRoutine *fdwroutine = fsstate->fdwroutine;
3473 :
3474 : /* Let the FDW emit whatever fields it wants */
3475 391 : if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
3476 ECB : {
3477 GIC 32 : if (fdwroutine->ExplainDirectModify != NULL)
3478 32 : fdwroutine->ExplainDirectModify(fsstate, es);
3479 : }
3480 : else
3481 : {
3482 CBC 359 : if (fdwroutine->ExplainForeignScan != NULL)
3483 359 : fdwroutine->ExplainForeignScan(fsstate, es);
3484 : }
3485 391 : }
3486 ECB :
3487 : /*
3488 : * Show initplan params evaluated at Gather or Gather Merge node.
3489 : */
3490 : static void
3491 GIC 21 : show_eval_params(Bitmapset *bms_params, ExplainState *es)
3492 ECB : {
3493 GIC 21 : int paramid = -1;
3494 CBC 21 : List *params = NIL;
3495 ECB :
3496 GIC 21 : Assert(bms_params);
3497 EUB :
3498 GIC 45 : while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
3499 : {
3500 : char param[32];
3501 :
3502 24 : snprintf(param, sizeof(param), "$%d", paramid);
3503 24 : params = lappend(params, pstrdup(param));
3504 : }
3505 ECB :
3506 GIC 21 : if (params)
3507 CBC 21 : ExplainPropertyList("Params Evaluated", params, es);
3508 GIC 21 : }
3509 :
3510 ECB : /*
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 GIC 4516 : explain_get_index_name(Oid indexId)
3522 : {
3523 : const char *result;
3524 :
3525 4516 : if (explain_get_index_name_hook)
3526 LBC 0 : result = (*explain_get_index_name_hook) (indexId);
3527 : else
3528 CBC 4516 : result = NULL;
3529 4516 : if (result == NULL)
3530 : {
3531 ECB : /* default behavior: look it up in the catalogs */
3532 GIC 4516 : result = get_rel_name(indexId);
3533 CBC 4516 : if (result == NULL)
3534 UIC 0 : elog(ERROR, "cache lookup failed for index %u", indexId);
3535 : }
3536 GIC 4516 : return result;
3537 ECB : }
3538 :
3539 : /*
3540 : * Show buffer usage details.
3541 : */
3542 : static void
3543 CBC 112 : show_buffer_usage(ExplainState *es, const BufferUsage *usage, bool planning)
3544 : {
3545 GIC 112 : if (es->format == EXPLAIN_FORMAT_TEXT)
3546 : {
3547 34 : bool has_shared = (usage->shared_blks_hit > 0 ||
3548 10 : usage->shared_blks_read > 0 ||
3549 31 : usage->shared_blks_dirtied > 0 ||
3550 9 : usage->shared_blks_written > 0);
3551 36 : bool has_local = (usage->local_blks_hit > 0 ||
3552 12 : usage->local_blks_read > 0 ||
3553 36 : usage->local_blks_dirtied > 0 ||
3554 12 : usage->local_blks_written > 0);
3555 24 : bool has_temp = (usage->temp_blks_read > 0 ||
3556 CBC 12 : usage->temp_blks_written > 0);
3557 GIC 24 : bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
3558 12 : !INSTR_TIME_IS_ZERO(usage->blk_write_time));
3559 24 : bool has_temp_timing = (!INSTR_TIME_IS_ZERO(usage->temp_blk_read_time) ||
3560 CBC 12 : !INSTR_TIME_IS_ZERO(usage->temp_blk_write_time));
3561 GBC 18 : bool show_planning = (planning && (has_shared ||
3562 GIC 6 : has_local || has_temp || has_timing ||
3563 ECB : has_temp_timing));
3564 :
3565 GIC 12 : if (show_planning)
3566 : {
3567 LBC 0 : ExplainIndentText(es);
3568 0 : appendStringInfoString(es->str, "Planning:\n");
3569 UBC 0 : es->indent++;
3570 : }
3571 ECB :
3572 : /* Show only positive counter values. */
3573 GIC 12 : if (has_shared || has_local || has_temp)
3574 : {
3575 3 : ExplainIndentText(es);
3576 3 : appendStringInfoString(es->str, "Buffers:");
3577 :
3578 CBC 3 : if (has_shared)
3579 : {
3580 3 : appendStringInfoString(es->str, " shared");
3581 GIC 3 : if (usage->shared_blks_hit > 0)
3582 CBC 2 : appendStringInfo(es->str, " hit=%lld",
3583 2 : (long long) usage->shared_blks_hit);
3584 3 : if (usage->shared_blks_read > 0)
3585 1 : appendStringInfo(es->str, " read=%lld",
3586 1 : (long long) usage->shared_blks_read);
3587 3 : if (usage->shared_blks_dirtied > 0)
3588 LBC 0 : appendStringInfo(es->str, " dirtied=%lld",
3589 0 : (long long) usage->shared_blks_dirtied);
3590 CBC 3 : if (usage->shared_blks_written > 0)
3591 LBC 0 : appendStringInfo(es->str, " written=%lld",
3592 0 : (long long) usage->shared_blks_written);
3593 CBC 3 : if (has_local || has_temp)
3594 LBC 0 : appendStringInfoChar(es->str, ',');
3595 ECB : }
3596 CBC 3 : if (has_local)
3597 ECB : {
3598 UIC 0 : appendStringInfoString(es->str, " local");
3599 0 : if (usage->local_blks_hit > 0)
3600 LBC 0 : appendStringInfo(es->str, " hit=%lld",
3601 UIC 0 : (long long) usage->local_blks_hit);
3602 UBC 0 : if (usage->local_blks_read > 0)
3603 0 : appendStringInfo(es->str, " read=%lld",
3604 0 : (long long) usage->local_blks_read);
3605 UIC 0 : if (usage->local_blks_dirtied > 0)
3606 0 : appendStringInfo(es->str, " dirtied=%lld",
3607 0 : (long long) usage->local_blks_dirtied);
3608 LBC 0 : if (usage->local_blks_written > 0)
3609 UIC 0 : appendStringInfo(es->str, " written=%lld",
3610 LBC 0 : (long long) usage->local_blks_written);
3611 0 : if (has_temp)
3612 UIC 0 : appendStringInfoChar(es->str, ',');
3613 ECB : }
3614 GIC 3 : if (has_temp)
3615 ECB : {
3616 LBC 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 EUB : }
3624 GBC 3 : appendStringInfoChar(es->str, '\n');
3625 ECB : }
3626 EUB :
3627 : /* As above, show only positive counter values. */
3628 CBC 12 : if (has_timing || has_temp_timing)
3629 EUB : {
3630 UIC 0 : ExplainIndentText(es);
3631 LBC 0 : appendStringInfoString(es->str, "I/O Timings:");
3632 :
3633 UBC 0 : if (has_timing)
3634 EUB : {
3635 UBC 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 EUB : }
3645 UBC 0 : if (has_temp_timing)
3646 EUB : {
3647 UBC 0 : appendStringInfoString(es->str, " temp");
3648 UIC 0 : if (!INSTR_TIME_IS_ZERO(usage->temp_blk_read_time))
3649 LBC 0 : appendStringInfo(es->str, " read=%0.3f",
3650 UIC 0 : INSTR_TIME_GET_MILLISEC(usage->temp_blk_read_time));
3651 UBC 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 EUB : }
3655 UBC 0 : appendStringInfoChar(es->str, '\n');
3656 EUB : }
3657 :
3658 GIC 12 : if (show_planning)
3659 LBC 0 : es->indent--;
3660 : }
3661 : else
3662 : {
3663 CBC 100 : ExplainPropertyInteger("Shared Hit Blocks", NULL,
3664 GIC 100 : usage->shared_blks_hit, es);
3665 GBC 100 : ExplainPropertyInteger("Shared Read Blocks", NULL,
3666 100 : usage->shared_blks_read, es);
3667 GIC 100 : ExplainPropertyInteger("Shared Dirtied Blocks", NULL,
3668 GBC 100 : usage->shared_blks_dirtied, es);
3669 GIC 100 : ExplainPropertyInteger("Shared Written Blocks", NULL,
3670 GBC 100 : usage->shared_blks_written, es);
3671 100 : ExplainPropertyInteger("Local Hit Blocks", NULL,
3672 100 : usage->local_blks_hit, es);
3673 100 : ExplainPropertyInteger("Local Read Blocks", NULL,
3674 100 : usage->local_blks_read, es);
3675 100 : ExplainPropertyInteger("Local Dirtied Blocks", NULL,
3676 100 : usage->local_blks_dirtied, es);
3677 100 : ExplainPropertyInteger("Local Written Blocks", NULL,
3678 100 : usage->local_blks_written, es);
3679 GIC 100 : ExplainPropertyInteger("Temp Read Blocks", NULL,
3680 GBC 100 : usage->temp_blks_read, es);
3681 GIC 100 : ExplainPropertyInteger("Temp Written Blocks", NULL,
3682 GBC 100 : usage->temp_blks_written, es);
3683 100 : if (track_io_timing)
3684 EUB : {
3685 GBC 6 : ExplainPropertyFloat("I/O Read Time", "ms",
3686 6 : INSTR_TIME_GET_MILLISEC(usage->blk_read_time),
3687 EUB : 3, es);
3688 GBC 6 : ExplainPropertyFloat("I/O Write Time", "ms",
3689 GIC 6 : INSTR_TIME_GET_MILLISEC(usage->blk_write_time),
3690 EUB : 3, es);
3691 GIC 6 : ExplainPropertyFloat("Temp I/O Read Time", "ms",
3692 6 : INSTR_TIME_GET_MILLISEC(usage->temp_blk_read_time),
3693 ECB : 3, es);
3694 GBC 6 : ExplainPropertyFloat("Temp I/O Write Time", "ms",
3695 GIC 6 : INSTR_TIME_GET_MILLISEC(usage->temp_blk_write_time),
3696 : 3, es);
3697 : }
3698 ECB : }
3699 CBC 112 : }
3700 ECB :
3701 : /*
3702 : * Show WAL usage details.
3703 : */
3704 : static void
3705 LBC 0 : show_wal_usage(ExplainState *es, const WalUsage *usage)
3706 ECB : {
3707 LBC 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
3708 ECB : {
3709 : /* Show only positive counter values. */
3710 LBC 0 : if ((usage->wal_records > 0) || (usage->wal_fpi > 0) ||
3711 0 : (usage->wal_bytes > 0))
3712 ECB : {
3713 LBC 0 : ExplainIndentText(es);
3714 0 : appendStringInfoString(es->str, "WAL:");
3715 ECB :
3716 LBC 0 : if (usage->wal_records > 0)
3717 0 : appendStringInfo(es->str, " records=%lld",
3718 0 : (long long) usage->wal_records);
3719 UIC 0 : if (usage->wal_fpi > 0)
3720 LBC 0 : appendStringInfo(es->str, " fpi=%lld",
3721 0 : (long long) usage->wal_fpi);
3722 UIC 0 : if (usage->wal_bytes > 0)
3723 LBC 0 : appendStringInfo(es->str, " bytes=" UINT64_FORMAT,
3724 0 : usage->wal_bytes);
3725 UIC 0 : appendStringInfoChar(es->str, '\n');
3726 ECB : }
3727 : }
3728 : else
3729 : {
3730 LBC 0 : ExplainPropertyInteger("WAL Records", NULL,
3731 UIC 0 : usage->wal_records, es);
3732 0 : ExplainPropertyInteger("WAL FPI", NULL,
3733 0 : usage->wal_fpi, es);
3734 LBC 0 : ExplainPropertyUInteger("WAL Bytes", NULL,
3735 UIC 0 : usage->wal_bytes, es);
3736 : }
3737 0 : }
3738 :
3739 : /*
3740 EUB : * Add some additional details about an IndexScan or IndexOnlyScan
3741 : */
3742 : static void
3743 GIC 2557 : ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
3744 : ExplainState *es)
3745 EUB : {
3746 GBC 2557 : const char *indexname = explain_get_index_name(indexid);
3747 :
3748 2557 : if (es->format == EXPLAIN_FORMAT_TEXT)
3749 EUB : {
3750 GIC 2536 : if (ScanDirectionIsBackward(indexorderdir))
3751 GBC 103 : appendStringInfoString(es->str, " Backward");
3752 2536 : appendStringInfo(es->str, " using %s", quote_identifier(indexname));
3753 EUB : }
3754 : else
3755 : {
3756 : const char *scandir;
3757 :
3758 GBC 21 : switch (indexorderdir)
3759 EUB : {
3760 UBC 0 : case BackwardScanDirection:
3761 UIC 0 : scandir = "Backward";
3762 0 : break;
3763 GBC 21 : case ForwardScanDirection:
3764 21 : scandir = "Forward";
3765 21 : break;
3766 UBC 0 : default:
3767 0 : scandir = "???";
3768 UIC 0 : break;
3769 EUB : }
3770 GIC 21 : ExplainPropertyText("Scan Direction", scandir, es);
3771 21 : ExplainPropertyText("Index Name", indexname, es);
3772 : }
3773 2557 : }
3774 :
3775 ECB : /*
3776 : * Show the target of a Scan node
3777 : */
3778 : static void
3779 GIC 16345 : ExplainScanTarget(Scan *plan, ExplainState *es)
3780 ECB : {
3781 GIC 16345 : ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
3782 CBC 16345 : }
3783 ECB :
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 GBC 400 : ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
3793 EUB : {
3794 GBC 400 : ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);
3795 CBC 400 : }
3796 ECB :
3797 : /*
3798 EUB : * Show the target relation of a scan or modify node
3799 : */
3800 : static void
3801 GIC 16976 : ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
3802 ECB : {
3803 CBC 16976 : char *objectname = NULL;
3804 GIC 16976 : char *namespace = NULL;
3805 CBC 16976 : const char *objecttag = NULL;
3806 : RangeTblEntry *rte;
3807 : char *refname;
3808 :
3809 GIC 16976 : rte = rt_fetch(rti, es->rtable);
3810 16976 : refname = (char *) list_nth(es->rtable_names, rti - 1);
3811 CBC 16976 : if (refname == NULL)
3812 UIC 0 : refname = rte->eref->aliasname;
3813 ECB :
3814 CBC 16976 : switch (nodeTag(plan))
3815 : {
3816 GIC 16160 : 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 ECB : case T_CustomScan:
3825 : case T_ModifyTable:
3826 : /* Assert it's on a real relation */
3827 CBC 16160 : Assert(rte->rtekind == RTE_RELATION);
3828 GIC 16160 : objectname = get_rel_name(rte->relid);
3829 16160 : if (es->verbose)
3830 1338 : namespace = get_namespace_name_or_temp(get_rel_namespace(rte->relid));
3831 16160 : objecttag = "Relation Name";
3832 16160 : break;
3833 CBC 186 : case T_FunctionScan:
3834 : {
3835 186 : FunctionScan *fscan = (FunctionScan *) plan;
3836 ECB :
3837 : /* Assert it's on a RangeFunction */
3838 GIC 186 : Assert(rte->rtekind == RTE_FUNCTION);
3839 :
3840 : /*
3841 ECB : * 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 EUB : * originally, the optimizer could have simplified it away.)
3845 : */
3846 CBC 186 : if (list_length(fscan->functions) == 1)
3847 : {
3848 186 : RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
3849 :
3850 GIC 186 : if (IsA(rtfunc->funcexpr, FuncExpr))
3851 : {
3852 174 : FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr;
3853 174 : Oid funcid = funcexpr->funcid;
3854 :
3855 174 : objectname = get_func_name(funcid);
3856 174 : if (es->verbose)
3857 58 : namespace = get_namespace_name_or_temp(get_func_namespace(funcid));
3858 : }
3859 ECB : }
3860 CBC 186 : objecttag = "Function Name";
3861 ECB : }
3862 CBC 186 : break;
3863 15 : case T_TableFuncScan:
3864 15 : Assert(rte->rtekind == RTE_TABLEFUNC);
3865 15 : objectname = "xmltable";
3866 GIC 15 : objecttag = "Table Function Name";
3867 CBC 15 : break;
3868 GIC 221 : case T_ValuesScan:
3869 221 : Assert(rte->rtekind == RTE_VALUES);
3870 CBC 221 : break;
3871 GIC 107 : case T_CteScan:
3872 : /* Assert it's on a non-self-reference CTE */
3873 107 : Assert(rte->rtekind == RTE_CTE);
3874 107 : Assert(!rte->self_reference);
3875 107 : objectname = rte->ctename;
3876 107 : objecttag = "CTE Name";
3877 107 : break;
3878 LBC 0 : case T_NamedTuplestoreScan:
3879 UIC 0 : Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
3880 LBC 0 : objectname = rte->enrname;
3881 UIC 0 : objecttag = "Tuplestore Name";
3882 LBC 0 : break;
3883 GIC 27 : case T_WorkTableScan:
3884 ECB : /* Assert it's on a self-reference CTE */
3885 CBC 27 : Assert(rte->rtekind == RTE_CTE);
3886 GIC 27 : Assert(rte->self_reference);
3887 CBC 27 : objectname = rte->ctename;
3888 27 : objecttag = "CTE Name";
3889 27 : break;
3890 GIC 260 : default:
3891 260 : break;
3892 ECB : }
3893 :
3894 CBC 16976 : if (es->format == EXPLAIN_FORMAT_TEXT)
3895 ECB : {
3896 CBC 16770 : appendStringInfoString(es->str, " on");
3897 16770 : if (namespace != NULL)
3898 1393 : appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
3899 ECB : quote_identifier(objectname));
3900 CBC 15377 : else if (objectname != NULL)
3901 14884 : appendStringInfo(es->str, " %s", quote_identifier(objectname));
3902 16770 : if (objectname == NULL || strcmp(refname, objectname) != 0)
3903 9139 : appendStringInfo(es->str, " %s", quote_identifier(refname));
3904 : }
3905 ECB : else
3906 : {
3907 CBC 206 : if (objecttag != NULL && objectname != NULL)
3908 206 : ExplainPropertyText(objecttag, objectname, es);
3909 206 : if (namespace != NULL)
3910 GBC 3 : ExplainPropertyText("Schema", namespace, es);
3911 206 : ExplainPropertyText("Alias", refname, es);
3912 EUB : }
3913 GBC 16976 : }
3914 EUB :
3915 ECB : /*
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 GIC 400 : show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
3925 : ExplainState *es)
3926 ECB : {
3927 GIC 400 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
3928 ECB : const char *operation;
3929 : const char *foperation;
3930 : bool labeltargets;
3931 : int j;
3932 CBC 400 : List *idxNames = NIL;
3933 ECB : ListCell *lst;
3934 :
3935 CBC 400 : switch (node->operation)
3936 : {
3937 GIC 110 : case CMD_INSERT:
3938 110 : operation = "Insert";
3939 CBC 110 : foperation = "Foreign Insert";
3940 110 : break;
3941 169 : case CMD_UPDATE:
3942 169 : operation = "Update";
3943 169 : foperation = "Foreign Update";
3944 GIC 169 : break;
3945 CBC 76 : case CMD_DELETE:
3946 GIC 76 : operation = "Delete";
3947 76 : foperation = "Foreign Delete";
3948 76 : break;
3949 45 : case CMD_MERGE:
3950 45 : operation = "Merge";
3951 : /* XXX unsupported for now, but avoid compiler noise */
3952 45 : foperation = "Foreign Merge";
3953 45 : break;
3954 UIC 0 : default:
3955 0 : operation = "???";
3956 LBC 0 : foperation = "Foreign ???";
3957 UIC 0 : break;
3958 : }
3959 ECB :
3960 : /* Should we explicitly label target relations? */
3961 GIC 725 : labeltargets = (mtstate->mt_nrels > 1 ||
3962 325 : (mtstate->mt_nrels == 1 &&
3963 325 : mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation));
3964 ECB :
3965 GIC 400 : if (labeltargets)
3966 94 : ExplainOpenGroup("Target Tables", "Target Tables", false, es);
3967 ECB :
3968 GIC 937 : for (j = 0; j < mtstate->mt_nrels; j++)
3969 ECB : {
3970 CBC 537 : ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
3971 537 : FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
3972 ECB :
3973 CBC 537 : if (labeltargets)
3974 ECB : {
3975 : /* Open a group for this target */
3976 CBC 231 : ExplainOpenGroup("Target Table", NULL, true, es);
3977 ECB :
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 CBC 231 : if (es->format == EXPLAIN_FORMAT_TEXT)
3983 : {
3984 231 : ExplainIndentText(es);
3985 231 : appendStringInfoString(es->str,
3986 EUB : fdwroutine ? foperation : operation);
3987 : }
3988 :
3989 : /* Identify target */
3990 GIC 231 : ExplainTargetRel((Plan *) node,
3991 : resultRelInfo->ri_RangeTableIndex,
3992 : es);
3993 ECB :
3994 CBC 231 : if (es->format == EXPLAIN_FORMAT_TEXT)
3995 ECB : {
3996 GIC 231 : appendStringInfoChar(es->str, '\n');
3997 CBC 231 : es->indent++;
3998 ECB : }
3999 : }
4000 :
4001 : /* Give FDW a chance if needed */
4002 CBC 537 : if (!resultRelInfo->ri_usesFdwDirectModify &&
4003 38 : fdwroutine != NULL &&
4004 GIC 38 : fdwroutine->ExplainForeignModify != NULL)
4005 ECB : {
4006 GIC 38 : List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
4007 :
4008 CBC 38 : fdwroutine->ExplainForeignModify(mtstate,
4009 : resultRelInfo,
4010 : fdw_private,
4011 : j,
4012 : es);
4013 : }
4014 ECB :
4015 GIC 537 : if (labeltargets)
4016 ECB : {
4017 : /* Undo the indentation we added in text format */
4018 GIC 231 : if (es->format == EXPLAIN_FORMAT_TEXT)
4019 231 : es->indent--;
4020 :
4021 : /* Close the group */
4022 CBC 231 : ExplainCloseGroup("Target Table", NULL, true, es);
4023 : }
4024 : }
4025 :
4026 ECB : /* Gather names of ON CONFLICT arbiter indexes */
4027 GIC 493 : foreach(lst, node->arbiterIndexes)
4028 ECB : {
4029 CBC 93 : char *indexname = get_rel_name(lfirst_oid(lst));
4030 :
4031 GIC 93 : idxNames = lappend(idxNames, indexname);
4032 : }
4033 :
4034 CBC 400 : if (node->onConflictAction != ONCONFLICT_NONE)
4035 ECB : {
4036 CBC 66 : ExplainPropertyText("Conflict Resolution",
4037 GIC 66 : node->onConflictAction == ONCONFLICT_NOTHING ?
4038 ECB : "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 GIC 66 : if (idxNames)
4046 66 : ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
4047 ECB :
4048 : /* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
4049 GIC 66 : if (node->onConflictWhere)
4050 ECB : {
4051 CBC 27 : show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
4052 : &mtstate->ps, ancestors, es);
4053 GIC 27 : show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
4054 ECB : }
4055 :
4056 : /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
4057 GIC 66 : if (es->analyze && mtstate->ps.instrument)
4058 : {
4059 ECB : double total;
4060 : double insert_path;
4061 : double other_path;
4062 :
4063 LBC 0 : InstrEndLoop(outerPlanState(mtstate)->instrument);
4064 :
4065 : /* count the number of source rows */
4066 0 : total = outerPlanState(mtstate)->instrument->ntuples;
4067 UIC 0 : other_path = mtstate->ps.instrument->ntuples2;
4068 LBC 0 : insert_path = total - other_path;
4069 ECB :
4070 UIC 0 : ExplainPropertyFloat("Tuples Inserted", NULL,
4071 : insert_path, 0, es);
4072 0 : ExplainPropertyFloat("Conflicting Tuples", NULL,
4073 : other_path, 0, es);
4074 : }
4075 : }
4076 GIC 334 : else if (node->operation == CMD_MERGE)
4077 ECB : {
4078 : /* EXPLAIN ANALYZE display of tuples processed */
4079 GIC 45 : if (es->analyze && mtstate->ps.instrument)
4080 : {
4081 ECB : double total;
4082 : double insert_path;
4083 : double update_path;
4084 : double delete_path;
4085 : double skipped_path;
4086 :
4087 GIC 18 : InstrEndLoop(outerPlanState(mtstate)->instrument);
4088 :
4089 ECB : /* count the number of source rows */
4090 GIC 18 : total = outerPlanState(mtstate)->instrument->ntuples;
4091 18 : insert_path = mtstate->mt_merge_inserted;
4092 18 : update_path = mtstate->mt_merge_updated;
4093 18 : delete_path = mtstate->mt_merge_deleted;
4094 18 : skipped_path = total - insert_path - update_path - delete_path;
4095 GBC 18 : Assert(skipped_path >= 0);
4096 :
4097 GIC 18 : if (es->format == EXPLAIN_FORMAT_TEXT)
4098 EUB : {
4099 GBC 18 : if (total > 0)
4100 EUB : {
4101 GIC 15 : ExplainIndentText(es);
4102 GBC 15 : appendStringInfoString(es->str, "Tuples:");
4103 GIC 15 : if (insert_path > 0)
4104 GBC 6 : appendStringInfo(es->str, " inserted=%.0f", insert_path);
4105 GIC 15 : if (update_path > 0)
4106 12 : appendStringInfo(es->str, " updated=%.0f", update_path);
4107 15 : if (delete_path > 0)
4108 CBC 6 : appendStringInfo(es->str, " deleted=%.0f", delete_path);
4109 GIC 15 : if (skipped_path > 0)
4110 12 : appendStringInfo(es->str, " skipped=%.0f", skipped_path);
4111 CBC 15 : appendStringInfoChar(es->str, '\n');
4112 : }
4113 : }
4114 : else
4115 : {
4116 UIC 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 LBC 0 : ExplainPropertyFloat("Tuples Skipped", NULL, skipped_path, 0, es);
4120 : }
4121 : }
4122 ECB : }
4123 :
4124 CBC 400 : if (labeltargets)
4125 94 : ExplainCloseGroup("Target Tables", "Target Tables", false, es);
4126 400 : }
4127 ECB :
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 CBC 1784 : ExplainMemberNodes(PlanState **planstates, int nplans,
4137 ECB : List *ancestors, ExplainState *es)
4138 : {
4139 : int j;
4140 :
4141 CBC 7358 : for (j = 0; j < nplans; j++)
4142 5574 : ExplainNode(planstates[j], ancestors,
4143 ECB : "Member", NULL, es);
4144 GIC 1784 : }
4145 :
4146 : /*
4147 : * Report about any pruned subnodes of an Append or MergeAppend node.
4148 EUB : *
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 GIC 1718 : ExplainMissingMembers(int nplans, int nchildren, ExplainState *es)
4155 : {
4156 CBC 1718 : if (nplans < nchildren || es->format != EXPLAIN_FORMAT_TEXT)
4157 89 : ExplainPropertyInteger("Subplans Removed", NULL,
4158 89 : nchildren - nplans, es);
4159 GIC 1718 : }
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 CBC 669 : ExplainSubPlans(List *plans, List *ancestors,
4169 : const char *relationship, ExplainState *es)
4170 : {
4171 : ListCell *lst;
4172 :
4173 1417 : foreach(lst, plans)
4174 ECB : {
4175 GIC 748 : SubPlanState *sps = (SubPlanState *) lfirst(lst);
4176 CBC 748 : 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 ECB : * attached to in such cases.)
4187 : */
4188 CBC 748 : if (bms_is_member(sp->plan_id, es->printed_subplans))
4189 39 : continue;
4190 709 : es->printed_subplans = bms_add_member(es->printed_subplans,
4191 ECB : 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 GIC 709 : ancestors = lcons(sp, ancestors);
4199 :
4200 CBC 709 : ExplainNode(sps->planstate, ancestors,
4201 GIC 709 : relationship, sp->plan_name, es);
4202 :
4203 709 : ancestors = list_delete_first(ancestors);
4204 : }
4205 CBC 669 : }
4206 :
4207 ECB : /*
4208 : * Explain a list of children of a CustomScan.
4209 : */
4210 : static void
4211 UIC 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 ECB :
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 CBC 507 : ExplainCreateWorkersState(int num_workers)
4234 : {
4235 ECB : ExplainWorkersState *wstate;
4236 :
4237 CBC 507 : wstate = (ExplainWorkersState *) palloc(sizeof(ExplainWorkersState));
4238 GIC 507 : wstate->num_workers = num_workers;
4239 507 : wstate->worker_inited = (bool *) palloc0(num_workers * sizeof(bool));
4240 507 : wstate->worker_str = (StringInfoData *)
4241 507 : palloc0(num_workers * sizeof(StringInfoData));
4242 507 : wstate->worker_state_save = (int *) palloc(num_workers * sizeof(int));
4243 GBC 507 : return wstate;
4244 : }
4245 :
4246 EUB : /*
4247 : * Begin or resume output into the set-aside group for worker N.
4248 : */
4249 : static void
4250 GBC 72 : ExplainOpenWorker(int n, ExplainState *es)
4251 EUB : {
4252 GIC 72 : ExplainWorkersState *wstate = es->workers_state;
4253 :
4254 72 : Assert(wstate);
4255 72 : Assert(n >= 0 && n < wstate->num_workers);
4256 :
4257 : /* Save prior output buffer pointer */
4258 72 : wstate->prev_str = es->str;
4259 :
4260 72 : if (!wstate->worker_inited[n])
4261 : {
4262 : /* First time through, so create the buffer for this worker */
4263 36 : initStringInfo(&wstate->worker_str[n]);
4264 36 : es->str = &wstate->worker_str[n];
4265 ECB :
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 CBC 36 : ExplainOpenSetAsideGroup("Worker", NULL, true, 2, es);
4272 ECB :
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 GIC 36 : if (es->format != EXPLAIN_FORMAT_TEXT)
4278 24 : ExplainPropertyInteger("Worker Number", NULL, n, es);
4279 :
4280 36 : wstate->worker_inited[n] = true;
4281 : }
4282 ECB : else
4283 : {
4284 : /* Resuming output for a worker we've already emitted some data for */
4285 GIC 36 : es->str = &wstate->worker_str[n];
4286 ECB :
4287 : /* Restore formatting state saved by last ExplainCloseWorker() */
4288 GIC 36 : ExplainRestoreGroup(es, 2, &wstate->worker_state_save[n]);
4289 : }
4290 ECB :
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 CBC 72 : if (es->format == EXPLAIN_FORMAT_TEXT)
4297 : {
4298 GIC 12 : if (es->str->len == 0)
4299 : {
4300 12 : ExplainIndentText(es);
4301 12 : appendStringInfo(es->str, "Worker %d: ", n);
4302 : }
4303 ECB :
4304 GIC 12 : es->indent++;
4305 : }
4306 72 : }
4307 :
4308 : /*
4309 ECB : * End output for worker N --- must pair with previous ExplainOpenWorker call
4310 : */
4311 : static void
4312 CBC 72 : ExplainCloseWorker(int n, ExplainState *es)
4313 : {
4314 GIC 72 : ExplainWorkersState *wstate = es->workers_state;
4315 :
4316 72 : Assert(wstate);
4317 CBC 72 : Assert(n >= 0 && n < wstate->num_workers);
4318 GIC 72 : Assert(wstate->worker_inited[n]);
4319 :
4320 ECB : /*
4321 : * Save formatting state in case we do another ExplainOpenWorker(), then
4322 : * pop the formatting stack.
4323 : */
4324 GIC 72 : 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 ECB : * 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 CBC 72 : if (es->format == EXPLAIN_FORMAT_TEXT)
4333 ECB : {
4334 GIC 12 : while (es->str->len > 0 && es->str->data[es->str->len - 1] != '\n')
4335 UIC 0 : es->str->data[--(es->str->len)] = '\0';
4336 ECB :
4337 GIC 12 : es->indent--;
4338 ECB : }
4339 :
4340 : /* Restore prior output buffer pointer */
4341 GIC 72 : es->str = wstate->prev_str;
4342 72 : }
4343 :
4344 ECB : /*
4345 : * Print per-worker info for current node, then free the ExplainWorkersState.
4346 : */
4347 : static void
4348 CBC 507 : ExplainFlushWorkersState(ExplainState *es)
4349 ECB : {
4350 CBC 507 : ExplainWorkersState *wstate = es->workers_state;
4351 :
4352 GIC 507 : ExplainOpenGroup("Workers", "Workers", false, es);
4353 1332 : for (int i = 0; i < wstate->num_workers; i++)
4354 : {
4355 825 : if (wstate->worker_inited[i])
4356 ECB : {
4357 : /* This must match previous ExplainOpenSetAsideGroup call */
4358 GIC 36 : ExplainOpenGroup("Worker", NULL, true, es);
4359 36 : appendStringInfoString(es->str, wstate->worker_str[i].data);
4360 36 : ExplainCloseGroup("Worker", NULL, true, es);
4361 :
4362 36 : pfree(wstate->worker_str[i].data);
4363 : }
4364 ECB : }
4365 GIC 507 : ExplainCloseGroup("Workers", "Workers", false, es);
4366 ECB :
4367 GBC 507 : pfree(wstate->worker_inited);
4368 GIC 507 : pfree(wstate->worker_str);
4369 CBC 507 : pfree(wstate->worker_state_save);
4370 GIC 507 : pfree(wstate);
4371 507 : }
4372 :
4373 ECB : /*
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 GIC 6416 : ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
4379 : {
4380 ECB : ListCell *lc;
4381 GIC 6416 : bool first = true;
4382 ECB :
4383 GIC 6416 : switch (es->format)
4384 ECB : {
4385 CBC 6347 : case EXPLAIN_FORMAT_TEXT:
4386 GIC 6347 : ExplainIndentText(es);
4387 CBC 6347 : appendStringInfo(es->str, "%s: ", qlabel);
4388 GIC 18909 : foreach(lc, data)
4389 : {
4390 CBC 12562 : if (!first)
4391 6215 : appendStringInfoString(es->str, ", ");
4392 12562 : appendStringInfoString(es->str, (const char *) lfirst(lc));
4393 GIC 12562 : first = false;
4394 ECB : }
4395 GIC 6347 : appendStringInfoChar(es->str, '\n');
4396 6347 : break;
4397 ECB :
4398 UIC 0 : case EXPLAIN_FORMAT_XML:
4399 LBC 0 : ExplainXMLTag(qlabel, X_OPENING, es);
4400 0 : foreach(lc, data)
4401 ECB : {
4402 : char *str;
4403 :
4404 UIC 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 ECB : }
4411 UIC 0 : ExplainXMLTag(qlabel, X_CLOSING, es);
4412 0 : break;
4413 ECB :
4414 GIC 69 : case EXPLAIN_FORMAT_JSON:
4415 CBC 69 : ExplainJSONLineEnding(es);
4416 GIC 69 : appendStringInfoSpaces(es->str, es->indent * 2);
4417 CBC 69 : escape_json(es->str, qlabel);
4418 69 : appendStringInfoString(es->str, ": [");
4419 297 : foreach(lc, data)
4420 ECB : {
4421 GIC 228 : if (!first)
4422 CBC 159 : appendStringInfoString(es->str, ", ");
4423 228 : escape_json(es->str, (const char *) lfirst(lc));
4424 228 : first = false;
4425 ECB : }
4426 GIC 69 : appendStringInfoChar(es->str, ']');
4427 CBC 69 : break;
4428 ECB :
4429 UIC 0 : case EXPLAIN_FORMAT_YAML:
4430 UBC 0 : ExplainYAMLLineStarting(es);
4431 0 : appendStringInfo(es->str, "%s: ", qlabel);
4432 0 : foreach(lc, data)
4433 : {
4434 UIC 0 : appendStringInfoChar(es->str, '\n');
4435 0 : appendStringInfoSpaces(es->str, es->indent * 2 + 2);
4436 UBC 0 : appendStringInfoString(es->str, "- ");
4437 0 : escape_yaml(es->str, (const char *) lfirst(lc));
4438 EUB : }
4439 UBC 0 : break;
4440 EUB : }
4441 GBC 6416 : }
4442 :
4443 EUB : /*
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 ECB : */
4447 : void
4448 CBC 226 : ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
4449 ECB : {
4450 : ListCell *lc;
4451 CBC 226 : bool first = true;
4452 :
4453 226 : switch (es->format)
4454 ECB : {
4455 CBC 226 : case EXPLAIN_FORMAT_TEXT:
4456 ECB : case EXPLAIN_FORMAT_XML:
4457 GIC 226 : ExplainPropertyList(qlabel, data, es);
4458 CBC 226 : return;
4459 ECB :
4460 UIC 0 : case EXPLAIN_FORMAT_JSON:
4461 UBC 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 UIC 0 : break;
4473 ECB :
4474 UIC 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 LBC 0 : appendStringInfoString(es->str, ", ");
4481 UIC 0 : escape_yaml(es->str, (const char *) lfirst(lc));
4482 0 : first = false;
4483 ECB : }
4484 UIC 0 : appendStringInfoChar(es->str, ']');
4485 LBC 0 : break;
4486 : }
4487 ECB : }
4488 :
4489 : /*
4490 : * Explain a simple property.
4491 : *
4492 EUB : * 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 GBC 28343 : ExplainProperty(const char *qlabel, const char *unit, const char *value,
4502 : bool numeric, ExplainState *es)
4503 EUB : {
4504 GBC 28343 : switch (es->format)
4505 : {
4506 19472 : case EXPLAIN_FORMAT_TEXT:
4507 19472 : ExplainIndentText(es);
4508 19472 : if (unit)
4509 2310 : appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit);
4510 : else
4511 17162 : appendStringInfo(es->str, "%s: %s\n", qlabel, value);
4512 19472 : break;
4513 EUB :
4514 GBC 105 : case EXPLAIN_FORMAT_XML:
4515 : {
4516 EUB : char *str;
4517 :
4518 GIC 105 : appendStringInfoSpaces(es->str, es->indent * 2);
4519 105 : ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
4520 105 : str = escape_xml(value);
4521 105 : appendStringInfoString(es->str, str);
4522 105 : pfree(str);
4523 105 : ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
4524 105 : appendStringInfoChar(es->str, '\n');
4525 : }
4526 105 : break;
4527 :
4528 8661 : case EXPLAIN_FORMAT_JSON:
4529 8661 : ExplainJSONLineEnding(es);
4530 8661 : appendStringInfoSpaces(es->str, es->indent * 2);
4531 8661 : escape_json(es->str, qlabel);
4532 8661 : appendStringInfoString(es->str, ": ");
4533 CBC 8661 : if (numeric)
4534 GIC 6856 : appendStringInfoString(es->str, value);
4535 : else
4536 CBC 1805 : escape_json(es->str, value);
4537 GIC 8661 : break;
4538 ECB :
4539 CBC 105 : case EXPLAIN_FORMAT_YAML:
4540 105 : ExplainYAMLLineStarting(es);
4541 105 : appendStringInfo(es->str, "%s: ", qlabel);
4542 GIC 105 : if (numeric)
4543 CBC 96 : appendStringInfoString(es->str, value);
4544 ECB : else
4545 GIC 9 : escape_yaml(es->str, value);
4546 CBC 105 : break;
4547 : }
4548 GIC 28343 : }
4549 :
4550 ECB : /*
4551 : * Explain a string-valued property.
4552 : */
4553 : void
4554 CBC 17620 : ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
4555 ECB : {
4556 CBC 17620 : ExplainProperty(qlabel, NULL, value, false, es);
4557 GIC 17620 : }
4558 ECB :
4559 : /*
4560 : * Explain an integer-valued property.
4561 : */
4562 : void
4563 CBC 2497 : ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value,
4564 ECB : ExplainState *es)
4565 : {
4566 : char buf[32];
4567 :
4568 CBC 2497 : snprintf(buf, sizeof(buf), INT64_FORMAT, value);
4569 2497 : ExplainProperty(qlabel, unit, buf, true, es);
4570 GIC 2497 : }
4571 ECB :
4572 : /*
4573 : * Explain an unsigned integer-valued property.
4574 : */
4575 : void
4576 UIC 0 : ExplainPropertyUInteger(const char *qlabel, const char *unit, uint64 value,
4577 ECB : ExplainState *es)
4578 : {
4579 : char buf[32];
4580 :
4581 UIC 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, value);
4582 0 : ExplainProperty(qlabel, unit, buf, true, es);
4583 0 : }
4584 :
4585 : /*
4586 ECB : * Explain a float-valued property, using the specified number of
4587 : * fractional digits.
4588 : */
4589 : void
4590 GIC 6972 : ExplainPropertyFloat(const char *qlabel, const char *unit, double value,
4591 : int ndigits, ExplainState *es)
4592 : {
4593 : char *buf;
4594 :
4595 CBC 6972 : buf = psprintf("%.*f", ndigits, value);
4596 GIC 6972 : ExplainProperty(qlabel, unit, buf, true, es);
4597 6972 : pfree(buf);
4598 6972 : }
4599 :
4600 ECB : /*
4601 : * Explain a bool-valued property.
4602 : */
4603 : void
4604 GIC 1254 : ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
4605 : {
4606 1254 : ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es);
4607 1254 : }
4608 EUB :
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 GIC 64321 : ExplainOpenGroup(const char *objtype, const char *labelname,
4620 : bool labeled, ExplainState *es)
4621 : {
4622 CBC 64321 : switch (es->format)
4623 : {
4624 GIC 62879 : case EXPLAIN_FORMAT_TEXT:
4625 : /* nothing to do */
4626 62879 : break;
4627 ECB :
4628 CBC 12 : case EXPLAIN_FORMAT_XML:
4629 12 : ExplainXMLTag(objtype, X_OPENING, es);
4630 12 : es->indent++;
4631 GIC 12 : break;
4632 :
4633 1418 : case EXPLAIN_FORMAT_JSON:
4634 1418 : ExplainJSONLineEnding(es);
4635 1418 : appendStringInfoSpaces(es->str, 2 * es->indent);
4636 CBC 1418 : if (labelname)
4637 : {
4638 879 : escape_json(es->str, labelname);
4639 879 : appendStringInfoString(es->str, ": ");
4640 : }
4641 GIC 1418 : 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 1418 : es->grouping_stack = lcons_int(0, es->grouping_stack);
4650 1418 : es->indent++;
4651 CBC 1418 : break;
4652 :
4653 GIC 12 : case EXPLAIN_FORMAT_YAML:
4654 ECB :
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 CBC 12 : ExplainYAMLLineStarting(es);
4662 12 : if (labelname)
4663 ECB : {
4664 GIC 9 : appendStringInfo(es->str, "%s: ", labelname);
4665 CBC 9 : es->grouping_stack = lcons_int(1, es->grouping_stack);
4666 ECB : }
4667 : else
4668 : {
4669 GIC 3 : appendStringInfoString(es->str, "- ");
4670 CBC 3 : es->grouping_stack = lcons_int(0, es->grouping_stack);
4671 ECB : }
4672 GIC 12 : es->indent++;
4673 CBC 12 : break;
4674 : }
4675 GIC 64321 : }
4676 :
4677 : /*
4678 : * Close a group of related objects.
4679 : * Parameters must match the corresponding ExplainOpenGroup call.
4680 : */
4681 ECB : void
4682 CBC 64321 : ExplainCloseGroup(const char *objtype, const char *labelname,
4683 ECB : bool labeled, ExplainState *es)
4684 : {
4685 CBC 64321 : switch (es->format)
4686 : {
4687 GIC 62879 : case EXPLAIN_FORMAT_TEXT:
4688 : /* nothing to do */
4689 62879 : break;
4690 :
4691 12 : case EXPLAIN_FORMAT_XML:
4692 12 : es->indent--;
4693 CBC 12 : ExplainXMLTag(objtype, X_CLOSING, es);
4694 12 : break;
4695 :
4696 1418 : case EXPLAIN_FORMAT_JSON:
4697 1418 : es->indent--;
4698 GIC 1418 : appendStringInfoChar(es->str, '\n');
4699 1418 : appendStringInfoSpaces(es->str, 2 * es->indent);
4700 1418 : appendStringInfoChar(es->str, labeled ? '}' : ']');
4701 CBC 1418 : es->grouping_stack = list_delete_first(es->grouping_stack);
4702 1418 : break;
4703 :
4704 12 : case EXPLAIN_FORMAT_YAML:
4705 12 : es->indent--;
4706 GIC 12 : es->grouping_stack = list_delete_first(es->grouping_stack);
4707 CBC 12 : break;
4708 : }
4709 GIC 64321 : }
4710 :
4711 : /*
4712 : * Open a group of related objects, without emitting actual data.
4713 : *
4714 ECB : * 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 CBC 36 : ExplainOpenSetAsideGroup(const char *objtype, const char *labelname,
4730 ECB : bool labeled, int depth, ExplainState *es)
4731 : {
4732 CBC 36 : switch (es->format)
4733 ECB : {
4734 CBC 12 : case EXPLAIN_FORMAT_TEXT:
4735 : /* nothing to do */
4736 12 : break;
4737 ECB :
4738 LBC 0 : case EXPLAIN_FORMAT_XML:
4739 0 : es->indent += depth;
4740 UIC 0 : break;
4741 ECB :
4742 GIC 24 : case EXPLAIN_FORMAT_JSON:
4743 24 : es->grouping_stack = lcons_int(0, es->grouping_stack);
4744 24 : es->indent += depth;
4745 24 : break;
4746 :
4747 UIC 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 GIC 36 : }
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 ECB : * 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 CBC 72 : ExplainSaveGroup(ExplainState *es, int depth, int *state_save)
4769 : {
4770 GBC 72 : switch (es->format)
4771 EUB : {
4772 GBC 12 : case EXPLAIN_FORMAT_TEXT:
4773 : /* nothing to do */
4774 CBC 12 : break;
4775 ECB :
4776 LBC 0 : case EXPLAIN_FORMAT_XML:
4777 0 : es->indent -= depth;
4778 UIC 0 : break;
4779 EUB :
4780 GBC 60 : case EXPLAIN_FORMAT_JSON:
4781 60 : es->indent -= depth;
4782 GIC 60 : *state_save = linitial_int(es->grouping_stack);
4783 GBC 60 : es->grouping_stack = list_delete_first(es->grouping_stack);
4784 60 : break;
4785 EUB :
4786 UIC 0 : case EXPLAIN_FORMAT_YAML:
4787 LBC 0 : es->indent -= depth;
4788 UIC 0 : *state_save = linitial_int(es->grouping_stack);
4789 0 : es->grouping_stack = list_delete_first(es->grouping_stack);
4790 0 : break;
4791 : }
4792 GIC 72 : }
4793 :
4794 : /*
4795 : * Re-push one level of grouping state, undoing the effects of ExplainSaveGroup.
4796 : */
4797 : static void
4798 36 : ExplainRestoreGroup(ExplainState *es, int depth, int *state_save)
4799 : {
4800 CBC 36 : switch (es->format)
4801 : {
4802 LBC 0 : case EXPLAIN_FORMAT_TEXT:
4803 : /* nothing to do */
4804 0 : break;
4805 :
4806 0 : case EXPLAIN_FORMAT_XML:
4807 UIC 0 : es->indent += depth;
4808 UBC 0 : break;
4809 EUB :
4810 GBC 36 : case EXPLAIN_FORMAT_JSON:
4811 GIC 36 : es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
4812 CBC 36 : es->indent += depth;
4813 36 : break;
4814 ECB :
4815 LBC 0 : case EXPLAIN_FORMAT_YAML:
4816 0 : es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
4817 UIC 0 : es->indent += depth;
4818 UBC 0 : break;
4819 EUB : }
4820 GBC 36 : }
4821 EUB :
4822 : /*
4823 : * Emit a "dummy" group that never has any members.
4824 ECB : *
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 GIC 15 : ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
4830 ECB : {
4831 GIC 15 : switch (es->format)
4832 ECB : {
4833 GIC 15 : case EXPLAIN_FORMAT_TEXT:
4834 EUB : /* nothing to do */
4835 GIC 15 : break;
4836 EUB :
4837 UIC 0 : case EXPLAIN_FORMAT_XML:
4838 UBC 0 : ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
4839 0 : break;
4840 EUB :
4841 UIC 0 : case EXPLAIN_FORMAT_JSON:
4842 LBC 0 : ExplainJSONLineEnding(es);
4843 0 : appendStringInfoSpaces(es->str, 2 * es->indent);
4844 0 : if (labelname)
4845 ECB : {
4846 UIC 0 : escape_json(es->str, labelname);
4847 UBC 0 : appendStringInfoString(es->str, ": ");
4848 EUB : }
4849 UBC 0 : escape_json(es->str, objtype);
4850 0 : break;
4851 :
4852 LBC 0 : case EXPLAIN_FORMAT_YAML:
4853 UIC 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 LBC 0 : appendStringInfoString(es->str, "- ");
4862 : }
4863 0 : escape_yaml(es->str, objtype);
4864 UIC 0 : break;
4865 ECB : }
4866 GIC 15 : }
4867 ECB :
4868 : /*
4869 EUB : * 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 GBC 9934 : ExplainBeginOutput(ExplainState *es)
4876 EUB : {
4877 GIC 9934 : switch (es->format)
4878 EUB : {
4879 GBC 9794 : case EXPLAIN_FORMAT_TEXT:
4880 : /* nothing to do */
4881 9794 : break;
4882 EUB :
4883 GIC 3 : case EXPLAIN_FORMAT_XML:
4884 GBC 3 : appendStringInfoString(es->str,
4885 EUB : "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
4886 GBC 3 : es->indent++;
4887 GIC 3 : break;
4888 EUB :
4889 GBC 134 : case EXPLAIN_FORMAT_JSON:
4890 : /* top-level structure is an array of plans */
4891 GIC 134 : appendStringInfoChar(es->str, '[');
4892 134 : es->grouping_stack = lcons_int(0, es->grouping_stack);
4893 GBC 134 : es->indent++;
4894 GIC 134 : break;
4895 EUB :
4896 GBC 3 : case EXPLAIN_FORMAT_YAML:
4897 GIC 3 : es->grouping_stack = lcons_int(0, es->grouping_stack);
4898 CBC 3 : break;
4899 : }
4900 GIC 9934 : }
4901 :
4902 : /*
4903 : * Emit the end-of-output boilerplate.
4904 : */
4905 : void
4906 9883 : ExplainEndOutput(ExplainState *es)
4907 ECB : {
4908 GIC 9883 : switch (es->format)
4909 ECB : {
4910 GIC 9743 : case EXPLAIN_FORMAT_TEXT:
4911 ECB : /* nothing to do */
4912 GIC 9743 : break;
4913 ECB :
4914 GIC 3 : case EXPLAIN_FORMAT_XML:
4915 CBC 3 : es->indent--;
4916 3 : appendStringInfoString(es->str, "</explain>");
4917 GIC 3 : break;
4918 ECB :
4919 CBC 134 : case EXPLAIN_FORMAT_JSON:
4920 GIC 134 : es->indent--;
4921 CBC 134 : appendStringInfoString(es->str, "\n]");
4922 GIC 134 : es->grouping_stack = list_delete_first(es->grouping_stack);
4923 CBC 134 : break;
4924 ECB :
4925 CBC 3 : case EXPLAIN_FORMAT_YAML:
4926 3 : es->grouping_stack = list_delete_first(es->grouping_stack);
4927 GIC 3 : break;
4928 ECB : }
4929 CBC 9883 : }
4930 ECB :
4931 : /*
4932 : * Put an appropriate separator between multiple plans
4933 : */
4934 : void
4935 GIC 6 : ExplainSeparatePlans(ExplainState *es)
4936 : {
4937 6 : switch (es->format)
4938 ECB : {
4939 GIC 6 : case EXPLAIN_FORMAT_TEXT:
4940 ECB : /* add a blank line */
4941 GIC 6 : appendStringInfoChar(es->str, '\n');
4942 CBC 6 : break;
4943 :
4944 LBC 0 : case EXPLAIN_FORMAT_XML:
4945 : case EXPLAIN_FORMAT_JSON:
4946 ECB : case EXPLAIN_FORMAT_YAML:
4947 : /* nothing to do */
4948 LBC 0 : break;
4949 ECB : }
4950 GIC 6 : }
4951 ECB :
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 GIC 234 : ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
4965 : {
4966 : const char *s;
4967 CBC 234 : const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
4968 :
4969 234 : if ((flags & X_NOWHITESPACE) == 0)
4970 GIC 24 : appendStringInfoSpaces(es->str, 2 * es->indent);
4971 CBC 234 : appendStringInfoCharMacro(es->str, '<');
4972 GIC 234 : if ((flags & X_CLOSING) != 0)
4973 CBC 117 : appendStringInfoCharMacro(es->str, '/');
4974 3690 : for (s = tagname; *s; s++)
4975 GIC 3456 : appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
4976 GBC 234 : if ((flags & X_CLOSE_IMMEDIATE) != 0)
4977 UIC 0 : appendStringInfoString(es->str, " /");
4978 GIC 234 : appendStringInfoCharMacro(es->str, '>');
4979 234 : if ((flags & X_NOWHITESPACE) == 0)
4980 GBC 24 : appendStringInfoCharMacro(es->str, '\n');
4981 GIC 234 : }
4982 ECB :
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 GIC 51301 : ExplainIndentText(ExplainState *es)
4992 : {
4993 51301 : Assert(es->format == EXPLAIN_FORMAT_TEXT);
4994 51301 : if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n')
4995 51289 : appendStringInfoSpaces(es->str, es->indent * 2);
4996 CBC 51301 : }
4997 :
4998 : /*
4999 ECB : * 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 CBC 10148 : ExplainJSONLineEnding(ExplainState *es)
5007 ECB : {
5008 CBC 10148 : Assert(es->format == EXPLAIN_FORMAT_JSON);
5009 GBC 10148 : if (linitial_int(es->grouping_stack) != 0)
5010 CBC 8913 : appendStringInfoChar(es->str, ',');
5011 ECB : else
5012 CBC 1235 : linitial_int(es->grouping_stack) = 1;
5013 10148 : appendStringInfoChar(es->str, '\n');
5014 GIC 10148 : }
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 ECB : * property of the group appears on the same line as the opening "- ".
5024 : */
5025 : static void
5026 CBC 117 : ExplainYAMLLineStarting(ExplainState *es)
5027 ECB : {
5028 CBC 117 : Assert(es->format == EXPLAIN_FORMAT_YAML);
5029 GIC 117 : if (linitial_int(es->grouping_stack) == 0)
5030 : {
5031 6 : linitial_int(es->grouping_stack) = 1;
5032 : }
5033 : else
5034 : {
5035 111 : appendStringInfoChar(es->str, '\n');
5036 111 : appendStringInfoSpaces(es->str, es->indent * 2);
5037 : }
5038 CBC 117 : }
5039 :
5040 ECB : /*
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 GIC 9 : escape_yaml(StringInfo buf, const char *str)
5052 : {
5053 9 : escape_json(buf, str);
5054 9 : }
|