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