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