Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_overexplain.c
4 : : * allow EXPLAIN to dump even more details
5 : : *
6 : : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 : : *
8 : : * contrib/pg_overexplain/pg_overexplain.c
9 : : *-------------------------------------------------------------------------
10 : : */
11 : : #include "postgres.h"
12 : :
13 : : #include "catalog/pg_class.h"
14 : : #include "commands/defrem.h"
15 : : #include "commands/explain.h"
16 : : #include "commands/explain_format.h"
17 : : #include "commands/explain_state.h"
18 : : #include "fmgr.h"
19 : : #include "parser/parsetree.h"
20 : : #include "storage/lock.h"
21 : : #include "utils/builtins.h"
22 : : #include "utils/lsyscache.h"
23 : :
352 rhaas@postgresql.org 24 :CBC 1 : PG_MODULE_MAGIC_EXT(
25 : : .name = "pg_overexplain",
26 : : .version = PG_VERSION
27 : : );
28 : :
29 : : typedef struct
30 : : {
31 : : bool debug;
32 : : bool range_table;
33 : : } overexplain_options;
34 : :
35 : : static overexplain_options *overexplain_ensure_options(ExplainState *es);
36 : : static void overexplain_debug_handler(ExplainState *es, DefElem *opt,
37 : : ParseState *pstate);
38 : : static void overexplain_range_table_handler(ExplainState *es, DefElem *opt,
39 : : ParseState *pstate);
40 : : static void overexplain_per_node_hook(PlanState *planstate, List *ancestors,
41 : : const char *relationship,
42 : : const char *plan_name,
43 : : ExplainState *es);
44 : : static void overexplain_per_plan_hook(PlannedStmt *plannedstmt,
45 : : IntoClause *into,
46 : : ExplainState *es,
47 : : const char *queryString,
48 : : ParamListInfo params,
49 : : QueryEnvironment *queryEnv);
50 : : static void overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es);
51 : : static void overexplain_range_table(PlannedStmt *plannedstmt,
52 : : ExplainState *es);
53 : : static void overexplain_alias(const char *qlabel, Alias *alias,
54 : : ExplainState *es);
55 : : static void overexplain_bitmapset(const char *qlabel, Bitmapset *bms,
56 : : ExplainState *es);
57 : : static void overexplain_bitmapset_list(const char *qlabel, List *bms_list,
58 : : ExplainState *es);
59 : : static void overexplain_intlist(const char *qlabel, List *list,
60 : : ExplainState *es);
61 : :
62 : : static int es_extension_id;
63 : : static explain_per_node_hook_type prev_explain_per_node_hook;
64 : : static explain_per_plan_hook_type prev_explain_per_plan_hook;
65 : :
66 : : /*
67 : : * Initialization we do when this module is loaded.
68 : : */
69 : : void
354 70 : 1 : _PG_init(void)
71 : : {
72 : : /* Get an ID that we can use to cache data in an ExplainState. */
73 : 1 : es_extension_id = GetExplainExtensionId("pg_overexplain");
74 : :
75 : : /* Register the new EXPLAIN options implemented by this module. */
76 : 1 : RegisterExtensionExplainOption("debug", overexplain_debug_handler);
77 : 1 : RegisterExtensionExplainOption("range_table",
78 : : overexplain_range_table_handler);
79 : :
80 : : /* Use the per-node and per-plan hooks to make our options do something. */
81 : 1 : prev_explain_per_node_hook = explain_per_node_hook;
82 : 1 : explain_per_node_hook = overexplain_per_node_hook;
83 : 1 : prev_explain_per_plan_hook = explain_per_plan_hook;
84 : 1 : explain_per_plan_hook = overexplain_per_plan_hook;
85 : 1 : }
86 : :
87 : : /*
88 : : * Get the overexplain_options structure from an ExplainState; if there is
89 : : * none, create one, attach it to the ExplainState, and return it.
90 : : */
91 : : static overexplain_options *
92 : 13 : overexplain_ensure_options(ExplainState *es)
93 : : {
94 : : overexplain_options *options;
95 : :
96 : 13 : options = GetExplainExtensionState(es, es_extension_id);
97 : :
98 [ + + ]: 13 : if (options == NULL)
99 : : {
100 michael@paquier.xyz 100 :GNC 11 : options = palloc0_object(overexplain_options);
354 rhaas@postgresql.org 101 :CBC 11 : SetExplainExtensionState(es, es_extension_id, options);
102 : : }
103 : :
104 : 13 : return options;
105 : : }
106 : :
107 : : /*
108 : : * Parse handler for EXPLAIN (DEBUG).
109 : : */
110 : : static void
111 : 6 : overexplain_debug_handler(ExplainState *es, DefElem *opt, ParseState *pstate)
112 : : {
113 : 6 : overexplain_options *options = overexplain_ensure_options(es);
114 : :
115 : 6 : options->debug = defGetBoolean(opt);
116 : 6 : }
117 : :
118 : : /*
119 : : * Parse handler for EXPLAIN (RANGE_TABLE).
120 : : */
121 : : static void
122 : 7 : overexplain_range_table_handler(ExplainState *es, DefElem *opt,
123 : : ParseState *pstate)
124 : : {
125 : 7 : overexplain_options *options = overexplain_ensure_options(es);
126 : :
127 : 7 : options->range_table = defGetBoolean(opt);
128 : 7 : }
129 : :
130 : : /*
131 : : * Print out additional per-node information as appropriate. If the user didn't
132 : : * specify any of the options we support, do nothing; else, print whatever is
133 : : * relevant to the specified options.
134 : : */
135 : : static void
136 : 40 : overexplain_per_node_hook(PlanState *planstate, List *ancestors,
137 : : const char *relationship, const char *plan_name,
138 : : ExplainState *es)
139 : : {
140 : : overexplain_options *options;
141 : 40 : Plan *plan = planstate->plan;
142 : :
352 143 [ - + ]: 40 : if (prev_explain_per_node_hook)
352 rhaas@postgresql.org 144 :UBC 0 : (*prev_explain_per_node_hook) (planstate, ancestors, relationship,
145 : : plan_name, es);
146 : :
354 rhaas@postgresql.org 147 :CBC 40 : options = GetExplainExtensionState(es, es_extension_id);
148 [ - + ]: 40 : if (options == NULL)
354 rhaas@postgresql.org 149 :UBC 0 : return;
150 : :
151 : : /*
152 : : * If the "debug" option was given, display miscellaneous fields from the
153 : : * "Plan" node that would not otherwise be displayed.
154 : : */
354 rhaas@postgresql.org 155 [ + + ]:CBC 40 : if (options->debug)
156 : : {
157 : : /*
158 : : * Normal EXPLAIN will display "Disabled: true" if the node is
159 : : * disabled; but that is based on noticing that plan->disabled_nodes
160 : : * is higher than the sum of its children; here, we display the raw
161 : : * value, for debugging purposes.
162 : : */
163 : 26 : ExplainPropertyInteger("Disabled Nodes", NULL, plan->disabled_nodes,
164 : : es);
165 : :
166 : : /*
167 : : * Normal EXPLAIN will display the parallel_aware flag; here, we show
168 : : * the parallel_safe flag as well.
169 : : */
170 : 26 : ExplainPropertyBool("Parallel Safe", plan->parallel_safe, es);
171 : :
172 : : /*
173 : : * The plan node ID isn't normally displayed, since it is only useful
174 : : * for debugging.
175 : : */
176 : 26 : ExplainPropertyInteger("Plan Node ID", NULL, plan->plan_node_id, es);
177 : :
178 : : /*
179 : : * It is difficult to explain what extParam and allParam mean in plain
180 : : * language, so we simply display these fields labelled with the
181 : : * structure member name. For compactness, the text format omits the
182 : : * display of this information when the bitmapset is empty.
183 : : */
184 [ + + + + ]: 26 : if (es->format != EXPLAIN_FORMAT_TEXT || !bms_is_empty(plan->extParam))
185 : 8 : overexplain_bitmapset("extParam", plan->extParam, es);
186 [ + + + + ]: 26 : if (es->format != EXPLAIN_FORMAT_TEXT || !bms_is_empty(plan->allParam))
187 : 8 : overexplain_bitmapset("allParam", plan->allParam, es);
188 : : }
189 : :
190 : : /*
191 : : * If the "range_table" option was specified, display information about
192 : : * the range table indexes for this node.
193 : : */
194 [ + + ]: 40 : if (options->range_table)
195 : : {
33 rhaas@postgresql.org 196 :GNC 24 : bool opened_elided_nodes = false;
197 : :
354 rhaas@postgresql.org 198 [ + - - + :CBC 24 : switch (nodeTag(plan))
+ - + + ]
199 : : {
200 : 11 : case T_SeqScan:
201 : : case T_SampleScan:
202 : : case T_IndexScan:
203 : : case T_IndexOnlyScan:
204 : : case T_BitmapHeapScan:
205 : : case T_TidScan:
206 : : case T_TidRangeScan:
207 : : case T_SubqueryScan:
208 : : case T_FunctionScan:
209 : : case T_TableFuncScan:
210 : : case T_ValuesScan:
211 : : case T_CteScan:
212 : : case T_NamedTuplestoreScan:
213 : : case T_WorkTableScan:
214 : 11 : ExplainPropertyInteger("Scan RTI", NULL,
215 : 11 : ((Scan *) plan)->scanrelid, es);
216 : 11 : break;
354 rhaas@postgresql.org 217 :UBC 0 : case T_ForeignScan:
218 : 0 : overexplain_bitmapset("Scan RTIs",
219 : : ((ForeignScan *) plan)->fs_base_relids,
220 : : es);
221 : 0 : break;
222 : 0 : case T_CustomScan:
223 : 0 : overexplain_bitmapset("Scan RTIs",
224 : : ((CustomScan *) plan)->custom_relids,
225 : : es);
226 : 0 : break;
354 rhaas@postgresql.org 227 :CBC 1 : case T_ModifyTable:
228 : 1 : ExplainPropertyInteger("Nominal RTI", NULL,
229 : 1 : ((ModifyTable *) plan)->nominalRelation, es);
230 : 1 : ExplainPropertyInteger("Exclude Relation RTI", NULL,
231 : 1 : ((ModifyTable *) plan)->exclRelRTI, es);
232 : 1 : break;
233 : 4 : case T_Append:
234 : 4 : overexplain_bitmapset("Append RTIs",
235 : : ((Append *) plan)->apprelids,
236 : : es);
33 rhaas@postgresql.org 237 :GNC 4 : overexplain_bitmapset_list("Child Append RTIs",
238 : : ((Append *) plan)->child_append_relid_sets,
239 : : es);
354 rhaas@postgresql.org 240 :CBC 4 : break;
354 rhaas@postgresql.org 241 :UBC 0 : case T_MergeAppend:
242 : 0 : overexplain_bitmapset("Append RTIs",
243 : : ((MergeAppend *) plan)->apprelids,
244 : : es);
33 rhaas@postgresql.org 245 :UNC 0 : overexplain_bitmapset_list("Child Append RTIs",
246 : : ((MergeAppend *) plan)->child_append_relid_sets,
247 : : es);
354 248 : 0 : break;
173 rhaas@postgresql.org 249 :GNC 2 : case T_Result:
250 : :
251 : : /*
252 : : * 'relids' is only meaningful when plan->lefttree is NULL,
253 : : * but if somehow it ends up set when plan->lefttree is not
254 : : * NULL, print it anyway.
255 : : */
256 [ - + ]: 2 : if (plan->lefttree == NULL ||
173 rhaas@postgresql.org 257 [ # # ]:UNC 0 : ((Result *) plan)->relids != NULL)
173 rhaas@postgresql.org 258 :GNC 2 : overexplain_bitmapset("RTIs",
259 : : ((Result *) plan)->relids,
260 : : es);
20 peter@eisentraut.org 261 :GBC 2 : break;
354 rhaas@postgresql.org 262 :CBC 6 : default:
263 : 6 : break;
264 : : }
265 : :
33 rhaas@postgresql.org 266 [ + + + + :GNC 69 : foreach_node(ElidedNode, n, es->pstmt->elidedNodes)
+ + ]
267 : : {
268 : : char *elidednodetag;
269 : :
270 [ + + ]: 21 : if (n->plan_node_id != plan->plan_node_id)
271 : 16 : continue;
272 : :
273 [ + + ]: 5 : if (!opened_elided_nodes)
274 : : {
275 : 3 : ExplainOpenGroup("Elided Nodes", "Elided Nodes", false, es);
276 : 3 : opened_elided_nodes = true;
277 : : }
278 : :
279 [ + - + - ]: 5 : switch (n->elided_type)
280 : : {
281 : 3 : case T_Append:
282 : 3 : elidednodetag = "Append";
283 : 3 : break;
33 rhaas@postgresql.org 284 :UNC 0 : case T_MergeAppend:
285 : 0 : elidednodetag = "MergeAppend";
286 : 0 : break;
33 rhaas@postgresql.org 287 :GNC 2 : case T_SubqueryScan:
288 : 2 : elidednodetag = "SubqueryScan";
289 : 2 : break;
33 rhaas@postgresql.org 290 :UNC 0 : default:
291 : 0 : elidednodetag = psprintf("%d", n->elided_type);
292 : 0 : break;
293 : : }
294 : :
33 rhaas@postgresql.org 295 :GNC 5 : ExplainOpenGroup("Elided Node", NULL, true, es);
296 : 5 : ExplainPropertyText("Elided Node Type", elidednodetag, es);
297 : 5 : overexplain_bitmapset("Elided Node RTIs", n->relids, es);
298 : 5 : ExplainCloseGroup("Elided Node", NULL, true, es);
299 : : }
300 [ + + ]: 24 : if (opened_elided_nodes)
301 : 3 : ExplainCloseGroup("Elided Nodes", "Elided Nodes", false, es);
302 : : }
303 : : }
304 : :
305 : : /*
306 : : * Print out additional per-query information as appropriate. Here again, if
307 : : * the user didn't specify any of the options implemented by this module, do
308 : : * nothing; otherwise, call the appropriate function for each specified
309 : : * option.
310 : : */
311 : : static void
354 rhaas@postgresql.org 312 :CBC 11 : overexplain_per_plan_hook(PlannedStmt *plannedstmt,
313 : : IntoClause *into,
314 : : ExplainState *es,
315 : : const char *queryString,
316 : : ParamListInfo params,
317 : : QueryEnvironment *queryEnv)
318 : : {
319 : : overexplain_options *options;
320 : :
352 321 [ - + ]: 11 : if (prev_explain_per_plan_hook)
352 rhaas@postgresql.org 322 :UBC 0 : (*prev_explain_per_plan_hook) (plannedstmt, into, es, queryString,
323 : : params, queryEnv);
324 : :
354 rhaas@postgresql.org 325 :CBC 11 : options = GetExplainExtensionState(es, es_extension_id);
326 [ - + ]: 11 : if (options == NULL)
354 rhaas@postgresql.org 327 :UBC 0 : return;
328 : :
354 rhaas@postgresql.org 329 [ + + ]:CBC 11 : if (options->debug)
330 : 6 : overexplain_debug(plannedstmt, es);
331 : :
332 [ + + ]: 11 : if (options->range_table)
333 : 7 : overexplain_range_table(plannedstmt, es);
334 : : }
335 : :
336 : : /*
337 : : * Print out various details from the PlannedStmt that wouldn't otherwise
338 : : * be displayed.
339 : : *
340 : : * We don't try to print everything here. Information that would be displayed
341 : : * anyway doesn't need to be printed again here, and things with lots of
342 : : * substructure probably should be printed via separate options, or not at all.
343 : : */
344 : : static void
345 : 6 : overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es)
346 : : {
347 : 6 : char *commandType = NULL;
348 : : StringInfoData flags;
349 : :
350 : : /* Even in text mode, we want to set this output apart as its own group. */
351 : 6 : ExplainOpenGroup("PlannedStmt", "PlannedStmt", true, es);
352 [ + + ]: 6 : if (es->format == EXPLAIN_FORMAT_TEXT)
353 : : {
354 : 5 : ExplainIndentText(es);
338 drowley@postgresql.o 355 : 5 : appendStringInfoString(es->str, "PlannedStmt:\n");
354 rhaas@postgresql.org 356 : 5 : es->indent++;
357 : : }
358 : :
359 : : /* Print the command type. */
360 [ - + - + : 6 : switch (plannedstmt->commandType)
- - - -
- ]
361 : : {
354 rhaas@postgresql.org 362 :UBC 0 : case CMD_UNKNOWN:
363 : 0 : commandType = "unknown";
364 : 0 : break;
354 rhaas@postgresql.org 365 :CBC 5 : case CMD_SELECT:
366 : 5 : commandType = "select";
367 : 5 : break;
354 rhaas@postgresql.org 368 :UBC 0 : case CMD_UPDATE:
369 : 0 : commandType = "update";
370 : 0 : break;
354 rhaas@postgresql.org 371 :CBC 1 : case CMD_INSERT:
372 : 1 : commandType = "insert";
373 : 1 : break;
354 rhaas@postgresql.org 374 :UBC 0 : case CMD_DELETE:
375 : 0 : commandType = "delete";
376 : 0 : break;
377 : 0 : case CMD_MERGE:
378 : 0 : commandType = "merge";
379 : 0 : break;
380 : 0 : case CMD_UTILITY:
381 : 0 : commandType = "utility";
382 : 0 : break;
383 : 0 : case CMD_NOTHING:
384 : 0 : commandType = "nothing";
385 : 0 : break;
386 : : }
354 rhaas@postgresql.org 387 :CBC 6 : ExplainPropertyText("Command Type", commandType, es);
388 : :
389 : : /* Print various properties as a comma-separated list of flags. */
390 : 6 : initStringInfo(&flags);
391 [ + + ]: 6 : if (plannedstmt->hasReturning)
338 drowley@postgresql.o 392 : 1 : appendStringInfoString(&flags, ", hasReturning");
354 rhaas@postgresql.org 393 [ - + ]: 6 : if (plannedstmt->hasModifyingCTE)
338 drowley@postgresql.o 394 :UBC 0 : appendStringInfoString(&flags, ", hasModifyingCTE");
354 rhaas@postgresql.org 395 [ + - ]:CBC 6 : if (plannedstmt->canSetTag)
338 drowley@postgresql.o 396 : 6 : appendStringInfoString(&flags, ", canSetTag");
354 rhaas@postgresql.org 397 [ - + ]: 6 : if (plannedstmt->transientPlan)
338 drowley@postgresql.o 398 :UBC 0 : appendStringInfoString(&flags, ", transientPlan");
354 rhaas@postgresql.org 399 [ - + ]:CBC 6 : if (plannedstmt->dependsOnRole)
338 drowley@postgresql.o 400 :UBC 0 : appendStringInfoString(&flags, ", dependsOnRole");
354 rhaas@postgresql.org 401 [ + + ]:CBC 6 : if (plannedstmt->parallelModeNeeded)
338 drowley@postgresql.o 402 : 1 : appendStringInfoString(&flags, ", parallelModeNeeded");
354 rhaas@postgresql.org 403 [ - + ]: 6 : if (flags.len == 0)
338 drowley@postgresql.o 404 :UBC 0 : appendStringInfoString(&flags, ", none");
354 rhaas@postgresql.org 405 :CBC 6 : ExplainPropertyText("Flags", flags.data + 2, es);
406 : :
407 : : /* Various lists of integers. */
408 : 6 : overexplain_bitmapset("Subplans Needing Rewind",
409 : : plannedstmt->rewindPlanIDs, es);
410 : 6 : overexplain_intlist("Relation OIDs",
411 : : plannedstmt->relationOids, es);
412 : 6 : overexplain_intlist("Executor Parameter Types",
413 : : plannedstmt->paramExecTypes, es);
414 : :
415 : : /*
416 : : * Print the statement location. (If desired, we could alternatively print
417 : : * stmt_location and stmt_len as two separate fields.)
418 : : */
419 [ - + ]: 6 : if (plannedstmt->stmt_location == -1)
354 rhaas@postgresql.org 420 :UBC 0 : ExplainPropertyText("Parse Location", "Unknown", es);
354 rhaas@postgresql.org 421 [ + - ]:CBC 6 : else if (plannedstmt->stmt_len == 0)
422 : 6 : ExplainPropertyText("Parse Location",
423 : 6 : psprintf("%d to end", plannedstmt->stmt_location),
424 : : es);
425 : : else
354 rhaas@postgresql.org 426 :UBC 0 : ExplainPropertyText("Parse Location",
427 : 0 : psprintf("%d for %d bytes",
428 : : plannedstmt->stmt_location,
429 : : plannedstmt->stmt_len),
430 : : es);
431 : :
432 : : /* Done with this group. */
354 rhaas@postgresql.org 433 [ + + ]:CBC 6 : if (es->format == EXPLAIN_FORMAT_TEXT)
434 : 5 : es->indent--;
435 : 6 : ExplainCloseGroup("PlannedStmt", "PlannedStmt", true, es);
436 : 6 : }
437 : :
438 : : /*
439 : : * Provide detailed information about the contents of the PlannedStmt's
440 : : * range table.
441 : : */
442 : : static void
443 : 7 : overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
444 : : {
445 : : Index rti;
33 rhaas@postgresql.org 446 :GNC 7 : ListCell *lc_subrtinfo = list_head(plannedstmt->subrtinfos);
447 : 7 : SubPlanRTInfo *rtinfo = NULL;
448 : :
449 : : /* Open group, one entry per RangeTblEntry */
354 rhaas@postgresql.org 450 :CBC 7 : ExplainOpenGroup("Range Table", "Range Table", false, es);
451 : :
452 : : /* Iterate over the range table */
453 [ + + ]: 32 : for (rti = 1; rti <= list_length(plannedstmt->rtable); ++rti)
454 : : {
455 : 25 : RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
456 : 25 : char *kind = NULL;
457 : : char *relkind;
458 : : SubPlanRTInfo *next_rtinfo;
459 : :
460 : : /* Advance to next SubRTInfo, if it's time. */
33 rhaas@postgresql.org 461 [ + + ]:GNC 25 : if (lc_subrtinfo != NULL)
462 : : {
463 : 10 : next_rtinfo = lfirst(lc_subrtinfo);
464 [ + + ]: 10 : if (rti > next_rtinfo->rtoffset)
465 : : {
466 : 2 : rtinfo = next_rtinfo;
467 : 2 : lc_subrtinfo = lnext(plannedstmt->subrtinfos, lc_subrtinfo);
468 : : }
469 : : }
470 : :
471 : : /* NULL entries are possible; skip them */
354 rhaas@postgresql.org 472 [ - + ]:CBC 25 : if (rte == NULL)
354 rhaas@postgresql.org 473 :UBC 0 : continue;
474 : :
475 : : /* Translate rtekind to a string */
354 rhaas@postgresql.org 476 [ + + - - :CBC 25 : switch (rte->rtekind)
- - - - +
+ - ]
477 : : {
478 : 19 : case RTE_RELATION:
479 : 19 : kind = "relation";
480 : 19 : break;
354 rhaas@postgresql.org 481 :GBC 2 : case RTE_SUBQUERY:
482 : 2 : kind = "subquery";
483 : 2 : break;
354 rhaas@postgresql.org 484 :UBC 0 : case RTE_JOIN:
485 : 0 : kind = "join";
486 : 0 : break;
487 : 0 : case RTE_FUNCTION:
488 : 0 : kind = "function";
489 : 0 : break;
490 : 0 : case RTE_TABLEFUNC:
491 : 0 : kind = "tablefunc";
492 : 0 : break;
493 : 0 : case RTE_VALUES:
494 : 0 : kind = "values";
495 : 0 : break;
496 : 0 : case RTE_CTE:
497 : 0 : kind = "cte";
498 : 0 : break;
499 : 0 : case RTE_NAMEDTUPLESTORE:
500 : 0 : kind = "namedtuplestore";
501 : 0 : break;
354 rhaas@postgresql.org 502 :CBC 2 : case RTE_RESULT:
503 : 2 : kind = "result";
504 : 2 : break;
505 : 2 : case RTE_GROUP:
506 : 2 : kind = "group";
507 : 2 : break;
508 : : }
509 : :
510 : : /* Begin group for this specific RTE */
511 : 25 : ExplainOpenGroup("Range Table Entry", NULL, true, es);
512 : :
513 : : /*
514 : : * In text format, the summary line displays the range table index and
515 : : * rtekind, plus indications if rte->inh and/or rte->inFromCl are set.
516 : : * In other formats, we display those as separate properties.
517 : : */
518 [ + + ]: 25 : if (es->format == EXPLAIN_FORMAT_TEXT)
519 : : {
520 : 21 : ExplainIndentText(es);
521 : 42 : appendStringInfo(es->str, "RTI %u (%s%s%s):\n", rti, kind,
522 [ + + ]: 21 : rte->inh ? ", inherited" : "",
523 [ + + ]: 21 : rte->inFromCl ? ", in-from-clause" : "");
524 : 21 : es->indent++;
525 : : }
526 : : else
527 : : {
528 : 4 : ExplainPropertyUInteger("RTI", NULL, rti, es);
529 : 4 : ExplainPropertyText("Kind", kind, es);
530 : 4 : ExplainPropertyBool("Inherited", rte->inh, es);
531 : 4 : ExplainPropertyBool("In From Clause", rte->inFromCl, es);
532 : : }
533 : :
534 : : /*
535 : : * Indicate which subplan is the origin of which RTE. Note dummy
536 : : * subplans. Here again, we crunch more onto one line in text format.
537 : : */
33 rhaas@postgresql.org 538 [ + + ]:GNC 25 : if (rtinfo != NULL)
539 : : {
540 [ + - ]: 4 : if (es->format == EXPLAIN_FORMAT_TEXT)
541 : : {
542 [ + - ]: 4 : if (!rtinfo->dummy)
543 : 4 : ExplainPropertyText("Subplan", rtinfo->plan_name, es);
544 : : else
33 rhaas@postgresql.org 545 :UNC 0 : ExplainPropertyText("Subplan",
546 : 0 : psprintf("%s (dummy)",
547 : : rtinfo->plan_name), es);
548 : : }
549 : : else
550 : : {
551 : 0 : ExplainPropertyText("Subplan", rtinfo->plan_name, es);
552 : 0 : ExplainPropertyBool("Subplan Is Dummy", rtinfo->dummy, es);
553 : : }
554 : : }
555 : :
556 : : /* rte->alias is optional; rte->eref is requested */
354 rhaas@postgresql.org 557 [ + + ]:CBC 25 : if (rte->alias != NULL)
558 : 14 : overexplain_alias("Alias", rte->alias, es);
559 : 25 : overexplain_alias("Eref", rte->eref, es);
560 : :
561 : : /*
562 : : * We adhere to the usual EXPLAIN convention that schema names are
563 : : * displayed only in verbose mode, and we emit nothing if there is no
564 : : * relation OID.
565 : : */
566 [ + + ]: 25 : if (rte->relid != 0)
567 : : {
568 : : const char *relname;
569 : : const char *qualname;
570 : :
571 : 19 : relname = quote_identifier(get_rel_name(rte->relid));
572 : :
573 [ - + ]: 19 : if (es->verbose)
574 : : {
354 rhaas@postgresql.org 575 :UBC 0 : Oid nspoid = get_rel_namespace(rte->relid);
576 : : char *nspname;
577 : :
578 : 0 : nspname = get_namespace_name_or_temp(nspoid);
579 : 0 : qualname = psprintf("%s.%s", quote_identifier(nspname),
580 : : relname);
581 : : }
582 : : else
354 rhaas@postgresql.org 583 :CBC 19 : qualname = relname;
584 : :
585 : 19 : ExplainPropertyText("Relation", qualname, es);
586 : : }
587 : :
588 : : /* Translate relkind, if any, to a string */
589 [ + - - - : 25 : switch (rte->relkind)
- - - - +
- + - ]
590 : : {
591 : 11 : case RELKIND_RELATION:
592 : 11 : relkind = "relation";
593 : 11 : break;
354 rhaas@postgresql.org 594 :UBC 0 : case RELKIND_INDEX:
595 : 0 : relkind = "index";
596 : 0 : break;
597 : 0 : case RELKIND_SEQUENCE:
598 : 0 : relkind = "sequence";
599 : 0 : break;
600 : 0 : case RELKIND_TOASTVALUE:
601 : 0 : relkind = "toastvalue";
602 : 0 : break;
603 : 0 : case RELKIND_VIEW:
604 : 0 : relkind = "view";
605 : 0 : break;
606 : 0 : case RELKIND_MATVIEW:
607 : 0 : relkind = "matview";
608 : 0 : break;
609 : 0 : case RELKIND_COMPOSITE_TYPE:
610 : 0 : relkind = "composite_type";
611 : 0 : break;
612 : 0 : case RELKIND_FOREIGN_TABLE:
613 : 0 : relkind = "foreign_table";
614 : 0 : break;
354 rhaas@postgresql.org 615 :CBC 8 : case RELKIND_PARTITIONED_TABLE:
330 michael@paquier.xyz 616 : 8 : relkind = "partitioned_table";
354 rhaas@postgresql.org 617 : 8 : break;
354 rhaas@postgresql.org 618 :UBC 0 : case RELKIND_PARTITIONED_INDEX:
330 michael@paquier.xyz 619 : 0 : relkind = "partitioned_index";
354 rhaas@postgresql.org 620 : 0 : break;
354 rhaas@postgresql.org 621 :CBC 6 : case '\0':
622 : 6 : relkind = NULL;
623 : 6 : break;
354 rhaas@postgresql.org 624 :UBC 0 : default:
625 : 0 : relkind = psprintf("%c", rte->relkind);
626 : 0 : break;
627 : : }
628 : :
629 : : /* If there is a relkind, show it */
354 rhaas@postgresql.org 630 [ + + ]:CBC 25 : if (relkind != NULL)
631 : 19 : ExplainPropertyText("Relation Kind", relkind, es);
632 : :
633 : : /* If there is a lock mode, show it */
634 [ + + ]: 25 : if (rte->rellockmode != 0)
635 : 19 : ExplainPropertyText("Relation Lock Mode",
636 : : GetLockmodeName(DEFAULT_LOCKMETHOD,
637 : : rte->rellockmode), es);
638 : :
639 : : /*
640 : : * If there is a perminfoindex, show it. We don't try to display
641 : : * information from the RTEPermissionInfo node here because they are
642 : : * just indexes plannedstmt->permInfos which could be separately
643 : : * dumped if someone wants to add EXPLAIN (PERMISSIONS) or similar.
644 : : */
645 [ + + ]: 25 : if (rte->perminfoindex != 0)
646 : 8 : ExplainPropertyInteger("Permission Info Index", NULL,
647 : 8 : rte->perminfoindex, es);
648 : :
649 : : /*
650 : : * add_rte_to_flat_rtable will clear rte->tablesample and
651 : : * rte->subquery in the finished plan, so skip those fields.
652 : : *
653 : : * However, the security_barrier flag is not shown by the core code,
654 : : * so let's print it here.
655 : : */
656 [ + + - + ]: 25 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->security_barrier)
657 : 4 : ExplainPropertyBool("Security Barrier", rte->security_barrier, es);
658 : :
659 : : /*
660 : : * If this is a join, print out the fields that are specifically valid
661 : : * for joins.
662 : : */
663 [ - + ]: 25 : if (rte->rtekind == RTE_JOIN)
664 : : {
665 : : char *jointype;
666 : :
354 rhaas@postgresql.org 667 [ # # # # :UBC 0 : switch (rte->jointype)
# # # #
# ]
668 : : {
669 : 0 : case JOIN_INNER:
670 : 0 : jointype = "Inner";
671 : 0 : break;
672 : 0 : case JOIN_LEFT:
673 : 0 : jointype = "Left";
674 : 0 : break;
675 : 0 : case JOIN_FULL:
676 : 0 : jointype = "Full";
677 : 0 : break;
678 : 0 : case JOIN_RIGHT:
679 : 0 : jointype = "Right";
680 : 0 : break;
681 : 0 : case JOIN_SEMI:
682 : 0 : jointype = "Semi";
683 : 0 : break;
684 : 0 : case JOIN_ANTI:
685 : 0 : jointype = "Anti";
686 : 0 : break;
687 : 0 : case JOIN_RIGHT_SEMI:
688 : 0 : jointype = "Right Semi";
689 : 0 : break;
690 : 0 : case JOIN_RIGHT_ANTI:
691 : 0 : jointype = "Right Anti";
692 : 0 : break;
693 : 0 : default:
694 : 0 : jointype = "???";
695 : 0 : break;
696 : : }
697 : :
698 : : /* Join type */
699 : 0 : ExplainPropertyText("Join Type", jointype, es);
700 : :
701 : : /* # of JOIN USING columns */
702 [ # # # # ]: 0 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->joinmergedcols != 0)
703 : 0 : ExplainPropertyInteger("JOIN USING Columns", NULL,
704 : 0 : rte->joinmergedcols, es);
705 : :
706 : : /*
707 : : * add_rte_to_flat_rtable will clear joinaliasvars, joinleftcols,
708 : : * joinrightcols, and join_using_alias here, so skip those fields.
709 : : */
710 : : }
711 : :
712 : : /*
713 : : * add_rte_to_flat_rtable will clear functions, tablefunc, and
714 : : * values_lists, but we can display funcordinality.
715 : : */
354 rhaas@postgresql.org 716 [ - + ]:CBC 25 : if (rte->rtekind == RTE_FUNCTION)
354 rhaas@postgresql.org 717 :UBC 0 : ExplainPropertyBool("WITH ORDINALITY", rte->funcordinality, es);
718 : :
719 : : /*
720 : : * If this is a CTE, print out CTE-related properties.
721 : : */
354 rhaas@postgresql.org 722 [ - + ]:CBC 25 : if (rte->rtekind == RTE_CTE)
723 : : {
354 rhaas@postgresql.org 724 :UBC 0 : ExplainPropertyText("CTE Name", rte->ctename, es);
725 : 0 : ExplainPropertyUInteger("CTE Levels Up", NULL, rte->ctelevelsup,
726 : : es);
727 : 0 : ExplainPropertyBool("CTE Self-Reference", rte->self_reference, es);
728 : : }
729 : :
730 : : /*
731 : : * add_rte_to_flat_rtable will clear coltypes, coltypmods, and
732 : : * colcollations, so skip those fields.
733 : : *
734 : : * If this is an ephemeral named relation, print out ENR-related
735 : : * properties.
736 : : */
354 rhaas@postgresql.org 737 [ - + ]:CBC 25 : if (rte->rtekind == RTE_NAMEDTUPLESTORE)
738 : : {
354 rhaas@postgresql.org 739 :UBC 0 : ExplainPropertyText("ENR Name", rte->enrname, es);
740 : 0 : ExplainPropertyFloat("ENR Tuples", NULL, rte->enrtuples, 0, es);
741 : : }
742 : :
743 : : /*
744 : : * add_rte_to_flat_rtable will clear groupexprs and securityQuals, so
745 : : * skip that field. We have handled inFromCl above, so the only thing
746 : : * left to handle here is rte->lateral.
747 : : */
354 rhaas@postgresql.org 748 [ + + - + ]:CBC 25 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->lateral)
749 : 4 : ExplainPropertyBool("Lateral", rte->lateral, es);
750 : :
751 : : /* Done with this RTE */
752 [ + + ]: 25 : if (es->format == EXPLAIN_FORMAT_TEXT)
753 : 21 : es->indent--;
754 : 25 : ExplainCloseGroup("Range Table Entry", NULL, true, es);
755 : : }
756 : :
757 : : /* Print PlannedStmt fields that contain RTIs. */
758 [ + + ]: 7 : if (es->format != EXPLAIN_FORMAT_TEXT ||
759 [ + + ]: 6 : !bms_is_empty(plannedstmt->unprunableRelids))
760 : 6 : overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
761 : : es);
762 [ + + ]: 7 : if (es->format != EXPLAIN_FORMAT_TEXT ||
763 [ + + ]: 6 : plannedstmt->resultRelations != NIL)
764 : 2 : overexplain_intlist("Result RTIs", plannedstmt->resultRelations, es);
765 : :
766 : : /* Close group, we're all done */
767 : 7 : ExplainCloseGroup("Range Table", "Range Table", false, es);
768 : 7 : }
769 : :
770 : : /*
771 : : * Emit a text property describing the contents of an Alias.
772 : : *
773 : : * Column lists can be quite long here, so perhaps we should have an option
774 : : * to limit the display length by # of column or # of characters, but for
775 : : * now, just display everything.
776 : : */
777 : : static void
778 : 39 : overexplain_alias(const char *qlabel, Alias *alias, ExplainState *es)
779 : : {
780 : : StringInfoData buf;
781 : 39 : bool first = true;
782 : :
783 [ - + ]: 39 : Assert(alias != NULL);
784 : :
785 : 39 : initStringInfo(&buf);
786 : 39 : appendStringInfo(&buf, "%s (", quote_identifier(alias->aliasname));
787 : :
788 [ + + + + : 176 : foreach_node(String, cn, alias->colnames)
+ + ]
789 : : {
790 [ + + ]: 98 : appendStringInfo(&buf, "%s%s",
791 : : first ? "" : ", ",
792 : 98 : quote_identifier(cn->sval));
793 : 98 : first = false;
794 : : }
795 : :
796 : 39 : appendStringInfoChar(&buf, ')');
797 : 39 : ExplainPropertyText(qlabel, buf.data, es);
798 : 39 : pfree(buf.data);
799 : 39 : }
800 : :
801 : : /*
802 : : * Emit a text property describing the contents of a bitmapset -- either a
803 : : * space-separated list of integer members, or the word "none" if the bitmapset
804 : : * is empty.
805 : : */
806 : : static void
807 : 39 : overexplain_bitmapset(const char *qlabel, Bitmapset *bms, ExplainState *es)
808 : : {
809 : 39 : int x = -1;
810 : :
811 : : StringInfoData buf;
812 : :
813 [ + + ]: 39 : if (bms_is_empty(bms))
814 : : {
815 : 16 : ExplainPropertyText(qlabel, "none", es);
816 : 16 : return;
817 : : }
818 : :
819 : 23 : initStringInfo(&buf);
820 [ + + ]: 59 : while ((x = bms_next_member(bms, x)) >= 0)
821 : 36 : appendStringInfo(&buf, " %d", x);
822 [ - + ]: 23 : Assert(buf.data[0] == ' ');
823 : 23 : ExplainPropertyText(qlabel, buf.data + 1, es);
824 : 23 : pfree(buf.data);
825 : : }
826 : :
827 : : /*
828 : : * Emit a text property describing the contents of a list of bitmapsets.
829 : : * If a bitmapset contains exactly 1 member, we just print an integer;
830 : : * otherwise, we surround the list of members by parentheses.
831 : : *
832 : : * If there are no bitmapsets in the list, we print the word "none".
833 : : */
834 : : static void
33 rhaas@postgresql.org 835 :GNC 4 : overexplain_bitmapset_list(const char *qlabel, List *bms_list,
836 : : ExplainState *es)
837 : : {
838 : : StringInfoData buf;
839 : :
840 : 4 : initStringInfo(&buf);
841 : :
842 [ - + - - : 8 : foreach_node(Bitmapset, bms, bms_list)
+ + ]
843 : : {
33 rhaas@postgresql.org 844 [ # # ]:UNC 0 : if (bms_membership(bms) == BMS_SINGLETON)
845 : 0 : appendStringInfo(&buf, " %d", bms_singleton_member(bms));
846 : : else
847 : : {
848 : 0 : int x = -1;
849 : 0 : bool first = true;
850 : :
851 : 0 : appendStringInfoString(&buf, " (");
852 [ # # ]: 0 : while ((x = bms_next_member(bms, x)) >= 0)
853 : : {
854 [ # # ]: 0 : if (first)
855 : 0 : first = false;
856 : : else
857 : 0 : appendStringInfoChar(&buf, ' ');
858 : 0 : appendStringInfo(&buf, "%d", x);
859 : : }
860 : 0 : appendStringInfoChar(&buf, ')');
861 : : }
862 : : }
863 : :
33 rhaas@postgresql.org 864 [ + - ]:GNC 4 : if (buf.len == 0)
865 : : {
866 : 4 : ExplainPropertyText(qlabel, "none", es);
867 : 4 : return;
868 : : }
869 : :
33 rhaas@postgresql.org 870 [ # # ]:UNC 0 : Assert(buf.data[0] == ' ');
871 : 0 : ExplainPropertyText(qlabel, buf.data + 1, es);
872 : 0 : pfree(buf.data);
873 : : }
874 : :
875 : : /*
876 : : * Emit a text property describing the contents of a list of integers, OIDs,
877 : : * or XIDs -- either a space-separated list of integer members, or the word
878 : : * "none" if the list is empty.
879 : : */
880 : : static void
354 rhaas@postgresql.org 881 :CBC 14 : overexplain_intlist(const char *qlabel, List *list, ExplainState *es)
882 : : {
883 : : StringInfoData buf;
884 : :
885 : 14 : initStringInfo(&buf);
886 : :
887 [ + + ]: 14 : if (list == NIL)
888 : : {
889 : 6 : ExplainPropertyText(qlabel, "none", es);
890 : 6 : return;
891 : : }
892 : :
893 [ + + ]: 8 : if (IsA(list, IntList))
894 : : {
895 [ + - + + : 3 : foreach_int(i, list)
+ + ]
896 : 1 : appendStringInfo(&buf, " %d", i);
897 : : }
898 [ + - ]: 7 : else if (IsA(list, OidList))
899 : : {
900 [ + - + + : 33 : foreach_oid(o, list)
+ + ]
901 : 19 : appendStringInfo(&buf, " %u", o);
902 : : }
354 rhaas@postgresql.org 903 [ # # ]:UBC 0 : else if (IsA(list, XidList))
904 : : {
905 [ # # # # : 0 : foreach_xid(x, list)
# # ]
906 : 0 : appendStringInfo(&buf, " %u", x);
907 : : }
908 : : else
909 : : {
338 drowley@postgresql.o 910 : 0 : appendStringInfoString(&buf, " not an integer list");
354 rhaas@postgresql.org 911 : 0 : Assert(false);
912 : : }
913 : :
354 rhaas@postgresql.org 914 [ + - ]:CBC 8 : if (buf.len > 0)
915 : 8 : ExplainPropertyText(qlabel, buf.data + 1, es);
916 : :
917 : 8 : pfree(buf.data);
918 : : }
|