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