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