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