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