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