Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * explain.c
4 : * Explain query execution plans
5 : *
6 : * Portions Copyright (c) 1996-2017, 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_collation.h"
18 : #include "catalog/pg_type.h"
19 : #include "commands/createas.h"
20 : #include "commands/defrem.h"
21 : #include "commands/prepare.h"
22 : #include "executor/hashjoin.h"
23 : #include "foreign/fdwapi.h"
24 : #include "nodes/extensible.h"
25 : #include "nodes/nodeFuncs.h"
26 : #include "optimizer/clauses.h"
27 : #include "optimizer/planmain.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/json.h"
34 : #include "utils/lsyscache.h"
35 : #include "utils/rel.h"
36 : #include "utils/ruleutils.h"
37 : #include "utils/snapmgr.h"
38 : #include "utils/tuplesort.h"
39 : #include "utils/typcache.h"
40 : #include "utils/xml.h"
41 :
42 :
43 : /* Hook for plugins to get control in ExplainOneQuery() */
44 : ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
45 :
46 : /* Hook for plugins to get control in explain_get_index_name() */
47 : explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
48 :
49 :
50 : /* OR-able flags for ExplainXMLTag() */
51 : #define X_OPENING 0
52 : #define X_CLOSING 1
53 : #define X_CLOSE_IMMEDIATE 2
54 : #define X_NOWHITESPACE 4
55 :
56 : static void ExplainOneQuery(Query *query, int cursorOptions,
57 : IntoClause *into, ExplainState *es,
58 : const char *queryString, ParamListInfo params,
59 : QueryEnvironment *queryEnv);
60 : static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
61 : ExplainState *es);
62 : static double elapsed_time(instr_time *starttime);
63 : static bool ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used);
64 : static void ExplainNode(PlanState *planstate, List *ancestors,
65 : const char *relationship, const char *plan_name,
66 : ExplainState *es);
67 : static void show_plan_tlist(PlanState *planstate, List *ancestors,
68 : ExplainState *es);
69 : static void show_expression(Node *node, const char *qlabel,
70 : PlanState *planstate, List *ancestors,
71 : bool useprefix, ExplainState *es);
72 : static void show_qual(List *qual, const char *qlabel,
73 : PlanState *planstate, List *ancestors,
74 : bool useprefix, ExplainState *es);
75 : static void show_scan_qual(List *qual, const char *qlabel,
76 : PlanState *planstate, List *ancestors,
77 : ExplainState *es);
78 : static void show_upper_qual(List *qual, const char *qlabel,
79 : PlanState *planstate, List *ancestors,
80 : ExplainState *es);
81 : static void show_sort_keys(SortState *sortstate, List *ancestors,
82 : ExplainState *es);
83 : static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
84 : ExplainState *es);
85 : static void show_agg_keys(AggState *astate, List *ancestors,
86 : ExplainState *es);
87 : static void show_grouping_sets(PlanState *planstate, Agg *agg,
88 : List *ancestors, ExplainState *es);
89 : static void show_grouping_set_keys(PlanState *planstate,
90 : Agg *aggnode, Sort *sortnode,
91 : List *context, bool useprefix,
92 : List *ancestors, ExplainState *es);
93 : static void show_group_keys(GroupState *gstate, List *ancestors,
94 : ExplainState *es);
95 : static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
96 : int nkeys, AttrNumber *keycols,
97 : Oid *sortOperators, Oid *collations, bool *nullsFirst,
98 : List *ancestors, ExplainState *es);
99 : static void show_sortorder_options(StringInfo buf, Node *sortexpr,
100 : Oid sortOperator, Oid collation, bool nullsFirst);
101 : static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
102 : List *ancestors, ExplainState *es);
103 : static void show_sort_info(SortState *sortstate, ExplainState *es);
104 : static void show_hash_info(HashState *hashstate, ExplainState *es);
105 : static void show_tidbitmap_info(BitmapHeapScanState *planstate,
106 : ExplainState *es);
107 : static void show_instrumentation_count(const char *qlabel, int which,
108 : PlanState *planstate, ExplainState *es);
109 : static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
110 : static const char *explain_get_index_name(Oid indexId);
111 : static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
112 : static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
113 : ExplainState *es);
114 : static void ExplainScanTarget(Scan *plan, ExplainState *es);
115 : static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
116 : static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
117 : static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
118 : ExplainState *es);
119 : static void ExplainMemberNodes(List *plans, PlanState **planstates,
120 : List *ancestors, ExplainState *es);
121 : static void ExplainSubPlans(List *plans, List *ancestors,
122 : const char *relationship, ExplainState *es);
123 : static void ExplainCustomChildren(CustomScanState *css,
124 : List *ancestors, ExplainState *es);
125 : static void ExplainProperty(const char *qlabel, const char *value,
126 : bool numeric, ExplainState *es);
127 : static void ExplainOpenGroup(const char *objtype, const char *labelname,
128 : bool labeled, ExplainState *es);
129 : static void ExplainCloseGroup(const char *objtype, const char *labelname,
130 : bool labeled, ExplainState *es);
131 : static void ExplainDummyGroup(const char *objtype, const char *labelname,
132 : ExplainState *es);
133 : static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
134 : static void ExplainJSONLineEnding(ExplainState *es);
135 : static void ExplainYAMLLineStarting(ExplainState *es);
136 : static void escape_yaml(StringInfo buf, const char *str);
137 :
138 :
139 :
140 : /*
141 : * ExplainQuery -
142 : * execute an EXPLAIN command
143 : */
144 : void
145 1123 : ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
146 : ParamListInfo params, QueryEnvironment *queryEnv,
147 : DestReceiver *dest)
148 : {
149 1123 : ExplainState *es = NewExplainState();
150 : TupOutputState *tstate;
151 : List *rewritten;
152 : ListCell *lc;
153 1123 : bool timing_set = false;
154 1123 : bool summary_set = false;
155 :
156 : /* Parse options list. */
157 1857 : foreach(lc, stmt->options)
158 : {
159 734 : DefElem *opt = (DefElem *) lfirst(lc);
160 :
161 734 : if (strcmp(opt->defname, "analyze") == 0)
162 7 : es->analyze = defGetBoolean(opt);
163 727 : else if (strcmp(opt->defname, "verbose") == 0)
164 88 : es->verbose = defGetBoolean(opt);
165 639 : else if (strcmp(opt->defname, "costs") == 0)
166 623 : es->costs = defGetBoolean(opt);
167 16 : else if (strcmp(opt->defname, "buffers") == 0)
168 0 : es->buffers = defGetBoolean(opt);
169 16 : else if (strcmp(opt->defname, "timing") == 0)
170 : {
171 9 : timing_set = true;
172 9 : es->timing = defGetBoolean(opt);
173 : }
174 7 : else if (strcmp(opt->defname, "summary") == 0)
175 : {
176 6 : summary_set = true;
177 6 : es->summary = defGetBoolean(opt);
178 : }
179 1 : else if (strcmp(opt->defname, "format") == 0)
180 : {
181 1 : char *p = defGetString(opt);
182 :
183 1 : if (strcmp(p, "text") == 0)
184 0 : es->format = EXPLAIN_FORMAT_TEXT;
185 1 : else if (strcmp(p, "xml") == 0)
186 0 : es->format = EXPLAIN_FORMAT_XML;
187 1 : else if (strcmp(p, "json") == 0)
188 1 : es->format = EXPLAIN_FORMAT_JSON;
189 0 : else if (strcmp(p, "yaml") == 0)
190 0 : es->format = EXPLAIN_FORMAT_YAML;
191 : else
192 0 : ereport(ERROR,
193 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
194 : errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
195 : opt->defname, p),
196 : parser_errposition(pstate, opt->location)));
197 : }
198 : else
199 0 : ereport(ERROR,
200 : (errcode(ERRCODE_SYNTAX_ERROR),
201 : errmsg("unrecognized EXPLAIN option \"%s\"",
202 : opt->defname),
203 : parser_errposition(pstate, opt->location)));
204 : }
205 :
206 1123 : if (es->buffers && !es->analyze)
207 0 : ereport(ERROR,
208 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
209 : errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
210 :
211 : /* if the timing was not set explicitly, set default value */
212 1123 : es->timing = (timing_set) ? es->timing : es->analyze;
213 :
214 : /* check that timing is used with EXPLAIN ANALYZE */
215 1123 : if (es->timing && !es->analyze)
216 0 : ereport(ERROR,
217 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
218 : errmsg("EXPLAIN option TIMING requires ANALYZE")));
219 :
220 : /* if the summary was not set explicitly, set default value */
221 1123 : es->summary = (summary_set) ? es->summary : es->analyze;
222 :
223 : /*
224 : * Parse analysis was done already, but we still have to run the rule
225 : * rewriter. We do not do AcquireRewriteLocks: we assume the query either
226 : * came straight from the parser, or suitable locks were acquired by
227 : * plancache.c.
228 : *
229 : * Because the rewriter and planner tend to scribble on the input, we make
230 : * a preliminary copy of the source querytree. This prevents problems in
231 : * the case that the EXPLAIN is in a portal or plpgsql function and is
232 : * executed repeatedly. (See also the same hack in DECLARE CURSOR and
233 : * PREPARE.) XXX FIXME someday.
234 : */
235 1123 : rewritten = QueryRewrite(castNode(Query, copyObject(stmt->query)));
236 :
237 : /* emit opening boilerplate */
238 1123 : ExplainBeginOutput(es);
239 :
240 1123 : if (rewritten == NIL)
241 : {
242 : /*
243 : * In the case of an INSTEAD NOTHING, tell at least that. But in
244 : * non-text format, the output is delimited, so this isn't necessary.
245 : */
246 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
247 0 : appendStringInfoString(es->str, "Query rewrites to nothing\n");
248 : }
249 : else
250 : {
251 : ListCell *l;
252 :
253 : /* Explain every plan */
254 2241 : foreach(l, rewritten)
255 : {
256 1125 : ExplainOneQuery(lfirst_node(Query, l),
257 : CURSOR_OPT_PARALLEL_OK, NULL, es,
258 : queryString, params, queryEnv);
259 :
260 : /* Separate plans with an appropriate separator */
261 1118 : if (lnext(l) != NULL)
262 2 : ExplainSeparatePlans(es);
263 : }
264 : }
265 :
266 : /* emit closing boilerplate */
267 1116 : ExplainEndOutput(es);
268 1116 : Assert(es->indent == 0);
269 :
270 : /* output tuples */
271 1116 : tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
272 1116 : if (es->format == EXPLAIN_FORMAT_TEXT)
273 1115 : do_text_output_multiline(tstate, es->str->data);
274 : else
275 1 : do_text_output_oneline(tstate, es->str->data);
276 1116 : end_tup_output(tstate);
277 :
278 1116 : pfree(es->str->data);
279 1116 : }
280 :
281 : /*
282 : * Create a new ExplainState struct initialized with default options.
283 : */
284 : ExplainState *
285 1123 : NewExplainState(void)
286 : {
287 1123 : ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
288 :
289 : /* Set default options (most fields can be left as zeroes). */
290 1123 : es->costs = true;
291 : /* Prepare output buffer. */
292 1123 : es->str = makeStringInfo();
293 :
294 1123 : return es;
295 : }
296 :
297 : /*
298 : * ExplainResultDesc -
299 : * construct the result tupledesc for an EXPLAIN
300 : */
301 : TupleDesc
302 2739 : ExplainResultDesc(ExplainStmt *stmt)
303 : {
304 : TupleDesc tupdesc;
305 : ListCell *lc;
306 2739 : Oid result_type = TEXTOID;
307 :
308 : /* Check for XML format option */
309 4211 : foreach(lc, stmt->options)
310 : {
311 1472 : DefElem *opt = (DefElem *) lfirst(lc);
312 :
313 1472 : if (strcmp(opt->defname, "format") == 0)
314 : {
315 2 : char *p = defGetString(opt);
316 :
317 2 : if (strcmp(p, "xml") == 0)
318 0 : result_type = XMLOID;
319 2 : else if (strcmp(p, "json") == 0)
320 2 : result_type = JSONOID;
321 : else
322 0 : result_type = TEXTOID;
323 : /* don't "break", as ExplainQuery will use the last value */
324 : }
325 : }
326 :
327 : /* Need a tuple descriptor representing a single TEXT or XML column */
328 2739 : tupdesc = CreateTemplateTupleDesc(1, false);
329 2739 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
330 : result_type, -1, 0);
331 2739 : return tupdesc;
332 : }
333 :
334 : /*
335 : * ExplainOneQuery -
336 : * print out the execution plan for one Query
337 : *
338 : * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
339 : */
340 : static void
341 1133 : ExplainOneQuery(Query *query, int cursorOptions,
342 : IntoClause *into, ExplainState *es,
343 : const char *queryString, ParamListInfo params,
344 : QueryEnvironment *queryEnv)
345 : {
346 : /* planner will not cope with utility statements */
347 1133 : if (query->commandType == CMD_UTILITY)
348 : {
349 27 : ExplainOneUtility(query->utilityStmt, into, es, queryString, params,
350 : queryEnv);
351 1153 : return;
352 : }
353 :
354 : /* if an advisor plugin is present, let it manage things */
355 1106 : if (ExplainOneQuery_hook)
356 0 : (*ExplainOneQuery_hook) (query, cursorOptions, into, es,
357 : queryString, params);
358 : else
359 : {
360 : PlannedStmt *plan;
361 : instr_time planstart,
362 : planduration;
363 :
364 1106 : INSTR_TIME_SET_CURRENT(planstart);
365 :
366 : /* plan the query */
367 1106 : plan = pg_plan_query(query, cursorOptions, params);
368 :
369 1102 : INSTR_TIME_SET_CURRENT(planduration);
370 1102 : INSTR_TIME_SUBTRACT(planduration, planstart);
371 :
372 : /* run it (if needed) and produce output */
373 1102 : ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
374 : &planduration);
375 : }
376 : }
377 :
378 : /*
379 : * ExplainOneUtility -
380 : * print out the execution plan for one utility statement
381 : * (In general, utility statements don't have plans, but there are some
382 : * we treat as special cases)
383 : *
384 : * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
385 : *
386 : * This is exported because it's called back from prepare.c in the
387 : * EXPLAIN EXECUTE case.
388 : */
389 : void
390 27 : ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
391 : const char *queryString, ParamListInfo params,
392 : QueryEnvironment *queryEnv)
393 : {
394 27 : if (utilityStmt == NULL)
395 27 : return;
396 :
397 27 : if (IsA(utilityStmt, CreateTableAsStmt))
398 : {
399 : /*
400 : * We have to rewrite the contained SELECT and then pass it back to
401 : * ExplainOneQuery. It's probably not really necessary to copy the
402 : * contained parsetree another time, but let's be safe.
403 : *
404 : * Like ExecCreateTableAs, disallow parallelism in the plan.
405 : */
406 4 : CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
407 : List *rewritten;
408 :
409 4 : rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
410 4 : Assert(list_length(rewritten) == 1);
411 4 : ExplainOneQuery(linitial_node(Query, rewritten),
412 : 0, ctas->into, es,
413 : queryString, params, queryEnv);
414 : }
415 23 : else if (IsA(utilityStmt, DeclareCursorStmt))
416 : {
417 : /*
418 : * Likewise for DECLARE CURSOR.
419 : *
420 : * Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll
421 : * actually run the query. This is different from pre-8.3 behavior
422 : * but seems more useful than not running the query. No cursor will
423 : * be created, however.
424 : */
425 4 : DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
426 : List *rewritten;
427 :
428 4 : rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));
429 4 : Assert(list_length(rewritten) == 1);
430 4 : ExplainOneQuery(linitial_node(Query, rewritten),
431 : dcs->options, NULL, es,
432 : queryString, params, queryEnv);
433 : }
434 19 : else if (IsA(utilityStmt, ExecuteStmt))
435 19 : ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
436 : queryString, params, queryEnv);
437 0 : else if (IsA(utilityStmt, NotifyStmt))
438 : {
439 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
440 0 : appendStringInfoString(es->str, "NOTIFY\n");
441 : else
442 0 : ExplainDummyGroup("Notify", NULL, es);
443 : }
444 : else
445 : {
446 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
447 0 : appendStringInfoString(es->str,
448 : "Utility statements have no plan structure\n");
449 : else
450 0 : ExplainDummyGroup("Utility Statement", NULL, es);
451 : }
452 : }
453 :
454 : /*
455 : * ExplainOnePlan -
456 : * given a planned query, execute it if needed, and then print
457 : * EXPLAIN output
458 : *
459 : * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
460 : * in which case executing the query should result in creating that table.
461 : *
462 : * This is exported because it's called back from prepare.c in the
463 : * EXPLAIN EXECUTE case, and because an index advisor plugin would need
464 : * to call it.
465 : */
466 : void
467 1121 : ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
468 : const char *queryString, ParamListInfo params,
469 : QueryEnvironment *queryEnv, const instr_time *planduration)
470 : {
471 : DestReceiver *dest;
472 : QueryDesc *queryDesc;
473 : instr_time starttime;
474 1121 : double totaltime = 0;
475 : int eflags;
476 1121 : int instrument_option = 0;
477 :
478 1121 : Assert(plannedstmt->commandType != CMD_UTILITY);
479 :
480 1121 : if (es->analyze && es->timing)
481 1 : instrument_option |= INSTRUMENT_TIMER;
482 1120 : else if (es->analyze)
483 6 : instrument_option |= INSTRUMENT_ROWS;
484 :
485 1121 : if (es->buffers)
486 0 : instrument_option |= INSTRUMENT_BUFFERS;
487 :
488 : /*
489 : * We always collect timing for the entire statement, even when node-level
490 : * timing is off, so we don't look at es->timing here. (We could skip
491 : * this if !es->summary, but it's hardly worth the complication.)
492 : */
493 1121 : INSTR_TIME_SET_CURRENT(starttime);
494 :
495 : /*
496 : * Use a snapshot with an updated command ID to ensure this query sees
497 : * results of any previously executed queries.
498 : */
499 1121 : PushCopiedSnapshot(GetActiveSnapshot());
500 1121 : UpdateActiveSnapshotCommandId();
501 :
502 : /*
503 : * Normally we discard the query's output, but if explaining CREATE TABLE
504 : * AS, we'd better use the appropriate tuple receiver.
505 : */
506 1121 : if (into)
507 4 : dest = CreateIntoRelDestReceiver(into);
508 : else
509 1117 : dest = None_Receiver;
510 :
511 : /* Create a QueryDesc for the query */
512 1121 : queryDesc = CreateQueryDesc(plannedstmt, queryString,
513 : GetActiveSnapshot(), InvalidSnapshot,
514 : dest, params, queryEnv, instrument_option);
515 :
516 : /* Select execution options */
517 1121 : if (es->analyze)
518 7 : eflags = 0; /* default run-to-completion flags */
519 : else
520 1114 : eflags = EXEC_FLAG_EXPLAIN_ONLY;
521 1121 : if (into)
522 4 : eflags |= GetIntoRelEFlags(into);
523 :
524 : /* call ExecutorStart to prepare the plan for execution */
525 1121 : ExecutorStart(queryDesc, eflags);
526 :
527 : /* Execute the plan for statistics if asked for */
528 1119 : if (es->analyze)
529 : {
530 : ScanDirection dir;
531 :
532 : /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
533 7 : if (into && into->skipData)
534 0 : dir = NoMovementScanDirection;
535 : else
536 7 : dir = ForwardScanDirection;
537 :
538 : /* run the plan */
539 7 : ExecutorRun(queryDesc, dir, 0L, true);
540 :
541 : /* run cleanup too */
542 6 : ExecutorFinish(queryDesc);
543 :
544 : /* We can't run ExecutorEnd 'till we're done printing the stats... */
545 6 : totaltime += elapsed_time(&starttime);
546 : }
547 :
548 1118 : ExplainOpenGroup("Query", NULL, true, es);
549 :
550 : /* Create textual dump of plan tree */
551 1118 : ExplainPrintPlan(es, queryDesc);
552 :
553 1118 : if (es->summary && planduration)
554 : {
555 1 : double plantime = INSTR_TIME_GET_DOUBLE(*planduration);
556 :
557 1 : if (es->format == EXPLAIN_FORMAT_TEXT)
558 1 : appendStringInfo(es->str, "Planning time: %.3f ms\n",
559 : 1000.0 * plantime);
560 : else
561 0 : ExplainPropertyFloat("Planning Time", 1000.0 * plantime, 3, es);
562 : }
563 :
564 : /* Print info about runtime of triggers */
565 1118 : if (es->analyze)
566 6 : ExplainPrintTriggers(es, queryDesc);
567 :
568 : /*
569 : * Close down the query and free resources. Include time for this in the
570 : * total execution time (although it should be pretty minimal).
571 : */
572 1118 : INSTR_TIME_SET_CURRENT(starttime);
573 :
574 1118 : ExecutorEnd(queryDesc);
575 :
576 1118 : FreeQueryDesc(queryDesc);
577 :
578 1118 : PopActiveSnapshot();
579 :
580 : /* We need a CCI just in case query expanded to multiple plans */
581 1118 : if (es->analyze)
582 6 : CommandCounterIncrement();
583 :
584 1118 : totaltime += elapsed_time(&starttime);
585 :
586 : /*
587 : * We only report execution time if we actually ran the query (that is,
588 : * the user specified ANALYZE), and if summary reporting is enabled (the
589 : * user can set SUMMARY OFF to not have the timing information included in
590 : * the output). By default, ANALYZE sets SUMMARY to true.
591 : */
592 1118 : if (es->summary && es->analyze)
593 : {
594 1 : if (es->format == EXPLAIN_FORMAT_TEXT)
595 1 : appendStringInfo(es->str, "Execution time: %.3f ms\n",
596 : 1000.0 * totaltime);
597 : else
598 0 : ExplainPropertyFloat("Execution Time", 1000.0 * totaltime,
599 : 3, es);
600 : }
601 :
602 1118 : ExplainCloseGroup("Query", NULL, true, es);
603 1118 : }
604 :
605 : /*
606 : * ExplainPrintPlan -
607 : * convert a QueryDesc's plan tree to text and append it to es->str
608 : *
609 : * The caller should have set up the options fields of *es, as well as
610 : * initializing the output buffer es->str. Also, output formatting state
611 : * such as the indent level is assumed valid. Plan-tree-specific fields
612 : * in *es are initialized here.
613 : *
614 : * NB: will not work on utility statements
615 : */
616 : void
617 1118 : ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
618 : {
619 1118 : Bitmapset *rels_used = NULL;
620 : PlanState *ps;
621 :
622 : /* Set up ExplainState fields associated with this plan tree */
623 1118 : Assert(queryDesc->plannedstmt != NULL);
624 1118 : es->pstmt = queryDesc->plannedstmt;
625 1118 : es->rtable = queryDesc->plannedstmt->rtable;
626 1118 : ExplainPreScanNode(queryDesc->planstate, &rels_used);
627 1118 : es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
628 1118 : es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,
629 : es->rtable_names);
630 1118 : es->printed_subplans = NULL;
631 :
632 : /*
633 : * Sometimes we mark a Gather node as "invisible", which means that it's
634 : * not displayed in EXPLAIN output. The purpose of this is to allow
635 : * running regression tests with force_parallel_mode=regress to get the
636 : * same results as running the same tests with force_parallel_mode=off.
637 : */
638 1118 : ps = queryDesc->planstate;
639 1118 : if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
640 0 : ps = outerPlanState(ps);
641 1118 : ExplainNode(ps, NIL, NULL, NULL, es);
642 1118 : }
643 :
644 : /*
645 : * ExplainPrintTriggers -
646 : * convert a QueryDesc's trigger statistics to text and append it to
647 : * es->str
648 : *
649 : * The caller should have set up the options fields of *es, as well as
650 : * initializing the output buffer es->str. Other fields in *es are
651 : * initialized here.
652 : */
653 : void
654 6 : ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
655 : {
656 : ResultRelInfo *rInfo;
657 : bool show_relname;
658 6 : int numrels = queryDesc->estate->es_num_result_relations;
659 6 : int numrootrels = queryDesc->estate->es_num_root_result_relations;
660 6 : List *leafrels = queryDesc->estate->es_leaf_result_relations;
661 6 : List *targrels = queryDesc->estate->es_trig_target_relations;
662 : int nr;
663 : ListCell *l;
664 :
665 6 : ExplainOpenGroup("Triggers", "Triggers", false, es);
666 :
667 12 : show_relname = (numrels > 1 || numrootrels > 0 ||
668 12 : leafrels != NIL || targrels != NIL);
669 6 : rInfo = queryDesc->estate->es_result_relations;
670 8 : for (nr = 0; nr < numrels; rInfo++, nr++)
671 2 : report_triggers(rInfo, show_relname, es);
672 :
673 6 : rInfo = queryDesc->estate->es_root_result_relations;
674 6 : for (nr = 0; nr < numrootrels; rInfo++, nr++)
675 0 : report_triggers(rInfo, show_relname, es);
676 :
677 6 : foreach(l, leafrels)
678 : {
679 0 : rInfo = (ResultRelInfo *) lfirst(l);
680 0 : report_triggers(rInfo, show_relname, es);
681 : }
682 :
683 6 : foreach(l, targrels)
684 : {
685 0 : rInfo = (ResultRelInfo *) lfirst(l);
686 0 : report_triggers(rInfo, show_relname, es);
687 : }
688 :
689 6 : ExplainCloseGroup("Triggers", "Triggers", false, es);
690 6 : }
691 :
692 : /*
693 : * ExplainQueryText -
694 : * add a "Query Text" node that contains the actual text of the query
695 : *
696 : * The caller should have set up the options fields of *es, as well as
697 : * initializing the output buffer es->str.
698 : *
699 : */
700 : void
701 0 : ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
702 : {
703 0 : if (queryDesc->sourceText)
704 0 : ExplainPropertyText("Query Text", queryDesc->sourceText, es);
705 0 : }
706 :
707 : /*
708 : * report_triggers -
709 : * report execution stats for a single relation's triggers
710 : */
711 : static void
712 2 : report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
713 : {
714 : int nt;
715 :
716 2 : if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
717 4 : return;
718 0 : for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
719 : {
720 0 : Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
721 0 : Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
722 : char *relname;
723 0 : char *conname = NULL;
724 :
725 : /* Must clean up instrumentation state */
726 0 : InstrEndLoop(instr);
727 :
728 : /*
729 : * We ignore triggers that were never invoked; they likely aren't
730 : * relevant to the current query type.
731 : */
732 0 : if (instr->ntuples == 0)
733 0 : continue;
734 :
735 0 : ExplainOpenGroup("Trigger", NULL, true, es);
736 :
737 0 : relname = RelationGetRelationName(rInfo->ri_RelationDesc);
738 0 : if (OidIsValid(trig->tgconstraint))
739 0 : conname = get_constraint_name(trig->tgconstraint);
740 :
741 : /*
742 : * In text format, we avoid printing both the trigger name and the
743 : * constraint name unless VERBOSE is specified. In non-text formats
744 : * we just print everything.
745 : */
746 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
747 : {
748 0 : if (es->verbose || conname == NULL)
749 0 : appendStringInfo(es->str, "Trigger %s", trig->tgname);
750 : else
751 0 : appendStringInfoString(es->str, "Trigger");
752 0 : if (conname)
753 0 : appendStringInfo(es->str, " for constraint %s", conname);
754 0 : if (show_relname)
755 0 : appendStringInfo(es->str, " on %s", relname);
756 0 : if (es->timing)
757 0 : appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
758 0 : 1000.0 * instr->total, instr->ntuples);
759 : else
760 0 : appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples);
761 : }
762 : else
763 : {
764 0 : ExplainPropertyText("Trigger Name", trig->tgname, es);
765 0 : if (conname)
766 0 : ExplainPropertyText("Constraint Name", conname, es);
767 0 : ExplainPropertyText("Relation", relname, es);
768 0 : if (es->timing)
769 0 : ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
770 0 : ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
771 : }
772 :
773 0 : if (conname)
774 0 : pfree(conname);
775 :
776 0 : ExplainCloseGroup("Trigger", NULL, true, es);
777 : }
778 : }
779 :
780 : /* Compute elapsed time in seconds since given timestamp */
781 : static double
782 1124 : elapsed_time(instr_time *starttime)
783 : {
784 : instr_time endtime;
785 :
786 1124 : INSTR_TIME_SET_CURRENT(endtime);
787 1124 : INSTR_TIME_SUBTRACT(endtime, *starttime);
788 1124 : return INSTR_TIME_GET_DOUBLE(endtime);
789 : }
790 :
791 : /*
792 : * ExplainPreScanNode -
793 : * Prescan the planstate tree to identify which RTEs are referenced
794 : *
795 : * Adds the relid of each referenced RTE to *rels_used. The result controls
796 : * which RTEs are assigned aliases by select_rtable_names_for_explain.
797 : * This ensures that we don't confusingly assign un-suffixed aliases to RTEs
798 : * that never appear in the EXPLAIN output (such as inheritance parents).
799 : */
800 : static bool
801 3420 : ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
802 : {
803 3420 : Plan *plan = planstate->plan;
804 :
805 3420 : switch (nodeTag(plan))
806 : {
807 : case T_SeqScan:
808 : case T_SampleScan:
809 : case T_IndexScan:
810 : case T_IndexOnlyScan:
811 : case T_BitmapHeapScan:
812 : case T_TidScan:
813 : case T_SubqueryScan:
814 : case T_FunctionScan:
815 : case T_TableFuncScan:
816 : case T_ValuesScan:
817 : case T_CteScan:
818 : case T_NamedTuplestoreScan:
819 : case T_WorkTableScan:
820 1598 : *rels_used = bms_add_member(*rels_used,
821 1598 : ((Scan *) plan)->scanrelid);
822 1598 : break;
823 : case T_ForeignScan:
824 0 : *rels_used = bms_add_members(*rels_used,
825 0 : ((ForeignScan *) plan)->fs_relids);
826 0 : break;
827 : case T_CustomScan:
828 0 : *rels_used = bms_add_members(*rels_used,
829 0 : ((CustomScan *) plan)->custom_relids);
830 0 : break;
831 : case T_ModifyTable:
832 61 : *rels_used = bms_add_member(*rels_used,
833 61 : ((ModifyTable *) plan)->nominalRelation);
834 61 : if (((ModifyTable *) plan)->exclRelRTI)
835 11 : *rels_used = bms_add_member(*rels_used,
836 11 : ((ModifyTable *) plan)->exclRelRTI);
837 61 : break;
838 : default:
839 1761 : break;
840 : }
841 :
842 3420 : return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
843 : }
844 :
845 : /*
846 : * ExplainNode -
847 : * Appends a description of a plan tree to es->str
848 : *
849 : * planstate points to the executor state node for the current plan node.
850 : * We need to work from a PlanState node, not just a Plan node, in order to
851 : * get at the instrumentation data (if any) as well as the list of subplans.
852 : *
853 : * ancestors is a list of parent PlanState nodes, most-closely-nested first.
854 : * These are needed in order to interpret PARAM_EXEC Params.
855 : *
856 : * relationship describes the relationship of this plan node to its parent
857 : * (eg, "Outer", "Inner"); it can be null at top level. plan_name is an
858 : * optional name to be attached to the node.
859 : *
860 : * In text format, es->indent is controlled in this function since we only
861 : * want it to change at plan-node boundaries. In non-text formats, es->indent
862 : * corresponds to the nesting depth of logical output groups, and therefore
863 : * is controlled by ExplainOpenGroup/ExplainCloseGroup.
864 : */
865 : static void
866 3384 : ExplainNode(PlanState *planstate, List *ancestors,
867 : const char *relationship, const char *plan_name,
868 : ExplainState *es)
869 : {
870 3384 : Plan *plan = planstate->plan;
871 : const char *pname; /* node type name for text output */
872 : const char *sname; /* node type name for non-text output */
873 3384 : const char *strategy = NULL;
874 3384 : const char *partialmode = NULL;
875 3384 : const char *operation = NULL;
876 3384 : const char *custom_name = NULL;
877 3384 : int save_indent = es->indent;
878 : bool haschildren;
879 :
880 3384 : switch (nodeTag(plan))
881 : {
882 : case T_Result:
883 108 : pname = sname = "Result";
884 108 : break;
885 : case T_ProjectSet:
886 17 : pname = sname = "ProjectSet";
887 17 : break;
888 : case T_ModifyTable:
889 61 : sname = "ModifyTable";
890 61 : switch (((ModifyTable *) plan)->operation)
891 : {
892 : case CMD_INSERT:
893 24 : pname = operation = "Insert";
894 24 : break;
895 : case CMD_UPDATE:
896 25 : pname = operation = "Update";
897 25 : break;
898 : case CMD_DELETE:
899 12 : pname = operation = "Delete";
900 12 : break;
901 : default:
902 0 : pname = "???";
903 0 : break;
904 : }
905 61 : break;
906 : case T_Append:
907 80 : pname = sname = "Append";
908 80 : break;
909 : case T_MergeAppend:
910 22 : pname = sname = "Merge Append";
911 22 : break;
912 : case T_RecursiveUnion:
913 0 : pname = sname = "Recursive Union";
914 0 : break;
915 : case T_BitmapAnd:
916 1 : pname = sname = "BitmapAnd";
917 1 : break;
918 : case T_BitmapOr:
919 10 : pname = sname = "BitmapOr";
920 10 : break;
921 : case T_NestLoop:
922 167 : pname = sname = "Nested Loop";
923 167 : break;
924 : case T_MergeJoin:
925 14 : pname = "Merge"; /* "Join" gets added by jointype switch */
926 14 : sname = "Merge Join";
927 14 : break;
928 : case T_HashJoin:
929 53 : pname = "Hash"; /* "Join" gets added by jointype switch */
930 53 : sname = "Hash Join";
931 53 : break;
932 : case T_SeqScan:
933 813 : pname = sname = "Seq Scan";
934 813 : break;
935 : case T_SampleScan:
936 9 : pname = sname = "Sample Scan";
937 9 : break;
938 : case T_Gather:
939 14 : pname = sname = "Gather";
940 14 : break;
941 : case T_GatherMerge:
942 4 : pname = sname = "Gather Merge";
943 4 : break;
944 : case T_IndexScan:
945 164 : pname = sname = "Index Scan";
946 164 : break;
947 : case T_IndexOnlyScan:
948 160 : pname = sname = "Index Only Scan";
949 160 : break;
950 : case T_BitmapIndexScan:
951 327 : pname = sname = "Bitmap Index Scan";
952 327 : break;
953 : case T_BitmapHeapScan:
954 314 : pname = sname = "Bitmap Heap Scan";
955 314 : break;
956 : case T_TidScan:
957 7 : pname = sname = "Tid Scan";
958 7 : break;
959 : case T_SubqueryScan:
960 36 : pname = sname = "Subquery Scan";
961 36 : break;
962 : case T_FunctionScan:
963 23 : pname = sname = "Function Scan";
964 23 : break;
965 : case T_TableFuncScan:
966 5 : pname = sname = "Table Function Scan";
967 5 : break;
968 : case T_ValuesScan:
969 24 : pname = sname = "Values Scan";
970 24 : break;
971 : case T_CteScan:
972 15 : pname = sname = "CTE Scan";
973 15 : break;
974 : case T_NamedTuplestoreScan:
975 4 : pname = sname = "Named Tuplestore Scan";
976 4 : break;
977 : case T_WorkTableScan:
978 0 : pname = sname = "WorkTable Scan";
979 0 : break;
980 : case T_ForeignScan:
981 0 : sname = "Foreign Scan";
982 0 : switch (((ForeignScan *) plan)->operation)
983 : {
984 : case CMD_SELECT:
985 0 : pname = "Foreign Scan";
986 0 : operation = "Select";
987 0 : break;
988 : case CMD_INSERT:
989 0 : pname = "Foreign Insert";
990 0 : operation = "Insert";
991 0 : break;
992 : case CMD_UPDATE:
993 0 : pname = "Foreign Update";
994 0 : operation = "Update";
995 0 : break;
996 : case CMD_DELETE:
997 0 : pname = "Foreign Delete";
998 0 : operation = "Delete";
999 0 : break;
1000 : default:
1001 0 : pname = "???";
1002 0 : break;
1003 : }
1004 0 : break;
1005 : case T_CustomScan:
1006 0 : sname = "Custom Scan";
1007 0 : custom_name = ((CustomScan *) plan)->methods->CustomName;
1008 0 : if (custom_name)
1009 0 : pname = psprintf("Custom Scan (%s)", custom_name);
1010 : else
1011 0 : pname = sname;
1012 0 : break;
1013 : case T_Material:
1014 35 : pname = sname = "Materialize";
1015 35 : break;
1016 : case T_Sort:
1017 98 : pname = sname = "Sort";
1018 98 : break;
1019 : case T_Group:
1020 2 : pname = sname = "Group";
1021 2 : break;
1022 : case T_Agg:
1023 : {
1024 674 : Agg *agg = (Agg *) plan;
1025 :
1026 674 : sname = "Aggregate";
1027 674 : switch (agg->aggstrategy)
1028 : {
1029 : case AGG_PLAIN:
1030 609 : pname = "Aggregate";
1031 609 : strategy = "Plain";
1032 609 : break;
1033 : case AGG_SORTED:
1034 17 : pname = "GroupAggregate";
1035 17 : strategy = "Sorted";
1036 17 : break;
1037 : case AGG_HASHED:
1038 39 : pname = "HashAggregate";
1039 39 : strategy = "Hashed";
1040 39 : break;
1041 : case AGG_MIXED:
1042 9 : pname = "MixedAggregate";
1043 9 : strategy = "Mixed";
1044 9 : break;
1045 : default:
1046 0 : pname = "Aggregate ???";
1047 0 : strategy = "???";
1048 0 : break;
1049 : }
1050 :
1051 674 : if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit))
1052 : {
1053 11 : partialmode = "Partial";
1054 11 : pname = psprintf("%s %s", partialmode, pname);
1055 : }
1056 663 : else if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
1057 : {
1058 11 : partialmode = "Finalize";
1059 11 : pname = psprintf("%s %s", partialmode, pname);
1060 : }
1061 : else
1062 652 : partialmode = "Simple";
1063 : }
1064 674 : break;
1065 : case T_WindowAgg:
1066 6 : pname = sname = "WindowAgg";
1067 6 : break;
1068 : case T_Unique:
1069 5 : pname = sname = "Unique";
1070 5 : break;
1071 : case T_SetOp:
1072 4 : sname = "SetOp";
1073 4 : switch (((SetOp *) plan)->strategy)
1074 : {
1075 : case SETOP_SORTED:
1076 2 : pname = "SetOp";
1077 2 : strategy = "Sorted";
1078 2 : break;
1079 : case SETOP_HASHED:
1080 2 : pname = "HashSetOp";
1081 2 : strategy = "Hashed";
1082 2 : break;
1083 : default:
1084 0 : pname = "SetOp ???";
1085 0 : strategy = "???";
1086 0 : break;
1087 : }
1088 4 : break;
1089 : case T_LockRows:
1090 3 : pname = sname = "LockRows";
1091 3 : break;
1092 : case T_Limit:
1093 52 : pname = sname = "Limit";
1094 52 : break;
1095 : case T_Hash:
1096 53 : pname = sname = "Hash";
1097 53 : break;
1098 : default:
1099 0 : pname = sname = "???";
1100 0 : break;
1101 : }
1102 :
1103 3384 : ExplainOpenGroup("Plan",
1104 : relationship ? NULL : "Plan",
1105 : true, es);
1106 :
1107 3384 : if (es->format == EXPLAIN_FORMAT_TEXT)
1108 : {
1109 3382 : if (plan_name)
1110 : {
1111 83 : appendStringInfoSpaces(es->str, es->indent * 2);
1112 83 : appendStringInfo(es->str, "%s\n", plan_name);
1113 83 : es->indent++;
1114 : }
1115 3382 : if (es->indent)
1116 : {
1117 2265 : appendStringInfoSpaces(es->str, es->indent * 2);
1118 2265 : appendStringInfoString(es->str, "-> ");
1119 2265 : es->indent += 2;
1120 : }
1121 3382 : if (plan->parallel_aware)
1122 22 : appendStringInfoString(es->str, "Parallel ");
1123 3382 : appendStringInfoString(es->str, pname);
1124 3382 : es->indent++;
1125 : }
1126 : else
1127 : {
1128 2 : ExplainPropertyText("Node Type", sname, es);
1129 2 : if (strategy)
1130 0 : ExplainPropertyText("Strategy", strategy, es);
1131 2 : if (partialmode)
1132 0 : ExplainPropertyText("Partial Mode", partialmode, es);
1133 2 : if (operation)
1134 1 : ExplainPropertyText("Operation", operation, es);
1135 2 : if (relationship)
1136 1 : ExplainPropertyText("Parent Relationship", relationship, es);
1137 2 : if (plan_name)
1138 0 : ExplainPropertyText("Subplan Name", plan_name, es);
1139 2 : if (custom_name)
1140 0 : ExplainPropertyText("Custom Plan Provider", custom_name, es);
1141 2 : ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es);
1142 : }
1143 :
1144 3384 : switch (nodeTag(plan))
1145 : {
1146 : case T_SeqScan:
1147 : case T_SampleScan:
1148 : case T_BitmapHeapScan:
1149 : case T_TidScan:
1150 : case T_SubqueryScan:
1151 : case T_FunctionScan:
1152 : case T_TableFuncScan:
1153 : case T_ValuesScan:
1154 : case T_CteScan:
1155 : case T_WorkTableScan:
1156 1246 : ExplainScanTarget((Scan *) plan, es);
1157 1246 : break;
1158 : case T_ForeignScan:
1159 : case T_CustomScan:
1160 0 : if (((Scan *) plan)->scanrelid > 0)
1161 0 : ExplainScanTarget((Scan *) plan, es);
1162 0 : break;
1163 : case T_IndexScan:
1164 : {
1165 164 : IndexScan *indexscan = (IndexScan *) plan;
1166 :
1167 164 : ExplainIndexScanDetails(indexscan->indexid,
1168 : indexscan->indexorderdir,
1169 : es);
1170 164 : ExplainScanTarget((Scan *) indexscan, es);
1171 : }
1172 164 : break;
1173 : case T_IndexOnlyScan:
1174 : {
1175 160 : IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;
1176 :
1177 160 : ExplainIndexScanDetails(indexonlyscan->indexid,
1178 : indexonlyscan->indexorderdir,
1179 : es);
1180 160 : ExplainScanTarget((Scan *) indexonlyscan, es);
1181 : }
1182 160 : break;
1183 : case T_BitmapIndexScan:
1184 : {
1185 327 : BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
1186 327 : const char *indexname =
1187 327 : explain_get_index_name(bitmapindexscan->indexid);
1188 :
1189 327 : if (es->format == EXPLAIN_FORMAT_TEXT)
1190 327 : appendStringInfo(es->str, " on %s", indexname);
1191 : else
1192 0 : ExplainPropertyText("Index Name", indexname, es);
1193 : }
1194 327 : break;
1195 : case T_ModifyTable:
1196 61 : ExplainModifyTarget((ModifyTable *) plan, es);
1197 61 : break;
1198 : case T_NestLoop:
1199 : case T_MergeJoin:
1200 : case T_HashJoin:
1201 : {
1202 : const char *jointype;
1203 :
1204 234 : switch (((Join *) plan)->jointype)
1205 : {
1206 : case JOIN_INNER:
1207 161 : jointype = "Inner";
1208 161 : break;
1209 : case JOIN_LEFT:
1210 50 : jointype = "Left";
1211 50 : break;
1212 : case JOIN_FULL:
1213 5 : jointype = "Full";
1214 5 : break;
1215 : case JOIN_RIGHT:
1216 12 : jointype = "Right";
1217 12 : break;
1218 : case JOIN_SEMI:
1219 4 : jointype = "Semi";
1220 4 : break;
1221 : case JOIN_ANTI:
1222 2 : jointype = "Anti";
1223 2 : break;
1224 : default:
1225 0 : jointype = "???";
1226 0 : break;
1227 : }
1228 234 : if (es->format == EXPLAIN_FORMAT_TEXT)
1229 : {
1230 : /*
1231 : * For historical reasons, the join type is interpolated
1232 : * into the node type name...
1233 : */
1234 234 : if (((Join *) plan)->jointype != JOIN_INNER)
1235 73 : appendStringInfo(es->str, " %s Join", jointype);
1236 161 : else if (!IsA(plan, NestLoop))
1237 33 : appendStringInfoString(es->str, " Join");
1238 : }
1239 : else
1240 0 : ExplainPropertyText("Join Type", jointype, es);
1241 : }
1242 234 : break;
1243 : case T_SetOp:
1244 : {
1245 : const char *setopcmd;
1246 :
1247 4 : switch (((SetOp *) plan)->cmd)
1248 : {
1249 : case SETOPCMD_INTERSECT:
1250 2 : setopcmd = "Intersect";
1251 2 : break;
1252 : case SETOPCMD_INTERSECT_ALL:
1253 0 : setopcmd = "Intersect All";
1254 0 : break;
1255 : case SETOPCMD_EXCEPT:
1256 2 : setopcmd = "Except";
1257 2 : break;
1258 : case SETOPCMD_EXCEPT_ALL:
1259 0 : setopcmd = "Except All";
1260 0 : break;
1261 : default:
1262 0 : setopcmd = "???";
1263 0 : break;
1264 : }
1265 4 : if (es->format == EXPLAIN_FORMAT_TEXT)
1266 4 : appendStringInfo(es->str, " %s", setopcmd);
1267 : else
1268 0 : ExplainPropertyText("Command", setopcmd, es);
1269 : }
1270 4 : break;
1271 : default:
1272 1188 : break;
1273 : }
1274 :
1275 3384 : if (es->costs)
1276 : {
1277 1247 : if (es->format == EXPLAIN_FORMAT_TEXT)
1278 : {
1279 1247 : appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
1280 : plan->startup_cost, plan->total_cost,
1281 : plan->plan_rows, plan->plan_width);
1282 : }
1283 : else
1284 : {
1285 0 : ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es);
1286 0 : ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
1287 0 : ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
1288 0 : ExplainPropertyInteger("Plan Width", plan->plan_width, es);
1289 : }
1290 : }
1291 :
1292 : /*
1293 : * We have to forcibly clean up the instrumentation state because we
1294 : * haven't done ExecutorEnd yet. This is pretty grotty ...
1295 : *
1296 : * Note: contrib/auto_explain could cause instrumentation to be set up
1297 : * even though we didn't ask for it here. Be careful not to print any
1298 : * instrumentation results the user didn't ask for. But we do the
1299 : * InstrEndLoop call anyway, if possible, to reduce the number of cases
1300 : * auto_explain has to contend with.
1301 : */
1302 3384 : if (planstate->instrument)
1303 12 : InstrEndLoop(planstate->instrument);
1304 :
1305 3396 : if (es->analyze &&
1306 24 : planstate->instrument && planstate->instrument->nloops > 0)
1307 12 : {
1308 12 : double nloops = planstate->instrument->nloops;
1309 12 : double startup_sec = 1000.0 * planstate->instrument->startup / nloops;
1310 12 : double total_sec = 1000.0 * planstate->instrument->total / nloops;
1311 12 : double rows = planstate->instrument->ntuples / nloops;
1312 :
1313 12 : if (es->format == EXPLAIN_FORMAT_TEXT)
1314 : {
1315 12 : if (es->timing)
1316 1 : appendStringInfo(es->str,
1317 : " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
1318 : startup_sec, total_sec, rows, nloops);
1319 : else
1320 11 : appendStringInfo(es->str,
1321 : " (actual rows=%.0f loops=%.0f)",
1322 : rows, nloops);
1323 : }
1324 : else
1325 : {
1326 0 : if (es->timing)
1327 : {
1328 0 : ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
1329 0 : ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
1330 : }
1331 0 : ExplainPropertyFloat("Actual Rows", rows, 0, es);
1332 0 : ExplainPropertyFloat("Actual Loops", nloops, 0, es);
1333 : }
1334 : }
1335 3372 : else if (es->analyze)
1336 : {
1337 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
1338 0 : appendStringInfoString(es->str, " (never executed)");
1339 : else
1340 : {
1341 0 : if (es->timing)
1342 : {
1343 0 : ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
1344 0 : ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
1345 : }
1346 0 : ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
1347 0 : ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
1348 : }
1349 : }
1350 :
1351 : /* in text format, first line ends here */
1352 3384 : if (es->format == EXPLAIN_FORMAT_TEXT)
1353 3382 : appendStringInfoChar(es->str, '\n');
1354 :
1355 : /* target list */
1356 3384 : if (es->verbose)
1357 435 : show_plan_tlist(planstate, ancestors, es);
1358 :
1359 : /* unique join */
1360 3384 : switch (nodeTag(plan))
1361 : {
1362 : case T_NestLoop:
1363 : case T_MergeJoin:
1364 : case T_HashJoin:
1365 : /* try not to be too chatty about this in text mode */
1366 468 : if (es->format != EXPLAIN_FORMAT_TEXT ||
1367 323 : (es->verbose && ((Join *) plan)->inner_unique))
1368 9 : ExplainPropertyBool("Inner Unique",
1369 9 : ((Join *) plan)->inner_unique,
1370 : es);
1371 234 : break;
1372 : default:
1373 3150 : break;
1374 : }
1375 :
1376 : /* quals, sort keys, etc */
1377 3384 : switch (nodeTag(plan))
1378 : {
1379 : case T_IndexScan:
1380 164 : show_scan_qual(((IndexScan *) plan)->indexqualorig,
1381 : "Index Cond", planstate, ancestors, es);
1382 164 : if (((IndexScan *) plan)->indexqualorig)
1383 135 : show_instrumentation_count("Rows Removed by Index Recheck", 2,
1384 : planstate, es);
1385 164 : show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
1386 : "Order By", planstate, ancestors, es);
1387 164 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1388 164 : if (plan->qual)
1389 33 : show_instrumentation_count("Rows Removed by Filter", 1,
1390 : planstate, es);
1391 164 : break;
1392 : case T_IndexOnlyScan:
1393 160 : show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
1394 : "Index Cond", planstate, ancestors, es);
1395 160 : if (((IndexOnlyScan *) plan)->indexqual)
1396 128 : show_instrumentation_count("Rows Removed by Index Recheck", 2,
1397 : planstate, es);
1398 160 : show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
1399 : "Order By", planstate, ancestors, es);
1400 160 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1401 160 : if (plan->qual)
1402 4 : show_instrumentation_count("Rows Removed by Filter", 1,
1403 : planstate, es);
1404 160 : if (es->analyze)
1405 0 : ExplainPropertyLong("Heap Fetches",
1406 : ((IndexOnlyScanState *) planstate)->ioss_HeapFetches, es);
1407 160 : break;
1408 : case T_BitmapIndexScan:
1409 327 : show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
1410 : "Index Cond", planstate, ancestors, es);
1411 327 : break;
1412 : case T_BitmapHeapScan:
1413 314 : show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
1414 : "Recheck Cond", planstate, ancestors, es);
1415 314 : if (((BitmapHeapScan *) plan)->bitmapqualorig)
1416 312 : show_instrumentation_count("Rows Removed by Index Recheck", 2,
1417 : planstate, es);
1418 314 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1419 314 : if (plan->qual)
1420 5 : show_instrumentation_count("Rows Removed by Filter", 1,
1421 : planstate, es);
1422 314 : if (es->analyze)
1423 0 : show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
1424 314 : break;
1425 : case T_SampleScan:
1426 9 : show_tablesample(((SampleScan *) plan)->tablesample,
1427 : planstate, ancestors, es);
1428 : /* FALL THRU to print additional fields the same as SeqScan */
1429 : case T_SeqScan:
1430 : case T_ValuesScan:
1431 : case T_CteScan:
1432 : case T_NamedTuplestoreScan:
1433 : case T_WorkTableScan:
1434 : case T_SubqueryScan:
1435 901 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1436 901 : if (plan->qual)
1437 547 : show_instrumentation_count("Rows Removed by Filter", 1,
1438 : planstate, es);
1439 901 : break;
1440 : case T_Gather:
1441 : {
1442 14 : Gather *gather = (Gather *) plan;
1443 :
1444 14 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1445 14 : if (plan->qual)
1446 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1447 : planstate, es);
1448 14 : ExplainPropertyInteger("Workers Planned",
1449 : gather->num_workers, es);
1450 14 : if (es->analyze)
1451 : {
1452 : int nworkers;
1453 :
1454 1 : nworkers = ((GatherState *) planstate)->nworkers_launched;
1455 1 : ExplainPropertyInteger("Workers Launched",
1456 : nworkers, es);
1457 : }
1458 14 : if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)
1459 1 : ExplainPropertyBool("Single Copy", gather->single_copy, es);
1460 : }
1461 14 : break;
1462 : case T_GatherMerge:
1463 : {
1464 4 : GatherMerge *gm = (GatherMerge *) plan;
1465 :
1466 4 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1467 4 : if (plan->qual)
1468 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1469 : planstate, es);
1470 4 : ExplainPropertyInteger("Workers Planned",
1471 : gm->num_workers, es);
1472 4 : if (es->analyze)
1473 : {
1474 : int nworkers;
1475 :
1476 0 : nworkers = ((GatherMergeState *) planstate)->nworkers_launched;
1477 0 : ExplainPropertyInteger("Workers Launched",
1478 : nworkers, es);
1479 : }
1480 : }
1481 4 : break;
1482 : case T_FunctionScan:
1483 23 : if (es->verbose)
1484 : {
1485 7 : List *fexprs = NIL;
1486 : ListCell *lc;
1487 :
1488 14 : foreach(lc, ((FunctionScan *) plan)->functions)
1489 : {
1490 7 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
1491 :
1492 7 : fexprs = lappend(fexprs, rtfunc->funcexpr);
1493 : }
1494 : /* We rely on show_expression to insert commas as needed */
1495 7 : show_expression((Node *) fexprs,
1496 : "Function Call", planstate, ancestors,
1497 7 : es->verbose, es);
1498 : }
1499 23 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1500 23 : if (plan->qual)
1501 2 : show_instrumentation_count("Rows Removed by Filter", 1,
1502 : planstate, es);
1503 23 : break;
1504 : case T_TableFuncScan:
1505 5 : if (es->verbose)
1506 : {
1507 4 : TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;
1508 :
1509 4 : show_expression((Node *) tablefunc,
1510 : "Table Function Call", planstate, ancestors,
1511 4 : es->verbose, es);
1512 : }
1513 5 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1514 5 : if (plan->qual)
1515 2 : show_instrumentation_count("Rows Removed by Filter", 1,
1516 : planstate, es);
1517 5 : break;
1518 : case T_TidScan:
1519 : {
1520 : /*
1521 : * The tidquals list has OR semantics, so be sure to show it
1522 : * as an OR condition.
1523 : */
1524 7 : List *tidquals = ((TidScan *) plan)->tidquals;
1525 :
1526 7 : if (list_length(tidquals) > 1)
1527 1 : tidquals = list_make1(make_orclause(tidquals));
1528 7 : show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
1529 7 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1530 7 : if (plan->qual)
1531 2 : show_instrumentation_count("Rows Removed by Filter", 1,
1532 : planstate, es);
1533 : }
1534 7 : break;
1535 : case T_ForeignScan:
1536 0 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1537 0 : if (plan->qual)
1538 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1539 : planstate, es);
1540 0 : show_foreignscan_info((ForeignScanState *) planstate, es);
1541 0 : break;
1542 : case T_CustomScan:
1543 : {
1544 0 : CustomScanState *css = (CustomScanState *) planstate;
1545 :
1546 0 : show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1547 0 : if (plan->qual)
1548 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1549 : planstate, es);
1550 0 : if (css->methods->ExplainCustomScan)
1551 0 : css->methods->ExplainCustomScan(css, ancestors, es);
1552 : }
1553 0 : break;
1554 : case T_NestLoop:
1555 167 : show_upper_qual(((NestLoop *) plan)->join.joinqual,
1556 : "Join Filter", planstate, ancestors, es);
1557 167 : if (((NestLoop *) plan)->join.joinqual)
1558 48 : show_instrumentation_count("Rows Removed by Join Filter", 1,
1559 : planstate, es);
1560 167 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1561 167 : if (plan->qual)
1562 4 : show_instrumentation_count("Rows Removed by Filter", 2,
1563 : planstate, es);
1564 167 : break;
1565 : case T_MergeJoin:
1566 14 : show_upper_qual(((MergeJoin *) plan)->mergeclauses,
1567 : "Merge Cond", planstate, ancestors, es);
1568 14 : show_upper_qual(((MergeJoin *) plan)->join.joinqual,
1569 : "Join Filter", planstate, ancestors, es);
1570 14 : if (((MergeJoin *) plan)->join.joinqual)
1571 1 : show_instrumentation_count("Rows Removed by Join Filter", 1,
1572 : planstate, es);
1573 14 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1574 14 : if (plan->qual)
1575 1 : show_instrumentation_count("Rows Removed by Filter", 2,
1576 : planstate, es);
1577 14 : break;
1578 : case T_HashJoin:
1579 53 : show_upper_qual(((HashJoin *) plan)->hashclauses,
1580 : "Hash Cond", planstate, ancestors, es);
1581 53 : show_upper_qual(((HashJoin *) plan)->join.joinqual,
1582 : "Join Filter", planstate, ancestors, es);
1583 53 : if (((HashJoin *) plan)->join.joinqual)
1584 0 : show_instrumentation_count("Rows Removed by Join Filter", 1,
1585 : planstate, es);
1586 53 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1587 53 : if (plan->qual)
1588 1 : show_instrumentation_count("Rows Removed by Filter", 2,
1589 : planstate, es);
1590 53 : break;
1591 : case T_Agg:
1592 674 : show_agg_keys(castNode(AggState, planstate), ancestors, es);
1593 674 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1594 674 : if (plan->qual)
1595 2 : show_instrumentation_count("Rows Removed by Filter", 1,
1596 : planstate, es);
1597 674 : break;
1598 : case T_Group:
1599 2 : show_group_keys(castNode(GroupState, planstate), ancestors, es);
1600 2 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1601 2 : if (plan->qual)
1602 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1603 : planstate, es);
1604 2 : break;
1605 : case T_Sort:
1606 98 : show_sort_keys(castNode(SortState, planstate), ancestors, es);
1607 98 : show_sort_info(castNode(SortState, planstate), es);
1608 98 : break;
1609 : case T_MergeAppend:
1610 22 : show_merge_append_keys(castNode(MergeAppendState, planstate),
1611 : ancestors, es);
1612 22 : break;
1613 : case T_Result:
1614 108 : show_upper_qual((List *) ((Result *) plan)->resconstantqual,
1615 : "One-Time Filter", planstate, ancestors, es);
1616 108 : show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1617 108 : if (plan->qual)
1618 0 : show_instrumentation_count("Rows Removed by Filter", 1,
1619 : planstate, es);
1620 108 : break;
1621 : case T_ModifyTable:
1622 61 : show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,
1623 : es);
1624 61 : break;
1625 : case T_Hash:
1626 53 : show_hash_info(castNode(HashState, planstate), es);
1627 53 : break;
1628 : default:
1629 213 : break;
1630 : }
1631 :
1632 : /* Show buffer usage */
1633 3384 : if (es->buffers && planstate->instrument)
1634 0 : show_buffer_usage(es, &planstate->instrument->bufusage);
1635 :
1636 : /* Show worker detail */
1637 3384 : if (es->analyze && es->verbose && planstate->worker_instrument)
1638 : {
1639 0 : WorkerInstrumentation *w = planstate->worker_instrument;
1640 0 : bool opened_group = false;
1641 : int n;
1642 :
1643 0 : for (n = 0; n < w->num_workers; ++n)
1644 : {
1645 0 : Instrumentation *instrument = &w->instrument[n];
1646 0 : double nloops = instrument->nloops;
1647 : double startup_sec;
1648 : double total_sec;
1649 : double rows;
1650 :
1651 0 : if (nloops <= 0)
1652 0 : continue;
1653 0 : startup_sec = 1000.0 * instrument->startup / nloops;
1654 0 : total_sec = 1000.0 * instrument->total / nloops;
1655 0 : rows = instrument->ntuples / nloops;
1656 :
1657 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
1658 : {
1659 0 : appendStringInfoSpaces(es->str, es->indent * 2);
1660 0 : appendStringInfo(es->str, "Worker %d: ", n);
1661 0 : if (es->timing)
1662 0 : appendStringInfo(es->str,
1663 : "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",
1664 : startup_sec, total_sec, rows, nloops);
1665 : else
1666 0 : appendStringInfo(es->str,
1667 : "actual rows=%.0f loops=%.0f\n",
1668 : rows, nloops);
1669 0 : es->indent++;
1670 0 : if (es->buffers)
1671 0 : show_buffer_usage(es, &instrument->bufusage);
1672 0 : es->indent--;
1673 : }
1674 : else
1675 : {
1676 0 : if (!opened_group)
1677 : {
1678 0 : ExplainOpenGroup("Workers", "Workers", false, es);
1679 0 : opened_group = true;
1680 : }
1681 0 : ExplainOpenGroup("Worker", NULL, true, es);
1682 0 : ExplainPropertyInteger("Worker Number", n, es);
1683 :
1684 0 : if (es->timing)
1685 : {
1686 0 : ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
1687 0 : ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
1688 : }
1689 0 : ExplainPropertyFloat("Actual Rows", rows, 0, es);
1690 0 : ExplainPropertyFloat("Actual Loops", nloops, 0, es);
1691 :
1692 0 : if (es->buffers)
1693 0 : show_buffer_usage(es, &instrument->bufusage);
1694 :
1695 0 : ExplainCloseGroup("Worker", NULL, true, es);
1696 : }
1697 : }
1698 :
1699 0 : if (opened_group)
1700 0 : ExplainCloseGroup("Workers", "Workers", false, es);
1701 : }
1702 :
1703 : /* Get ready to display the child plans */
1704 10102 : haschildren = planstate->initPlan ||
1705 5159 : outerPlanState(planstate) ||
1706 3650 : innerPlanState(planstate) ||
1707 3593 : IsA(plan, ModifyTable) ||
1708 3461 : IsA(plan, Append) ||
1709 3364 : IsA(plan, MergeAppend) ||
1710 3341 : IsA(plan, BitmapAnd) ||
1711 3330 : IsA(plan, BitmapOr) ||
1712 3284 : IsA(plan, SubqueryScan) ||
1713 1624 : (IsA(planstate, CustomScanState) &&
1714 5008 : ((CustomScanState *) planstate)->custom_ps != NIL) ||
1715 1624 : planstate->subPlan;
1716 3384 : if (haschildren)
1717 : {
1718 1784 : ExplainOpenGroup("Plans", "Plans", false, es);
1719 : /* Pass current PlanState as head of ancestors list for children */
1720 1784 : ancestors = lcons(planstate, ancestors);
1721 : }
1722 :
1723 : /* initPlan-s */
1724 3384 : if (planstate->initPlan)
1725 50 : ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
1726 :
1727 : /* lefttree */
1728 3384 : if (outerPlanState(planstate))
1729 1528 : ExplainNode(outerPlanState(planstate), ancestors,
1730 : "Outer", NULL, es);
1731 :
1732 : /* righttree */
1733 3384 : if (innerPlanState(planstate))
1734 234 : ExplainNode(innerPlanState(planstate), ancestors,
1735 : "Inner", NULL, es);
1736 :
1737 : /* special child plans */
1738 3384 : switch (nodeTag(plan))
1739 : {
1740 : case T_ModifyTable:
1741 61 : ExplainMemberNodes(((ModifyTable *) plan)->plans,
1742 : ((ModifyTableState *) planstate)->mt_plans,
1743 : ancestors, es);
1744 61 : break;
1745 : case T_Append:
1746 80 : ExplainMemberNodes(((Append *) plan)->appendplans,
1747 : ((AppendState *) planstate)->appendplans,
1748 : ancestors, es);
1749 80 : break;
1750 : case T_MergeAppend:
1751 22 : ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
1752 : ((MergeAppendState *) planstate)->mergeplans,
1753 : ancestors, es);
1754 22 : break;
1755 : case T_BitmapAnd:
1756 1 : ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
1757 : ((BitmapAndState *) planstate)->bitmapplans,
1758 : ancestors, es);
1759 1 : break;
1760 : case T_BitmapOr:
1761 10 : ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
1762 : ((BitmapOrState *) planstate)->bitmapplans,
1763 : ancestors, es);
1764 10 : break;
1765 : case T_SubqueryScan:
1766 36 : ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
1767 : "Subquery", NULL, es);
1768 36 : break;
1769 : case T_CustomScan:
1770 0 : ExplainCustomChildren((CustomScanState *) planstate,
1771 : ancestors, es);
1772 0 : break;
1773 : default:
1774 3174 : break;
1775 : }
1776 :
1777 : /* subPlan-s */
1778 3384 : if (planstate->subPlan)
1779 28 : ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
1780 :
1781 : /* end of child plans */
1782 3384 : if (haschildren)
1783 : {
1784 1784 : ancestors = list_delete_first(ancestors);
1785 1784 : ExplainCloseGroup("Plans", "Plans", false, es);
1786 : }
1787 :
1788 : /* in text format, undo whatever indentation we added */
1789 3384 : if (es->format == EXPLAIN_FORMAT_TEXT)
1790 3382 : es->indent = save_indent;
1791 :
1792 3384 : ExplainCloseGroup("Plan",
1793 : relationship ? NULL : "Plan",
1794 : true, es);
1795 3384 : }
1796 :
1797 : /*
1798 : * Show the targetlist of a plan node
1799 : */
1800 : static void
1801 435 : show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
1802 : {
1803 435 : Plan *plan = planstate->plan;
1804 : List *context;
1805 435 : List *result = NIL;
1806 : bool useprefix;
1807 : ListCell *lc;
1808 :
1809 : /* No work if empty tlist (this occurs eg in bitmap indexscans) */
1810 435 : if (plan->targetlist == NIL)
1811 22 : return;
1812 : /* The tlist of an Append isn't real helpful, so suppress it */
1813 413 : if (IsA(plan, Append))
1814 6 : return;
1815 : /* Likewise for MergeAppend and RecursiveUnion */
1816 407 : if (IsA(plan, MergeAppend))
1817 2 : return;
1818 405 : if (IsA(plan, RecursiveUnion))
1819 0 : return;
1820 :
1821 : /*
1822 : * Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE
1823 : *
1824 : * Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE
1825 : * might contain subplan output expressions that are confusing in this
1826 : * context. The tlist for a ForeignScan that executes a direct UPDATE/
1827 : * DELETE always contains "junk" target columns to identify the exact row
1828 : * to update or delete, which would be confusing in this context. So, we
1829 : * suppress it in all the cases.
1830 : */
1831 405 : if (IsA(plan, ForeignScan) &&
1832 0 : ((ForeignScan *) plan)->operation != CMD_SELECT)
1833 0 : return;
1834 :
1835 : /* Set up deparsing context */
1836 405 : context = set_deparse_context_planstate(es->deparse_cxt,
1837 : (Node *) planstate,
1838 : ancestors);
1839 405 : useprefix = list_length(es->rtable) > 1;
1840 :
1841 : /* Deparse each result column (we now include resjunk ones) */
1842 1272 : foreach(lc, plan->targetlist)
1843 : {
1844 867 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
1845 :
1846 867 : result = lappend(result,
1847 867 : deparse_expression((Node *) tle->expr, context,
1848 : useprefix, false));
1849 : }
1850 :
1851 : /* Print results */
1852 405 : ExplainPropertyList("Output", result, es);
1853 : }
1854 :
1855 : /*
1856 : * Show a generic expression
1857 : */
1858 : static void
1859 1669 : show_expression(Node *node, const char *qlabel,
1860 : PlanState *planstate, List *ancestors,
1861 : bool useprefix, ExplainState *es)
1862 : {
1863 : List *context;
1864 : char *exprstr;
1865 :
1866 : /* Set up deparsing context */
1867 1669 : context = set_deparse_context_planstate(es->deparse_cxt,
1868 : (Node *) planstate,
1869 : ancestors);
1870 :
1871 : /* Deparse the expression */
1872 1669 : exprstr = deparse_expression(node, context, useprefix, false);
1873 :
1874 : /* And add to es->str */
1875 1669 : ExplainPropertyText(qlabel, exprstr, es);
1876 1669 : }
1877 :
1878 : /*
1879 : * Show a qualifier expression (which is a List with implicit AND semantics)
1880 : */
1881 : static void
1882 4322 : show_qual(List *qual, const char *qlabel,
1883 : PlanState *planstate, List *ancestors,
1884 : bool useprefix, ExplainState *es)
1885 : {
1886 : Node *node;
1887 :
1888 : /* No work if empty qual */
1889 4322 : if (qual == NIL)
1890 6986 : return;
1891 :
1892 : /* Convert AND list to explicit AND */
1893 1658 : node = (Node *) make_ands_explicit(qual);
1894 :
1895 : /* And show it */
1896 1658 : show_expression(node, qlabel, planstate, ancestors, useprefix, es);
1897 : }
1898 :
1899 : /*
1900 : * Show a qualifier expression for a scan plan node
1901 : */
1902 : static void
1903 2888 : show_scan_qual(List *qual, const char *qlabel,
1904 : PlanState *planstate, List *ancestors,
1905 : ExplainState *es)
1906 : {
1907 : bool useprefix;
1908 :
1909 2888 : useprefix = (IsA(planstate->plan, SubqueryScan) ||es->verbose);
1910 2888 : show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
1911 2888 : }
1912 :
1913 : /*
1914 : * Show a qualifier expression for an upper-level plan node
1915 : */
1916 : static void
1917 1434 : show_upper_qual(List *qual, const char *qlabel,
1918 : PlanState *planstate, List *ancestors,
1919 : ExplainState *es)
1920 : {
1921 : bool useprefix;
1922 :
1923 1434 : useprefix = (list_length(es->rtable) > 1 || es->verbose);
1924 1434 : show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
1925 1434 : }
1926 :
1927 : /*
1928 : * Show the sort keys for a Sort node.
1929 : */
1930 : static void
1931 98 : show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
1932 : {
1933 98 : Sort *plan = (Sort *) sortstate->ss.ps.plan;
1934 :
1935 98 : show_sort_group_keys((PlanState *) sortstate, "Sort Key",
1936 : plan->numCols, plan->sortColIdx,
1937 : plan->sortOperators, plan->collations,
1938 : plan->nullsFirst,
1939 : ancestors, es);
1940 98 : }
1941 :
1942 : /*
1943 : * Likewise, for a MergeAppend node.
1944 : */
1945 : static void
1946 22 : show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
1947 : ExplainState *es)
1948 : {
1949 22 : MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
1950 :
1951 22 : show_sort_group_keys((PlanState *) mstate, "Sort Key",
1952 : plan->numCols, plan->sortColIdx,
1953 : plan->sortOperators, plan->collations,
1954 : plan->nullsFirst,
1955 : ancestors, es);
1956 22 : }
1957 :
1958 : /*
1959 : * Show the grouping keys for an Agg node.
1960 : */
1961 : static void
1962 674 : show_agg_keys(AggState *astate, List *ancestors,
1963 : ExplainState *es)
1964 : {
1965 674 : Agg *plan = (Agg *) astate->ss.ps.plan;
1966 :
1967 674 : if (plan->numCols > 0 || plan->groupingSets)
1968 : {
1969 : /* The key columns refer to the tlist of the child plan */
1970 67 : ancestors = lcons(astate, ancestors);
1971 :
1972 67 : if (plan->groupingSets)
1973 18 : show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
1974 : else
1975 49 : show_sort_group_keys(outerPlanState(astate), "Group Key",
1976 : plan->numCols, plan->grpColIdx,
1977 : NULL, NULL, NULL,
1978 : ancestors, es);
1979 :
1980 67 : ancestors = list_delete_first(ancestors);
1981 : }
1982 674 : }
1983 :
1984 : static void
1985 18 : show_grouping_sets(PlanState *planstate, Agg *agg,
1986 : List *ancestors, ExplainState *es)
1987 : {
1988 : List *context;
1989 : bool useprefix;
1990 : ListCell *lc;
1991 :
1992 : /* Set up deparsing context */
1993 18 : context = set_deparse_context_planstate(es->deparse_cxt,
1994 : (Node *) planstate,
1995 : ancestors);
1996 18 : useprefix = (list_length(es->rtable) > 1 || es->verbose);
1997 :
1998 18 : ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
1999 :
2000 18 : show_grouping_set_keys(planstate, agg, NULL,
2001 : context, useprefix, ancestors, es);
2002 :
2003 53 : foreach(lc, agg->chain)
2004 : {
2005 35 : Agg *aggnode = lfirst(lc);
2006 35 : Sort *sortnode = (Sort *) aggnode->plan.lefttree;
2007 :
2008 35 : show_grouping_set_keys(planstate, aggnode, sortnode,
2009 : context, useprefix, ancestors, es);
2010 : }
2011 :
2012 18 : ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
2013 18 : }
2014 :
2015 : static void
2016 53 : show_grouping_set_keys(PlanState *planstate,
2017 : Agg *aggnode, Sort *sortnode,
2018 : List *context, bool useprefix,
2019 : List *ancestors, ExplainState *es)
2020 : {
2021 53 : Plan *plan = planstate->plan;
2022 : char *exprstr;
2023 : ListCell *lc;
2024 53 : List *gsets = aggnode->groupingSets;
2025 53 : AttrNumber *keycols = aggnode->grpColIdx;
2026 : const char *keyname;
2027 : const char *keysetname;
2028 :
2029 53 : if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED)
2030 : {
2031 36 : keyname = "Hash Key";
2032 36 : keysetname = "Hash Keys";
2033 : }
2034 : else
2035 : {
2036 17 : keyname = "Group Key";
2037 17 : keysetname = "Group Keys";
2038 : }
2039 :
2040 53 : ExplainOpenGroup("Grouping Set", NULL, true, es);
2041 :
2042 53 : if (sortnode)
2043 : {
2044 4 : show_sort_group_keys(planstate, "Sort Key",
2045 : sortnode->numCols, sortnode->sortColIdx,
2046 : sortnode->sortOperators, sortnode->collations,
2047 : sortnode->nullsFirst,
2048 : ancestors, es);
2049 4 : if (es->format == EXPLAIN_FORMAT_TEXT)
2050 4 : es->indent++;
2051 : }
2052 :
2053 53 : ExplainOpenGroup(keysetname, keysetname, false, es);
2054 :
2055 113 : foreach(lc, gsets)
2056 : {
2057 60 : List *result = NIL;
2058 : ListCell *lc2;
2059 :
2060 121 : foreach(lc2, (List *) lfirst(lc))
2061 : {
2062 61 : Index i = lfirst_int(lc2);
2063 61 : AttrNumber keyresno = keycols[i];
2064 61 : TargetEntry *target = get_tle_by_resno(plan->targetlist,
2065 : keyresno);
2066 :
2067 61 : if (!target)
2068 0 : elog(ERROR, "no tlist entry for key %d", keyresno);
2069 : /* Deparse the expression, showing any top-level cast */
2070 61 : exprstr = deparse_expression((Node *) target->expr, context,
2071 : useprefix, true);
2072 :
2073 61 : result = lappend(result, exprstr);
2074 : }
2075 :
2076 60 : if (!result && es->format == EXPLAIN_FORMAT_TEXT)
2077 12 : ExplainPropertyText(keyname, "()", es);
2078 : else
2079 48 : ExplainPropertyListNested(keyname, result, es);
2080 : }
2081 :
2082 53 : ExplainCloseGroup(keysetname, keysetname, false, es);
2083 :
2084 53 : if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
2085 4 : es->indent--;
2086 :
2087 53 : ExplainCloseGroup("Grouping Set", NULL, true, es);
2088 53 : }
2089 :
2090 : /*
2091 : * Show the grouping keys for a Group node.
2092 : */
2093 : static void
2094 2 : show_group_keys(GroupState *gstate, List *ancestors,
2095 : ExplainState *es)
2096 : {
2097 2 : Group *plan = (Group *) gstate->ss.ps.plan;
2098 :
2099 : /* The key columns refer to the tlist of the child plan */
2100 2 : ancestors = lcons(gstate, ancestors);
2101 2 : show_sort_group_keys(outerPlanState(gstate), "Group Key",
2102 : plan->numCols, plan->grpColIdx,
2103 : NULL, NULL, NULL,
2104 : ancestors, es);
2105 2 : ancestors = list_delete_first(ancestors);
2106 2 : }
2107 :
2108 : /*
2109 : * Common code to show sort/group keys, which are represented in plan nodes
2110 : * as arrays of targetlist indexes. If it's a sort key rather than a group
2111 : * key, also pass sort operators/collations/nullsFirst arrays.
2112 : */
2113 : static void
2114 175 : show_sort_group_keys(PlanState *planstate, const char *qlabel,
2115 : int nkeys, AttrNumber *keycols,
2116 : Oid *sortOperators, Oid *collations, bool *nullsFirst,
2117 : List *ancestors, ExplainState *es)
2118 : {
2119 175 : Plan *plan = planstate->plan;
2120 : List *context;
2121 175 : List *result = NIL;
2122 : StringInfoData sortkeybuf;
2123 : bool useprefix;
2124 : int keyno;
2125 :
2126 175 : if (nkeys <= 0)
2127 175 : return;
2128 :
2129 175 : initStringInfo(&sortkeybuf);
2130 :
2131 : /* Set up deparsing context */
2132 175 : context = set_deparse_context_planstate(es->deparse_cxt,
2133 : (Node *) planstate,
2134 : ancestors);
2135 175 : useprefix = (list_length(es->rtable) > 1 || es->verbose);
2136 :
2137 456 : for (keyno = 0; keyno < nkeys; keyno++)
2138 : {
2139 : /* find key expression in tlist */
2140 281 : AttrNumber keyresno = keycols[keyno];
2141 281 : TargetEntry *target = get_tle_by_resno(plan->targetlist,
2142 : keyresno);
2143 : char *exprstr;
2144 :
2145 281 : if (!target)
2146 0 : elog(ERROR, "no tlist entry for key %d", keyresno);
2147 : /* Deparse the expression, showing any top-level cast */
2148 281 : exprstr = deparse_expression((Node *) target->expr, context,
2149 : useprefix, true);
2150 281 : resetStringInfo(&sortkeybuf);
2151 281 : appendStringInfoString(&sortkeybuf, exprstr);
2152 : /* Append sort order information, if relevant */
2153 281 : if (sortOperators != NULL)
2154 716 : show_sortorder_options(&sortkeybuf,
2155 179 : (Node *) target->expr,
2156 179 : sortOperators[keyno],
2157 179 : collations[keyno],
2158 179 : nullsFirst[keyno]);
2159 : /* Emit one property-list item per sort key */
2160 281 : result = lappend(result, pstrdup(sortkeybuf.data));
2161 : }
2162 :
2163 175 : ExplainPropertyList(qlabel, result, es);
2164 : }
2165 :
2166 : /*
2167 : * Append nondefault characteristics of the sort ordering of a column to buf
2168 : * (collation, direction, NULLS FIRST/LAST)
2169 : */
2170 : static void
2171 179 : show_sortorder_options(StringInfo buf, Node *sortexpr,
2172 : Oid sortOperator, Oid collation, bool nullsFirst)
2173 : {
2174 179 : Oid sortcoltype = exprType(sortexpr);
2175 179 : bool reverse = false;
2176 : TypeCacheEntry *typentry;
2177 :
2178 179 : typentry = lookup_type_cache(sortcoltype,
2179 : TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
2180 :
2181 : /*
2182 : * Print COLLATE if it's not default. There are some cases where this is
2183 : * redundant, eg if expression is a column whose declared collation is
2184 : * that collation, but it's hard to distinguish that here.
2185 : */
2186 179 : if (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)
2187 : {
2188 4 : char *collname = get_collation_name(collation);
2189 :
2190 4 : if (collname == NULL)
2191 0 : elog(ERROR, "cache lookup failed for collation %u", collation);
2192 4 : appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
2193 : }
2194 :
2195 : /* Print direction if not ASC, or USING if non-default sort operator */
2196 179 : if (sortOperator == typentry->gt_opr)
2197 : {
2198 6 : appendStringInfoString(buf, " DESC");
2199 6 : reverse = true;
2200 : }
2201 173 : else if (sortOperator != typentry->lt_opr)
2202 : {
2203 2 : char *opname = get_opname(sortOperator);
2204 :
2205 2 : if (opname == NULL)
2206 0 : elog(ERROR, "cache lookup failed for operator %u", sortOperator);
2207 2 : appendStringInfo(buf, " USING %s", opname);
2208 : /* Determine whether operator would be considered ASC or DESC */
2209 2 : (void) get_equality_op_for_ordering_op(sortOperator, &reverse);
2210 : }
2211 :
2212 : /* Add NULLS FIRST/LAST only if it wouldn't be default */
2213 179 : if (nullsFirst && !reverse)
2214 : {
2215 1 : appendStringInfoString(buf, " NULLS FIRST");
2216 : }
2217 178 : else if (!nullsFirst && reverse)
2218 : {
2219 0 : appendStringInfoString(buf, " NULLS LAST");
2220 : }
2221 179 : }
2222 :
2223 : /*
2224 : * Show TABLESAMPLE properties
2225 : */
2226 : static void
2227 9 : show_tablesample(TableSampleClause *tsc, PlanState *planstate,
2228 : List *ancestors, ExplainState *es)
2229 : {
2230 : List *context;
2231 : bool useprefix;
2232 : char *method_name;
2233 9 : List *params = NIL;
2234 : char *repeatable;
2235 : ListCell *lc;
2236 :
2237 : /* Set up deparsing context */
2238 9 : context = set_deparse_context_planstate(es->deparse_cxt,
2239 : (Node *) planstate,
2240 : ancestors);
2241 9 : useprefix = list_length(es->rtable) > 1;
2242 :
2243 : /* Get the tablesample method name */
2244 9 : method_name = get_func_name(tsc->tsmhandler);
2245 :
2246 : /* Deparse parameter expressions */
2247 18 : foreach(lc, tsc->args)
2248 : {
2249 9 : Node *arg = (Node *) lfirst(lc);
2250 :
2251 9 : params = lappend(params,
2252 9 : deparse_expression(arg, context,
2253 : useprefix, false));
2254 : }
2255 9 : if (tsc->repeatable)
2256 2 : repeatable = deparse_expression((Node *) tsc->repeatable, context,
2257 : useprefix, false);
2258 : else
2259 7 : repeatable = NULL;
2260 :
2261 : /* Print results */
2262 9 : if (es->format == EXPLAIN_FORMAT_TEXT)
2263 : {
2264 9 : bool first = true;
2265 :
2266 9 : appendStringInfoSpaces(es->str, es->indent * 2);
2267 9 : appendStringInfo(es->str, "Sampling: %s (", method_name);
2268 18 : foreach(lc, params)
2269 : {
2270 9 : if (!first)
2271 0 : appendStringInfoString(es->str, ", ");
2272 9 : appendStringInfoString(es->str, (const char *) lfirst(lc));
2273 9 : first = false;
2274 : }
2275 9 : appendStringInfoChar(es->str, ')');
2276 9 : if (repeatable)
2277 2 : appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);
2278 9 : appendStringInfoChar(es->str, '\n');
2279 : }
2280 : else
2281 : {
2282 0 : ExplainPropertyText("Sampling Method", method_name, es);
2283 0 : ExplainPropertyList("Sampling Parameters", params, es);
2284 0 : if (repeatable)
2285 0 : ExplainPropertyText("Repeatable Seed", repeatable, es);
2286 : }
2287 9 : }
2288 :
2289 : /*
2290 : * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
2291 : */
2292 : static void
2293 98 : show_sort_info(SortState *sortstate, ExplainState *es)
2294 : {
2295 98 : if (!es->analyze)
2296 195 : return;
2297 :
2298 1 : if (sortstate->sort_Done && sortstate->tuplesortstate != NULL)
2299 : {
2300 1 : Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
2301 : TuplesortInstrumentation stats;
2302 : const char *sortMethod;
2303 : const char *spaceType;
2304 : long spaceUsed;
2305 :
2306 1 : tuplesort_get_stats(state, &stats);
2307 1 : sortMethod = tuplesort_method_name(stats.sortMethod);
2308 1 : spaceType = tuplesort_space_type_name(stats.spaceType);
2309 1 : spaceUsed = stats.spaceUsed;
2310 :
2311 1 : if (es->format == EXPLAIN_FORMAT_TEXT)
2312 : {
2313 1 : appendStringInfoSpaces(es->str, es->indent * 2);
2314 1 : appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n",
2315 : sortMethod, spaceType, spaceUsed);
2316 : }
2317 : else
2318 : {
2319 0 : ExplainPropertyText("Sort Method", sortMethod, es);
2320 0 : ExplainPropertyLong("Sort Space Used", spaceUsed, es);
2321 0 : ExplainPropertyText("Sort Space Type", spaceType, es);
2322 : }
2323 : }
2324 :
2325 1 : if (sortstate->shared_info != NULL)
2326 : {
2327 : int n;
2328 0 : bool opened_group = false;
2329 :
2330 0 : for (n = 0; n < sortstate->shared_info->num_workers; n++)
2331 : {
2332 : TuplesortInstrumentation *sinstrument;
2333 : const char *sortMethod;
2334 : const char *spaceType;
2335 : long spaceUsed;
2336 :
2337 0 : sinstrument = &sortstate->shared_info->sinstrument[n];
2338 0 : if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
2339 0 : continue; /* ignore any unfilled slots */
2340 0 : sortMethod = tuplesort_method_name(sinstrument->sortMethod);
2341 0 : spaceType = tuplesort_space_type_name(sinstrument->spaceType);
2342 0 : spaceUsed = sinstrument->spaceUsed;
2343 :
2344 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
2345 : {
2346 0 : appendStringInfoSpaces(es->str, es->indent * 2);
2347 0 : appendStringInfo(es->str,
2348 : "Worker %d: Sort Method: %s %s: %ldkB\n",
2349 : n, sortMethod, spaceType, spaceUsed);
2350 : }
2351 : else
2352 : {
2353 0 : if (!opened_group)
2354 : {
2355 0 : ExplainOpenGroup("Workers", "Workers", false, es);
2356 0 : opened_group = true;
2357 : }
2358 0 : ExplainOpenGroup("Worker", NULL, true, es);
2359 0 : ExplainPropertyInteger("Worker Number", n, es);
2360 0 : ExplainPropertyText("Sort Method", sortMethod, es);
2361 0 : ExplainPropertyLong("Sort Space Used", spaceUsed, es);
2362 0 : ExplainPropertyText("Sort Space Type", spaceType, es);
2363 0 : ExplainCloseGroup("Worker", NULL, true, es);
2364 : }
2365 : }
2366 0 : if (opened_group)
2367 0 : ExplainCloseGroup("Workers", "Workers", false, es);
2368 : }
2369 : }
2370 :
2371 : /*
2372 : * Show information on hash buckets/batches.
2373 : */
2374 : static void
2375 53 : show_hash_info(HashState *hashstate, ExplainState *es)
2376 : {
2377 : HashJoinTable hashtable;
2378 :
2379 53 : hashtable = hashstate->hashtable;
2380 :
2381 53 : if (hashtable)
2382 : {
2383 0 : long spacePeakKb = (hashtable->spacePeak + 1023) / 1024;
2384 :
2385 0 : if (es->format != EXPLAIN_FORMAT_TEXT)
2386 : {
2387 0 : ExplainPropertyLong("Hash Buckets", hashtable->nbuckets, es);
2388 0 : ExplainPropertyLong("Original Hash Buckets",
2389 0 : hashtable->nbuckets_original, es);
2390 0 : ExplainPropertyLong("Hash Batches", hashtable->nbatch, es);
2391 0 : ExplainPropertyLong("Original Hash Batches",
2392 0 : hashtable->nbatch_original, es);
2393 0 : ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
2394 : }
2395 0 : else if (hashtable->nbatch_original != hashtable->nbatch ||
2396 0 : hashtable->nbuckets_original != hashtable->nbuckets)
2397 : {
2398 0 : appendStringInfoSpaces(es->str, es->indent * 2);
2399 0 : appendStringInfo(es->str,
2400 : "Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n",
2401 : hashtable->nbuckets,
2402 : hashtable->nbuckets_original,
2403 : hashtable->nbatch,
2404 : hashtable->nbatch_original,
2405 : spacePeakKb);
2406 : }
2407 : else
2408 : {
2409 0 : appendStringInfoSpaces(es->str, es->indent * 2);
2410 0 : appendStringInfo(es->str,
2411 : "Buckets: %d Batches: %d Memory Usage: %ldkB\n",
2412 : hashtable->nbuckets, hashtable->nbatch,
2413 : spacePeakKb);
2414 : }
2415 : }
2416 53 : }
2417 :
2418 : /*
2419 : * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
2420 : */
2421 : static void
2422 0 : show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
2423 : {
2424 0 : if (es->format != EXPLAIN_FORMAT_TEXT)
2425 : {
2426 0 : ExplainPropertyLong("Exact Heap Blocks", planstate->exact_pages, es);
2427 0 : ExplainPropertyLong("Lossy Heap Blocks", planstate->lossy_pages, es);
2428 : }
2429 : else
2430 : {
2431 0 : if (planstate->exact_pages > 0 || planstate->lossy_pages > 0)
2432 : {
2433 0 : appendStringInfoSpaces(es->str, es->indent * 2);
2434 0 : appendStringInfoString(es->str, "Heap Blocks:");
2435 0 : if (planstate->exact_pages > 0)
2436 0 : appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);
2437 0 : if (planstate->lossy_pages > 0)
2438 0 : appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);
2439 0 : appendStringInfoChar(es->str, '\n');
2440 : }
2441 : }
2442 0 : }
2443 :
2444 : /*
2445 : * If it's EXPLAIN ANALYZE, show instrumentation information for a plan node
2446 : *
2447 : * "which" identifies which instrumentation counter to print
2448 : */
2449 : static void
2450 1234 : show_instrumentation_count(const char *qlabel, int which,
2451 : PlanState *planstate, ExplainState *es)
2452 : {
2453 : double nfiltered;
2454 : double nloops;
2455 :
2456 1234 : if (!es->analyze || !planstate->instrument)
2457 2466 : return;
2458 :
2459 2 : if (which == 2)
2460 1 : nfiltered = planstate->instrument->nfiltered2;
2461 : else
2462 1 : nfiltered = planstate->instrument->nfiltered1;
2463 2 : nloops = planstate->instrument->nloops;
2464 :
2465 : /* In text mode, suppress zero counts; they're not interesting enough */
2466 2 : if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
2467 : {
2468 0 : if (nloops > 0)
2469 0 : ExplainPropertyFloat(qlabel, nfiltered / nloops, 0, es);
2470 : else
2471 0 : ExplainPropertyFloat(qlabel, 0.0, 0, es);
2472 : }
2473 : }
2474 :
2475 : /*
2476 : * Show extra information for a ForeignScan node.
2477 : */
2478 : static void
2479 0 : show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
2480 : {
2481 0 : FdwRoutine *fdwroutine = fsstate->fdwroutine;
2482 :
2483 : /* Let the FDW emit whatever fields it wants */
2484 0 : if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
2485 : {
2486 0 : if (fdwroutine->ExplainDirectModify != NULL)
2487 0 : fdwroutine->ExplainDirectModify(fsstate, es);
2488 : }
2489 : else
2490 : {
2491 0 : if (fdwroutine->ExplainForeignScan != NULL)
2492 0 : fdwroutine->ExplainForeignScan(fsstate, es);
2493 : }
2494 0 : }
2495 :
2496 : /*
2497 : * Fetch the name of an index in an EXPLAIN
2498 : *
2499 : * We allow plugins to get control here so that plans involving hypothetical
2500 : * indexes can be explained.
2501 : */
2502 : static const char *
2503 651 : explain_get_index_name(Oid indexId)
2504 : {
2505 : const char *result;
2506 :
2507 651 : if (explain_get_index_name_hook)
2508 0 : result = (*explain_get_index_name_hook) (indexId);
2509 : else
2510 651 : result = NULL;
2511 651 : if (result == NULL)
2512 : {
2513 : /* default behavior: look in the catalogs and quote it */
2514 651 : result = get_rel_name(indexId);
2515 651 : if (result == NULL)
2516 0 : elog(ERROR, "cache lookup failed for index %u", indexId);
2517 651 : result = quote_identifier(result);
2518 : }
2519 651 : return result;
2520 : }
2521 :
2522 : /*
2523 : * Show buffer usage details.
2524 : */
2525 : static void
2526 0 : show_buffer_usage(ExplainState *es, const BufferUsage *usage)
2527 : {
2528 0 : if (es->format == EXPLAIN_FORMAT_TEXT)
2529 : {
2530 0 : bool has_shared = (usage->shared_blks_hit > 0 ||
2531 0 : usage->shared_blks_read > 0 ||
2532 0 : usage->shared_blks_dirtied > 0 ||
2533 0 : usage->shared_blks_written > 0);
2534 0 : bool has_local = (usage->local_blks_hit > 0 ||
2535 0 : usage->local_blks_read > 0 ||
2536 0 : usage->local_blks_dirtied > 0 ||
2537 0 : usage->local_blks_written > 0);
2538 0 : bool has_temp = (usage->temp_blks_read > 0 ||
2539 0 : usage->temp_blks_written > 0);
2540 0 : bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
2541 0 : !INSTR_TIME_IS_ZERO(usage->blk_write_time));
2542 :
2543 : /* Show only positive counter values. */
2544 0 : if (has_shared || has_local || has_temp)
2545 : {
2546 0 : appendStringInfoSpaces(es->str, es->indent * 2);
2547 0 : appendStringInfoString(es->str, "Buffers:");
2548 :
2549 0 : if (has_shared)
2550 : {
2551 0 : appendStringInfoString(es->str, " shared");
2552 0 : if (usage->shared_blks_hit > 0)
2553 0 : appendStringInfo(es->str, " hit=%ld",
2554 : usage->shared_blks_hit);
2555 0 : if (usage->shared_blks_read > 0)
2556 0 : appendStringInfo(es->str, " read=%ld",
2557 : usage->shared_blks_read);
2558 0 : if (usage->shared_blks_dirtied > 0)
2559 0 : appendStringInfo(es->str, " dirtied=%ld",
2560 : usage->shared_blks_dirtied);
2561 0 : if (usage->shared_blks_written > 0)
2562 0 : appendStringInfo(es->str, " written=%ld",
2563 : usage->shared_blks_written);
2564 0 : if (has_local || has_temp)
2565 0 : appendStringInfoChar(es->str, ',');
2566 : }
2567 0 : if (has_local)
2568 : {
2569 0 : appendStringInfoString(es->str, " local");
2570 0 : if (usage->local_blks_hit > 0)
2571 0 : appendStringInfo(es->str, " hit=%ld",
2572 : usage->local_blks_hit);
2573 0 : if (usage->local_blks_read > 0)
2574 0 : appendStringInfo(es->str, " read=%ld",
2575 : usage->local_blks_read);
2576 0 : if (usage->local_blks_dirtied > 0)
2577 0 : appendStringInfo(es->str, " dirtied=%ld",
2578 : usage->local_blks_dirtied);
2579 0 : if (usage->local_blks_written > 0)
2580 0 : appendStringInfo(es->str, " written=%ld",
2581 : usage->local_blks_written);
2582 0 : if (has_temp)
2583 0 : appendStringInfoChar(es->str, ',');
2584 : }
2585 0 : if (has_temp)
2586 : {
2587 0 : appendStringInfoString(es->str, " temp");
2588 0 : if (usage->temp_blks_read > 0)
2589 0 : appendStringInfo(es->str, " read=%ld",
2590 : usage->temp_blks_read);
2591 0 : if (usage->temp_blks_written > 0)
2592 0 : appendStringInfo(es->str, " written=%ld",
2593 : usage->temp_blks_written);
2594 : }
2595 0 : appendStringInfoChar(es->str, '\n');
2596 : }
2597 :
2598 : /* As above, show only positive counter values. */
2599 0 : if (has_timing)
2600 : {
2601 0 : appendStringInfoSpaces(es->str, es->indent * 2);
2602 0 : appendStringInfoString(es->str, "I/O Timings:");
2603 0 : if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))
2604 0 : appendStringInfo(es->str, " read=%0.3f",
2605 0 : INSTR_TIME_GET_MILLISEC(usage->blk_read_time));
2606 0 : if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))
2607 0 : appendStringInfo(es->str, " write=%0.3f",
2608 0 : INSTR_TIME_GET_MILLISEC(usage->blk_write_time));
2609 0 : appendStringInfoChar(es->str, '\n');
2610 : }
2611 : }
2612 : else
2613 : {
2614 0 : ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es);
2615 0 : ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es);
2616 0 : ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es);
2617 0 : ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es);
2618 0 : ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es);
2619 0 : ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es);
2620 0 : ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es);
2621 0 : ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es);
2622 0 : ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es);
2623 0 : ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es);
2624 0 : if (track_io_timing)
2625 : {
2626 0 : ExplainPropertyFloat("I/O Read Time", INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3, es);
2627 0 : ExplainPropertyFloat("I/O Write Time", INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3, es);
2628 : }
2629 : }
2630 0 : }
2631 :
2632 : /*
2633 : * Add some additional details about an IndexScan or IndexOnlyScan
2634 : */
2635 : static void
2636 324 : ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
2637 : ExplainState *es)
2638 : {
2639 324 : const char *indexname = explain_get_index_name(indexid);
2640 :
2641 324 : if (es->format == EXPLAIN_FORMAT_TEXT)
2642 : {
2643 324 : if (ScanDirectionIsBackward(indexorderdir))
2644 20 : appendStringInfoString(es->str, " Backward");
2645 324 : appendStringInfo(es->str, " using %s", indexname);
2646 : }
2647 : else
2648 : {
2649 : const char *scandir;
2650 :
2651 0 : switch (indexorderdir)
2652 : {
2653 : case BackwardScanDirection:
2654 0 : scandir = "Backward";
2655 0 : break;
2656 : case NoMovementScanDirection:
2657 0 : scandir = "NoMovement";
2658 0 : break;
2659 : case ForwardScanDirection:
2660 0 : scandir = "Forward";
2661 0 : break;
2662 : default:
2663 0 : scandir = "???";
2664 0 : break;
2665 : }
2666 0 : ExplainPropertyText("Scan Direction", scandir, es);
2667 0 : ExplainPropertyText("Index Name", indexname, es);
2668 : }
2669 324 : }
2670 :
2671 : /*
2672 : * Show the target of a Scan node
2673 : */
2674 : static void
2675 1570 : ExplainScanTarget(Scan *plan, ExplainState *es)
2676 : {
2677 1570 : ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
2678 1570 : }
2679 :
2680 : /*
2681 : * Show the target of a ModifyTable node
2682 : *
2683 : * Here we show the nominal target (ie, the relation that was named in the
2684 : * original query). If the actual target(s) is/are different, we'll show them
2685 : * in show_modifytable_info().
2686 : */
2687 : static void
2688 61 : ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
2689 : {
2690 61 : ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);
2691 61 : }
2692 :
2693 : /*
2694 : * Show the target relation of a scan or modify node
2695 : */
2696 : static void
2697 1655 : ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
2698 : {
2699 1655 : char *objectname = NULL;
2700 1655 : char *namespace = NULL;
2701 1655 : const char *objecttag = NULL;
2702 : RangeTblEntry *rte;
2703 : char *refname;
2704 :
2705 1655 : rte = rt_fetch(rti, es->rtable);
2706 1655 : refname = (char *) list_nth(es->rtable_names, rti - 1);
2707 1655 : if (refname == NULL)
2708 0 : refname = rte->eref->aliasname;
2709 :
2710 1655 : switch (nodeTag(plan))
2711 : {
2712 : case T_SeqScan:
2713 : case T_SampleScan:
2714 : case T_IndexScan:
2715 : case T_IndexOnlyScan:
2716 : case T_BitmapHeapScan:
2717 : case T_TidScan:
2718 : case T_ForeignScan:
2719 : case T_CustomScan:
2720 : case T_ModifyTable:
2721 : /* Assert it's on a real relation */
2722 1552 : Assert(rte->rtekind == RTE_RELATION);
2723 1552 : objectname = get_rel_name(rte->relid);
2724 1552 : if (es->verbose)
2725 180 : namespace = get_namespace_name(get_rel_namespace(rte->relid));
2726 1552 : objecttag = "Relation Name";
2727 1552 : break;
2728 : case T_FunctionScan:
2729 : {
2730 23 : FunctionScan *fscan = (FunctionScan *) plan;
2731 :
2732 : /* Assert it's on a RangeFunction */
2733 23 : Assert(rte->rtekind == RTE_FUNCTION);
2734 :
2735 : /*
2736 : * If the expression is still a function call of a single
2737 : * function, we can get the real name of the function.
2738 : * Otherwise, punt. (Even if it was a single function call
2739 : * originally, the optimizer could have simplified it away.)
2740 : */
2741 23 : if (list_length(fscan->functions) == 1)
2742 : {
2743 23 : RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
2744 :
2745 23 : if (IsA(rtfunc->funcexpr, FuncExpr))
2746 : {
2747 18 : FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr;
2748 18 : Oid funcid = funcexpr->funcid;
2749 :
2750 18 : objectname = get_func_name(funcid);
2751 18 : if (es->verbose)
2752 6 : namespace =
2753 6 : get_namespace_name(get_func_namespace(funcid));
2754 : }
2755 : }
2756 23 : objecttag = "Function Name";
2757 : }
2758 23 : break;
2759 : case T_TableFuncScan:
2760 5 : Assert(rte->rtekind == RTE_TABLEFUNC);
2761 5 : objectname = "xmltable";
2762 5 : objecttag = "Table Function Name";
2763 5 : break;
2764 : case T_ValuesScan:
2765 24 : Assert(rte->rtekind == RTE_VALUES);
2766 24 : break;
2767 : case T_CteScan:
2768 : /* Assert it's on a non-self-reference CTE */
2769 15 : Assert(rte->rtekind == RTE_CTE);
2770 15 : Assert(!rte->self_reference);
2771 15 : objectname = rte->ctename;
2772 15 : objecttag = "CTE Name";
2773 15 : break;
2774 : case T_NamedTuplestoreScan:
2775 0 : Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
2776 0 : objectname = rte->enrname;
2777 0 : objecttag = "Tuplestore Name";
2778 0 : break;
2779 : case T_WorkTableScan:
2780 : /* Assert it's on a self-reference CTE */
2781 0 : Assert(rte->rtekind == RTE_CTE);
2782 0 : Assert(rte->self_reference);
2783 0 : objectname = rte->ctename;
2784 0 : objecttag = "CTE Name";
2785 0 : break;
2786 : default:
2787 36 : break;
2788 : }
2789 :
2790 1655 : if (es->format == EXPLAIN_FORMAT_TEXT)
2791 : {
2792 1654 : appendStringInfoString(es->str, " on");
2793 1654 : if (namespace != NULL)
2794 186 : appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
2795 : quote_identifier(objectname));
2796 1468 : else if (objectname != NULL)
2797 1403 : appendStringInfo(es->str, " %s", quote_identifier(objectname));
2798 1654 : if (objectname == NULL || strcmp(refname, objectname) != 0)
2799 386 : appendStringInfo(es->str, " %s", quote_identifier(refname));
2800 : }
2801 : else
2802 : {
2803 1 : if (objecttag != NULL && objectname != NULL)
2804 1 : ExplainPropertyText(objecttag, objectname, es);
2805 1 : if (namespace != NULL)
2806 0 : ExplainPropertyText("Schema", namespace, es);
2807 1 : ExplainPropertyText("Alias", refname, es);
2808 : }
2809 1655 : }
2810 :
2811 : /*
2812 : * Show extra information for a ModifyTable node
2813 : *
2814 : * We have three objectives here. First, if there's more than one target
2815 : * table or it's different from the nominal target, identify the actual
2816 : * target(s). Second, give FDWs a chance to display extra info about foreign
2817 : * targets. Third, show information about ON CONFLICT.
2818 : */
2819 : static void
2820 61 : show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
2821 : ExplainState *es)
2822 : {
2823 61 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
2824 : const char *operation;
2825 : const char *foperation;
2826 : bool labeltargets;
2827 : int j;
2828 61 : List *idxNames = NIL;
2829 : ListCell *lst;
2830 :
2831 61 : switch (node->operation)
2832 : {
2833 : case CMD_INSERT:
2834 24 : operation = "Insert";
2835 24 : foperation = "Foreign Insert";
2836 24 : break;
2837 : case CMD_UPDATE:
2838 25 : operation = "Update";
2839 25 : foperation = "Foreign Update";
2840 25 : break;
2841 : case CMD_DELETE:
2842 12 : operation = "Delete";
2843 12 : foperation = "Foreign Delete";
2844 12 : break;
2845 : default:
2846 0 : operation = "???";
2847 0 : foperation = "Foreign ???";
2848 0 : break;
2849 : }
2850 :
2851 : /* Should we explicitly label target relations? */
2852 122 : labeltargets = (mtstate->mt_nplans > 1 ||
2853 108 : (mtstate->mt_nplans == 1 &&
2854 54 : mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
2855 :
2856 61 : if (labeltargets)
2857 7 : ExplainOpenGroup("Target Tables", "Target Tables", false, es);
2858 :
2859 139 : for (j = 0; j < mtstate->mt_nplans; j++)
2860 : {
2861 78 : ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
2862 78 : FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
2863 :
2864 78 : if (labeltargets)
2865 : {
2866 : /* Open a group for this target */
2867 24 : ExplainOpenGroup("Target Table", NULL, true, es);
2868 :
2869 : /*
2870 : * In text mode, decorate each target with operation type, so that
2871 : * ExplainTargetRel's output of " on foo" will read nicely.
2872 : */
2873 24 : if (es->format == EXPLAIN_FORMAT_TEXT)
2874 : {
2875 24 : appendStringInfoSpaces(es->str, es->indent * 2);
2876 24 : appendStringInfoString(es->str,
2877 : fdwroutine ? foperation : operation);
2878 : }
2879 :
2880 : /* Identify target */
2881 24 : ExplainTargetRel((Plan *) node,
2882 : resultRelInfo->ri_RangeTableIndex,
2883 : es);
2884 :
2885 24 : if (es->format == EXPLAIN_FORMAT_TEXT)
2886 : {
2887 24 : appendStringInfoChar(es->str, '\n');
2888 24 : es->indent++;
2889 : }
2890 : }
2891 :
2892 : /* Give FDW a chance if needed */
2893 78 : if (!resultRelInfo->ri_usesFdwDirectModify &&
2894 0 : fdwroutine != NULL &&
2895 0 : fdwroutine->ExplainForeignModify != NULL)
2896 : {
2897 0 : List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
2898 :
2899 0 : fdwroutine->ExplainForeignModify(mtstate,
2900 : resultRelInfo,
2901 : fdw_private,
2902 : j,
2903 : es);
2904 : }
2905 :
2906 78 : if (labeltargets)
2907 : {
2908 : /* Undo the indentation we added in text format */
2909 24 : if (es->format == EXPLAIN_FORMAT_TEXT)
2910 24 : es->indent--;
2911 :
2912 : /* Close the group */
2913 24 : ExplainCloseGroup("Target Table", NULL, true, es);
2914 : }
2915 : }
2916 :
2917 : /* Gather names of ON CONFLICT arbiter indexes */
2918 90 : foreach(lst, node->arbiterIndexes)
2919 : {
2920 29 : char *indexname = get_rel_name(lfirst_oid(lst));
2921 :
2922 29 : idxNames = lappend(idxNames, indexname);
2923 : }
2924 :
2925 61 : if (node->onConflictAction != ONCONFLICT_NONE)
2926 : {
2927 20 : ExplainProperty("Conflict Resolution",
2928 20 : node->onConflictAction == ONCONFLICT_NOTHING ?
2929 : "NOTHING" : "UPDATE",
2930 : false, es);
2931 :
2932 : /*
2933 : * Don't display arbiter indexes at all when DO NOTHING variant
2934 : * implicitly ignores all conflicts
2935 : */
2936 20 : if (idxNames)
2937 20 : ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
2938 :
2939 : /* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
2940 20 : if (node->onConflictWhere)
2941 : {
2942 7 : show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
2943 : &mtstate->ps, ancestors, es);
2944 7 : show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
2945 : }
2946 :
2947 : /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
2948 20 : if (es->analyze && mtstate->ps.instrument)
2949 : {
2950 : double total;
2951 : double insert_path;
2952 : double other_path;
2953 :
2954 0 : InstrEndLoop(mtstate->mt_plans[0]->instrument);
2955 :
2956 : /* count the number of source rows */
2957 0 : total = mtstate->mt_plans[0]->instrument->ntuples;
2958 0 : other_path = mtstate->ps.instrument->nfiltered2;
2959 0 : insert_path = total - other_path;
2960 :
2961 0 : ExplainPropertyFloat("Tuples Inserted", insert_path, 0, es);
2962 0 : ExplainPropertyFloat("Conflicting Tuples", other_path, 0, es);
2963 : }
2964 : }
2965 :
2966 61 : if (labeltargets)
2967 7 : ExplainCloseGroup("Target Tables", "Target Tables", false, es);
2968 61 : }
2969 :
2970 : /*
2971 : * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
2972 : * BitmapAnd, or BitmapOr node.
2973 : *
2974 : * The ancestors list should already contain the immediate parent of these
2975 : * plans.
2976 : *
2977 : * Note: we don't actually need to examine the Plan list members, but
2978 : * we need the list in order to determine the length of the PlanState array.
2979 : */
2980 : static void
2981 174 : ExplainMemberNodes(List *plans, PlanState **planstates,
2982 : List *ancestors, ExplainState *es)
2983 : {
2984 174 : int nplans = list_length(plans);
2985 : int j;
2986 :
2987 559 : for (j = 0; j < nplans; j++)
2988 385 : ExplainNode(planstates[j], ancestors,
2989 : "Member", NULL, es);
2990 174 : }
2991 :
2992 : /*
2993 : * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
2994 : *
2995 : * The ancestors list should already contain the immediate parent of these
2996 : * SubPlanStates.
2997 : */
2998 : static void
2999 78 : ExplainSubPlans(List *plans, List *ancestors,
3000 : const char *relationship, ExplainState *es)
3001 : {
3002 : ListCell *lst;
3003 :
3004 173 : foreach(lst, plans)
3005 : {
3006 95 : SubPlanState *sps = (SubPlanState *) lfirst(lst);
3007 95 : SubPlan *sp = sps->subplan;
3008 :
3009 : /*
3010 : * There can be multiple SubPlan nodes referencing the same physical
3011 : * subplan (same plan_id, which is its index in PlannedStmt.subplans).
3012 : * We should print a subplan only once, so track which ones we already
3013 : * printed. This state must be global across the plan tree, since the
3014 : * duplicate nodes could be in different plan nodes, eg both a bitmap
3015 : * indexscan's indexqual and its parent heapscan's recheck qual. (We
3016 : * do not worry too much about which plan node we show the subplan as
3017 : * attached to in such cases.)
3018 : */
3019 95 : if (bms_is_member(sp->plan_id, es->printed_subplans))
3020 12 : continue;
3021 83 : es->printed_subplans = bms_add_member(es->printed_subplans,
3022 : sp->plan_id);
3023 :
3024 83 : ExplainNode(sps->planstate, ancestors,
3025 83 : relationship, sp->plan_name, es);
3026 : }
3027 78 : }
3028 :
3029 : /*
3030 : * Explain a list of children of a CustomScan.
3031 : */
3032 : static void
3033 0 : ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es)
3034 : {
3035 : ListCell *cell;
3036 0 : const char *label =
3037 0 : (list_length(css->custom_ps) != 1 ? "children" : "child");
3038 :
3039 0 : foreach(cell, css->custom_ps)
3040 0 : ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es);
3041 0 : }
3042 :
3043 : /*
3044 : * Explain a property, such as sort keys or targets, that takes the form of
3045 : * a list of unlabeled items. "data" is a list of C strings.
3046 : */
3047 : void
3048 648 : ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
3049 : {
3050 : ListCell *lc;
3051 648 : bool first = true;
3052 :
3053 648 : switch (es->format)
3054 : {
3055 : case EXPLAIN_FORMAT_TEXT:
3056 647 : appendStringInfoSpaces(es->str, es->indent * 2);
3057 647 : appendStringInfo(es->str, "%s: ", qlabel);
3058 1884 : foreach(lc, data)
3059 : {
3060 1237 : if (!first)
3061 590 : appendStringInfoString(es->str, ", ");
3062 1237 : appendStringInfoString(es->str, (const char *) lfirst(lc));
3063 1237 : first = false;
3064 : }
3065 647 : appendStringInfoChar(es->str, '\n');
3066 647 : break;
3067 :
3068 : case EXPLAIN_FORMAT_XML:
3069 0 : ExplainXMLTag(qlabel, X_OPENING, es);
3070 0 : foreach(lc, data)
3071 : {
3072 : char *str;
3073 :
3074 0 : appendStringInfoSpaces(es->str, es->indent * 2 + 2);
3075 0 : appendStringInfoString(es->str, "<Item>");
3076 0 : str = escape_xml((const char *) lfirst(lc));
3077 0 : appendStringInfoString(es->str, str);
3078 0 : pfree(str);
3079 0 : appendStringInfoString(es->str, "</Item>\n");
3080 : }
3081 0 : ExplainXMLTag(qlabel, X_CLOSING, es);
3082 0 : break;
3083 :
3084 : case EXPLAIN_FORMAT_JSON:
3085 1 : ExplainJSONLineEnding(es);
3086 1 : appendStringInfoSpaces(es->str, es->indent * 2);
3087 1 : escape_json(es->str, qlabel);
3088 1 : appendStringInfoString(es->str, ": [");
3089 2 : foreach(lc, data)
3090 : {
3091 1 : if (!first)
3092 0 : appendStringInfoString(es->str, ", ");
3093 1 : escape_json(es->str, (const char *) lfirst(lc));
3094 1 : first = false;
3095 : }
3096 1 : appendStringInfoChar(es->str, ']');
3097 1 : break;
3098 :
3099 : case EXPLAIN_FORMAT_YAML:
3100 0 : ExplainYAMLLineStarting(es);
3101 0 : appendStringInfo(es->str, "%s: ", qlabel);
3102 0 : foreach(lc, data)
3103 : {
3104 0 : appendStringInfoChar(es->str, '\n');
3105 0 : appendStringInfoSpaces(es->str, es->indent * 2 + 2);
3106 0 : appendStringInfoString(es->str, "- ");
3107 0 : escape_yaml(es->str, (const char *) lfirst(lc));
3108 : }
3109 0 : break;
3110 : }
3111 648 : }
3112 :
3113 : /*
3114 : * Explain a property that takes the form of a list of unlabeled items within
3115 : * another list. "data" is a list of C strings.
3116 : */
3117 : void
3118 48 : ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
3119 : {
3120 : ListCell *lc;
3121 48 : bool first = true;
3122 :
3123 48 : switch (es->format)
3124 : {
3125 : case EXPLAIN_FORMAT_TEXT:
3126 : case EXPLAIN_FORMAT_XML:
3127 48 : ExplainPropertyList(qlabel, data, es);
3128 96 : return;
3129 :
3130 : case EXPLAIN_FORMAT_JSON:
3131 0 : ExplainJSONLineEnding(es);
3132 0 : appendStringInfoSpaces(es->str, es->indent * 2);
3133 0 : appendStringInfoChar(es->str, '[');
3134 0 : foreach(lc, data)
3135 : {
3136 0 : if (!first)
3137 0 : appendStringInfoString(es->str, ", ");
3138 0 : escape_json(es->str, (const char *) lfirst(lc));
3139 0 : first = false;
3140 : }
3141 0 : appendStringInfoChar(es->str, ']');
3142 0 : break;
3143 :
3144 : case EXPLAIN_FORMAT_YAML:
3145 0 : ExplainYAMLLineStarting(es);
3146 0 : appendStringInfoString(es->str, "- [");
3147 0 : foreach(lc, data)
3148 : {
3149 0 : if (!first)
3150 0 : appendStringInfoString(es->str, ", ");
3151 0 : escape_yaml(es->str, (const char *) lfirst(lc));
3152 0 : first = false;
3153 : }
3154 0 : appendStringInfoChar(es->str, ']');
3155 0 : break;
3156 : }
3157 : }
3158 :
3159 : /*
3160 : * Explain a simple property.
3161 : *
3162 : * If "numeric" is true, the value is a number (or other value that
3163 : * doesn't need quoting in JSON).
3164 : *
3165 : * This usually should not be invoked directly, but via one of the datatype
3166 : * specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
3167 : */
3168 : static void
3169 1738 : ExplainProperty(const char *qlabel, const char *value, bool numeric,
3170 : ExplainState *es)
3171 : {
3172 1738 : switch (es->format)
3173 : {
3174 : case EXPLAIN_FORMAT_TEXT:
3175 1728 : appendStringInfoSpaces(es->str, es->indent * 2);
3176 1728 : appendStringInfo(es->str, "%s: %s\n", qlabel, value);
3177 1728 : break;
3178 :
3179 : case EXPLAIN_FORMAT_XML:
3180 : {
3181 : char *str;
3182 :
3183 0 : appendStringInfoSpaces(es->str, es->indent * 2);
3184 0 : ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
3185 0 : str = escape_xml(value);
3186 0 : appendStringInfoString(es->str, str);
3187 0 : pfree(str);
3188 0 : ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
3189 0 : appendStringInfoChar(es->str, '\n');
3190 : }
3191 0 : break;
3192 :
3193 : case EXPLAIN_FORMAT_JSON:
3194 10 : ExplainJSONLineEnding(es);
3195 10 : appendStringInfoSpaces(es->str, es->indent * 2);
3196 10 : escape_json(es->str, qlabel);
3197 10 : appendStringInfoString(es->str, ": ");
3198 10 : if (numeric)
3199 2 : appendStringInfoString(es->str, value);
3200 : else
3201 8 : escape_json(es->str, value);
3202 10 : break;
3203 :
3204 : case EXPLAIN_FORMAT_YAML:
3205 0 : ExplainYAMLLineStarting(es);
3206 0 : appendStringInfo(es->str, "%s: ", qlabel);
3207 0 : if (numeric)
3208 0 : appendStringInfoString(es->str, value);
3209 : else
3210 0 : escape_yaml(es->str, value);
3211 0 : break;
3212 : }
3213 1738 : }
3214 :
3215 : /*
3216 : * Explain a string-valued property.
3217 : */
3218 : void
3219 1687 : ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
3220 : {
3221 1687 : ExplainProperty(qlabel, value, false, es);
3222 1687 : }
3223 :
3224 : /*
3225 : * Explain an integer-valued property.
3226 : */
3227 : void
3228 19 : ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
3229 : {
3230 : char buf[32];
3231 :
3232 19 : snprintf(buf, sizeof(buf), "%d", value);
3233 19 : ExplainProperty(qlabel, buf, true, es);
3234 19 : }
3235 :
3236 : /*
3237 : * Explain a long-integer-valued property.
3238 : */
3239 : void
3240 0 : ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
3241 : {
3242 : char buf[32];
3243 :
3244 0 : snprintf(buf, sizeof(buf), "%ld", value);
3245 0 : ExplainProperty(qlabel, buf, true, es);
3246 0 : }
3247 :
3248 : /*
3249 : * Explain a float-valued property, using the specified number of
3250 : * fractional digits.
3251 : */
3252 : void
3253 0 : ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
3254 : ExplainState *es)
3255 : {
3256 : char buf[256];
3257 :
3258 0 : snprintf(buf, sizeof(buf), "%.*f", ndigits, value);
3259 0 : ExplainProperty(qlabel, buf, true, es);
3260 0 : }
3261 :
3262 : /*
3263 : * Explain a bool-valued property.
3264 : */
3265 : void
3266 12 : ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
3267 : {
3268 12 : ExplainProperty(qlabel, value ? "true" : "false", true, es);
3269 12 : }
3270 :
3271 : /*
3272 : * Open a group of related objects.
3273 : *
3274 : * objtype is the type of the group object, labelname is its label within
3275 : * a containing object (if any).
3276 : *
3277 : * If labeled is true, the group members will be labeled properties,
3278 : * while if it's false, they'll be unlabeled objects.
3279 : */
3280 : static void
3281 6447 : ExplainOpenGroup(const char *objtype, const char *labelname,
3282 : bool labeled, ExplainState *es)
3283 : {
3284 6447 : switch (es->format)
3285 : {
3286 : case EXPLAIN_FORMAT_TEXT:
3287 : /* nothing to do */
3288 6443 : break;
3289 :
3290 : case EXPLAIN_FORMAT_XML:
3291 0 : ExplainXMLTag(objtype, X_OPENING, es);
3292 0 : es->indent++;
3293 0 : break;
3294 :
3295 : case EXPLAIN_FORMAT_JSON:
3296 4 : ExplainJSONLineEnding(es);
3297 4 : appendStringInfoSpaces(es->str, 2 * es->indent);
3298 4 : if (labelname)
3299 : {
3300 2 : escape_json(es->str, labelname);
3301 2 : appendStringInfoString(es->str, ": ");
3302 : }
3303 4 : appendStringInfoChar(es->str, labeled ? '{' : '[');
3304 :
3305 : /*
3306 : * In JSON format, the grouping_stack is an integer list. 0 means
3307 : * we've emitted nothing at this grouping level, 1 means we've
3308 : * emitted something (and so the next item needs a comma). See
3309 : * ExplainJSONLineEnding().
3310 : */
3311 4 : es->grouping_stack = lcons_int(0, es->grouping_stack);
3312 4 : es->indent++;
3313 4 : break;
3314 :
3315 : case EXPLAIN_FORMAT_YAML:
3316 :
3317 : /*
3318 : * In YAML format, the grouping stack is an integer list. 0 means
3319 : * we've emitted nothing at this grouping level AND this grouping
3320 : * level is unlabelled and must be marked with "- ". See
3321 : * ExplainYAMLLineStarting().
3322 : */
3323 0 : ExplainYAMLLineStarting(es);
3324 0 : if (labelname)
3325 : {
3326 0 : appendStringInfo(es->str, "%s: ", labelname);
3327 0 : es->grouping_stack = lcons_int(1, es->grouping_stack);
3328 : }
3329 : else
3330 : {
3331 0 : appendStringInfoString(es->str, "- ");
3332 0 : es->grouping_stack = lcons_int(0, es->grouping_stack);
3333 : }
3334 0 : es->indent++;
3335 0 : break;
3336 : }
3337 6447 : }
3338 :
3339 : /*
3340 : * Close a group of related objects.
3341 : * Parameters must match the corresponding ExplainOpenGroup call.
3342 : */
3343 : static void
3344 6447 : ExplainCloseGroup(const char *objtype, const char *labelname,
3345 : bool labeled, ExplainState *es)
3346 : {
3347 6447 : switch (es->format)
3348 : {
3349 : case EXPLAIN_FORMAT_TEXT:
3350 : /* nothing to do */
3351 6443 : break;
3352 :
3353 : case EXPLAIN_FORMAT_XML:
3354 0 : es->indent--;
3355 0 : ExplainXMLTag(objtype, X_CLOSING, es);
3356 0 : break;
3357 :
3358 : case EXPLAIN_FORMAT_JSON:
3359 4 : es->indent--;
3360 4 : appendStringInfoChar(es->str, '\n');
3361 4 : appendStringInfoSpaces(es->str, 2 * es->indent);
3362 4 : appendStringInfoChar(es->str, labeled ? '}' : ']');
3363 4 : es->grouping_stack = list_delete_first(es->grouping_stack);
3364 4 : break;
3365 :
3366 : case EXPLAIN_FORMAT_YAML:
3367 0 : es->indent--;
3368 0 : es->grouping_stack = list_delete_first(es->grouping_stack);
3369 0 : break;
3370 : }
3371 6447 : }
3372 :
3373 : /*
3374 : * Emit a "dummy" group that never has any members.
3375 : *
3376 : * objtype is the type of the group object, labelname is its label within
3377 : * a containing object (if any).
3378 : */
3379 : static void
3380 0 : ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
3381 : {
3382 0 : switch (es->format)
3383 : {
3384 : case EXPLAIN_FORMAT_TEXT:
3385 : /* nothing to do */
3386 0 : break;
3387 :
3388 : case EXPLAIN_FORMAT_XML:
3389 0 : ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
3390 0 : break;
3391 :
3392 : case EXPLAIN_FORMAT_JSON:
3393 0 : ExplainJSONLineEnding(es);
3394 0 : appendStringInfoSpaces(es->str, 2 * es->indent);
3395 0 : if (labelname)
3396 : {
3397 0 : escape_json(es->str, labelname);
3398 0 : appendStringInfoString(es->str, ": ");
3399 : }
3400 0 : escape_json(es->str, objtype);
3401 0 : break;
3402 :
3403 : case EXPLAIN_FORMAT_YAML:
3404 0 : ExplainYAMLLineStarting(es);
3405 0 : if (labelname)
3406 : {
3407 0 : escape_yaml(es->str, labelname);
3408 0 : appendStringInfoString(es->str, ": ");
3409 : }
3410 : else
3411 : {
3412 0 : appendStringInfoString(es->str, "- ");
3413 : }
3414 0 : escape_yaml(es->str, objtype);
3415 0 : break;
3416 : }
3417 0 : }
3418 :
3419 : /*
3420 : * Emit the start-of-output boilerplate.
3421 : *
3422 : * This is just enough different from processing a subgroup that we need
3423 : * a separate pair of subroutines.
3424 : */
3425 : void
3426 1123 : ExplainBeginOutput(ExplainState *es)
3427 : {
3428 1123 : switch (es->format)
3429 : {
3430 : case EXPLAIN_FORMAT_TEXT:
3431 : /* nothing to do */
3432 1122 : break;
3433 :
3434 : case EXPLAIN_FORMAT_XML:
3435 0 : appendStringInfoString(es->str,
3436 : "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
3437 0 : es->indent++;
3438 0 : break;
3439 :
3440 : case EXPLAIN_FORMAT_JSON:
3441 : /* top-level structure is an array of plans */
3442 1 : appendStringInfoChar(es->str, '[');
3443 1 : es->grouping_stack = lcons_int(0, es->grouping_stack);
3444 1 : es->indent++;
3445 1 : break;
3446 :
3447 : case EXPLAIN_FORMAT_YAML:
3448 0 : es->grouping_stack = lcons_int(0, es->grouping_stack);
3449 0 : break;
3450 : }
3451 1123 : }
3452 :
3453 : /*
3454 : * Emit the end-of-output boilerplate.
3455 : */
3456 : void
3457 1116 : ExplainEndOutput(ExplainState *es)
3458 : {
3459 1116 : switch (es->format)
3460 : {
3461 : case EXPLAIN_FORMAT_TEXT:
3462 : /* nothing to do */
3463 1115 : break;
3464 :
3465 : case EXPLAIN_FORMAT_XML:
3466 0 : es->indent--;
3467 0 : appendStringInfoString(es->str, "</explain>");
3468 0 : break;
3469 :
3470 : case EXPLAIN_FORMAT_JSON:
3471 1 : es->indent--;
3472 1 : appendStringInfoString(es->str, "\n]");
3473 1 : es->grouping_stack = list_delete_first(es->grouping_stack);
3474 1 : break;
3475 :
3476 : case EXPLAIN_FORMAT_YAML:
3477 0 : es->grouping_stack = list_delete_first(es->grouping_stack);
3478 0 : break;
3479 : }
3480 1116 : }
3481 :
3482 : /*
3483 : * Put an appropriate separator between multiple plans
3484 : */
3485 : void
3486 2 : ExplainSeparatePlans(ExplainState *es)
3487 : {
3488 2 : switch (es->format)
3489 : {
3490 : case EXPLAIN_FORMAT_TEXT:
3491 : /* add a blank line */
3492 2 : appendStringInfoChar(es->str, '\n');
3493 2 : break;
3494 :
3495 : case EXPLAIN_FORMAT_XML:
3496 : case EXPLAIN_FORMAT_JSON:
3497 : case EXPLAIN_FORMAT_YAML:
3498 : /* nothing to do */
3499 0 : break;
3500 : }
3501 2 : }
3502 :
3503 : /*
3504 : * Emit opening or closing XML tag.
3505 : *
3506 : * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
3507 : * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
3508 : * add.
3509 : *
3510 : * XML restricts tag names more than our other output formats, eg they can't
3511 : * contain white space or slashes. Replace invalid characters with dashes,
3512 : * so that for example "I/O Read Time" becomes "I-O-Read-Time".
3513 : */
3514 : static void
3515 0 : ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
3516 : {
3517 : const char *s;
3518 0 : const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
3519 :
3520 0 : if ((flags & X_NOWHITESPACE) == 0)
3521 0 : appendStringInfoSpaces(es->str, 2 * es->indent);
3522 0 : appendStringInfoCharMacro(es->str, '<');
3523 0 : if ((flags & X_CLOSING) != 0)
3524 0 : appendStringInfoCharMacro(es->str, '/');
3525 0 : for (s = tagname; *s; s++)
3526 0 : appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
3527 0 : if ((flags & X_CLOSE_IMMEDIATE) != 0)
3528 0 : appendStringInfoString(es->str, " /");
3529 0 : appendStringInfoCharMacro(es->str, '>');
3530 0 : if ((flags & X_NOWHITESPACE) == 0)
3531 0 : appendStringInfoCharMacro(es->str, '\n');
3532 0 : }
3533 :
3534 : /*
3535 : * Emit a JSON line ending.
3536 : *
3537 : * JSON requires a comma after each property but the last. To facilitate this,
3538 : * in JSON format, the text emitted for each property begins just prior to the
3539 : * preceding line-break (and comma, if applicable).
3540 : */
3541 : static void
3542 15 : ExplainJSONLineEnding(ExplainState *es)
3543 : {
3544 15 : Assert(es->format == EXPLAIN_FORMAT_JSON);
3545 15 : if (linitial_int(es->grouping_stack) != 0)
3546 10 : appendStringInfoChar(es->str, ',');
3547 : else
3548 5 : linitial_int(es->grouping_stack) = 1;
3549 15 : appendStringInfoChar(es->str, '\n');
3550 15 : }
3551 :
3552 : /*
3553 : * Indent a YAML line.
3554 : *
3555 : * YAML lines are ordinarily indented by two spaces per indentation level.
3556 : * The text emitted for each property begins just prior to the preceding
3557 : * line-break, except for the first property in an unlabelled group, for which
3558 : * it begins immediately after the "- " that introduces the group. The first
3559 : * property of the group appears on the same line as the opening "- ".
3560 : */
3561 : static void
3562 0 : ExplainYAMLLineStarting(ExplainState *es)
3563 : {
3564 0 : Assert(es->format == EXPLAIN_FORMAT_YAML);
3565 0 : if (linitial_int(es->grouping_stack) == 0)
3566 : {
3567 0 : linitial_int(es->grouping_stack) = 1;
3568 : }
3569 : else
3570 : {
3571 0 : appendStringInfoChar(es->str, '\n');
3572 0 : appendStringInfoSpaces(es->str, es->indent * 2);
3573 : }
3574 0 : }
3575 :
3576 : /*
3577 : * YAML is a superset of JSON; unfortunately, the YAML quoting rules are
3578 : * ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
3579 : * http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
3580 : * Empty strings, strings with leading or trailing whitespace, and strings
3581 : * containing a variety of special characters must certainly be quoted or the
3582 : * output is invalid; and other seemingly harmless strings like "0xa" or
3583 : * "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
3584 : * constant rather than a string.
3585 : */
3586 : static void
3587 0 : escape_yaml(StringInfo buf, const char *str)
3588 : {
3589 0 : escape_json(buf, str);
3590 0 : }
|