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