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 : :
403 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
405 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. */
29 rhaas@postgresql.org 76 :GNC 14 : RegisterExtensionExplainOption("debug", overexplain_debug_handler,
77 : : GUCCheckBooleanExplainOption);
405 rhaas@postgresql.org 78 :CBC 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 : : {
151 michael@paquier.xyz 102 :GNC 14 : options = palloc0_object(overexplain_options);
405 rhaas@postgresql.org 103 :CBC 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 : :
403 145 [ - + ]: 59 : if (prev_explain_per_node_hook)
403 rhaas@postgresql.org 146 :UBC 0 : (*prev_explain_per_node_hook) (planstate, ancestors, relationship,
147 : : plan_name, es);
148 : :
405 rhaas@postgresql.org 149 :CBC 59 : options = GetExplainExtensionState(es, es_extension_id);
150 [ + + ]: 59 : if (options == NULL)
405 rhaas@postgresql.org 151 :GBC 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 : : */
405 rhaas@postgresql.org 157 [ + + ]:CBC 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 : : {
84 rhaas@postgresql.org 198 :GNC 32 : bool opened_elided_nodes = false;
199 : :
405 rhaas@postgresql.org 200 [ + - - + :CBC 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;
405 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;
405 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 : 6 : case T_Append:
236 : 6 : overexplain_bitmapset("Append RTIs",
237 : : ((Append *) plan)->apprelids,
238 : : es);
84 rhaas@postgresql.org 239 :GNC 6 : overexplain_bitmapset_list("Child Append RTIs",
240 : : ((Append *) plan)->child_append_relid_sets,
241 : : es);
405 rhaas@postgresql.org 242 :CBC 6 : break;
405 rhaas@postgresql.org 243 :UBC 0 : case T_MergeAppend:
244 : 0 : overexplain_bitmapset("Append RTIs",
245 : : ((MergeAppend *) plan)->apprelids,
246 : : es);
84 rhaas@postgresql.org 247 :UNC 0 : overexplain_bitmapset_list("Child Append RTIs",
248 : : ((MergeAppend *) plan)->child_append_relid_sets,
249 : : es);
405 250 : 0 : break;
224 rhaas@postgresql.org 251 :GNC 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 ||
224 rhaas@postgresql.org 259 [ # # ]:UNC 0 : ((Result *) plan)->relids != NULL)
224 rhaas@postgresql.org 260 :GNC 2 : overexplain_bitmapset("RTIs",
261 : : ((Result *) plan)->relids,
262 : : es);
71 peter@eisentraut.org 263 :GBC 2 : break;
405 rhaas@postgresql.org 264 :CBC 8 : default:
265 : 8 : break;
266 : : }
267 : :
84 rhaas@postgresql.org 268 [ + + + + :GNC 91 : foreach_node(ElidedNode, n, es->pstmt->elidedNodes)
+ + ]
269 : : {
270 : : char *elidednodetag;
271 : :
272 [ + + ]: 27 : if (n->plan_node_id != plan->plan_node_id)
273 : 20 : continue;
274 : :
275 [ + + ]: 7 : if (!opened_elided_nodes)
276 : : {
277 : 5 : ExplainOpenGroup("Elided Nodes", "Elided Nodes", false, es);
278 : 5 : opened_elided_nodes = true;
279 : : }
280 : :
281 [ + - + - ]: 7 : switch (n->elided_type)
282 : : {
283 : 3 : case T_Append:
284 : 3 : elidednodetag = "Append";
285 : 3 : break;
84 rhaas@postgresql.org 286 :UNC 0 : case T_MergeAppend:
287 : 0 : elidednodetag = "MergeAppend";
288 : 0 : break;
84 rhaas@postgresql.org 289 :GNC 4 : case T_SubqueryScan:
290 : 4 : elidednodetag = "SubqueryScan";
291 : 4 : break;
84 rhaas@postgresql.org 292 :UNC 0 : default:
293 : 0 : elidednodetag = psprintf("%d", n->elided_type);
294 : 0 : break;
295 : : }
296 : :
84 rhaas@postgresql.org 297 :GNC 7 : ExplainOpenGroup("Elided Node", NULL, true, es);
298 : 7 : ExplainPropertyText("Elided Node Type", elidednodetag, es);
299 : 7 : overexplain_bitmapset("Elided Node RTIs", n->relids, es);
300 : 7 : ExplainCloseGroup("Elided Node", NULL, true, es);
301 : : }
302 [ + + ]: 32 : if (opened_elided_nodes)
303 : 5 : ExplainCloseGroup("Elided Nodes", "Elided Nodes", false, es);
304 : : }
305 : : }
306 : :
307 : : /*
308 : : * Print out additional per-query information as appropriate. Here again, if
309 : : * the user didn't specify any of the options implemented by this module, do
310 : : * nothing; otherwise, call the appropriate function for each specified
311 : : * option.
312 : : */
313 : : static void
405 rhaas@postgresql.org 314 :CBC 24 : overexplain_per_plan_hook(PlannedStmt *plannedstmt,
315 : : IntoClause *into,
316 : : ExplainState *es,
317 : : const char *queryString,
318 : : ParamListInfo params,
319 : : QueryEnvironment *queryEnv)
320 : : {
321 : : overexplain_options *options;
322 : :
403 323 [ - + ]: 24 : if (prev_explain_per_plan_hook)
403 rhaas@postgresql.org 324 :UBC 0 : (*prev_explain_per_plan_hook) (plannedstmt, into, es, queryString,
325 : : params, queryEnv);
326 : :
405 rhaas@postgresql.org 327 :CBC 24 : options = GetExplainExtensionState(es, es_extension_id);
328 [ + + ]: 24 : if (options == NULL)
405 rhaas@postgresql.org 329 :GBC 10 : return;
330 : :
405 rhaas@postgresql.org 331 [ + + ]:CBC 14 : if (options->debug)
332 : 7 : overexplain_debug(plannedstmt, es);
333 : :
334 [ + + ]: 14 : if (options->range_table)
335 : 9 : overexplain_range_table(plannedstmt, es);
336 : : }
337 : :
338 : : /*
339 : : * Print out various details from the PlannedStmt that wouldn't otherwise
340 : : * be displayed.
341 : : *
342 : : * We don't try to print everything here. Information that would be displayed
343 : : * anyway doesn't need to be printed again here, and things with lots of
344 : : * substructure probably should be printed via separate options, or not at all.
345 : : */
346 : : static void
347 : 7 : overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es)
348 : : {
349 : 7 : char *commandType = NULL;
350 : : StringInfoData flags;
351 : :
352 : : /* Even in text mode, we want to set this output apart as its own group. */
353 : 7 : ExplainOpenGroup("PlannedStmt", "PlannedStmt", true, es);
354 [ + + ]: 7 : if (es->format == EXPLAIN_FORMAT_TEXT)
355 : : {
356 : 6 : ExplainIndentText(es);
389 drowley@postgresql.o 357 : 6 : appendStringInfoString(es->str, "PlannedStmt:\n");
405 rhaas@postgresql.org 358 : 6 : es->indent++;
359 : : }
360 : :
361 : : /* Print the command type. */
362 [ - + - + : 7 : switch (plannedstmt->commandType)
- - - -
- ]
363 : : {
405 rhaas@postgresql.org 364 :UBC 0 : case CMD_UNKNOWN:
365 : 0 : commandType = "unknown";
366 : 0 : break;
405 rhaas@postgresql.org 367 :CBC 6 : case CMD_SELECT:
368 : 6 : commandType = "select";
369 : 6 : break;
405 rhaas@postgresql.org 370 :UBC 0 : case CMD_UPDATE:
371 : 0 : commandType = "update";
372 : 0 : break;
405 rhaas@postgresql.org 373 :CBC 1 : case CMD_INSERT:
374 : 1 : commandType = "insert";
375 : 1 : break;
405 rhaas@postgresql.org 376 :UBC 0 : case CMD_DELETE:
377 : 0 : commandType = "delete";
378 : 0 : break;
379 : 0 : case CMD_MERGE:
380 : 0 : commandType = "merge";
381 : 0 : break;
382 : 0 : case CMD_UTILITY:
383 : 0 : commandType = "utility";
384 : 0 : break;
385 : 0 : case CMD_NOTHING:
386 : 0 : commandType = "nothing";
387 : 0 : break;
388 : : }
405 rhaas@postgresql.org 389 :CBC 7 : ExplainPropertyText("Command Type", commandType, es);
390 : :
391 : : /* Print various properties as a comma-separated list of flags. */
392 : 7 : initStringInfo(&flags);
393 [ + + ]: 7 : if (plannedstmt->hasReturning)
389 drowley@postgresql.o 394 : 1 : appendStringInfoString(&flags, ", hasReturning");
405 rhaas@postgresql.org 395 [ - + ]: 7 : if (plannedstmt->hasModifyingCTE)
389 drowley@postgresql.o 396 :UBC 0 : appendStringInfoString(&flags, ", hasModifyingCTE");
405 rhaas@postgresql.org 397 [ + - ]:CBC 7 : if (plannedstmt->canSetTag)
389 drowley@postgresql.o 398 : 7 : appendStringInfoString(&flags, ", canSetTag");
405 rhaas@postgresql.org 399 [ - + ]: 7 : if (plannedstmt->transientPlan)
389 drowley@postgresql.o 400 :UBC 0 : appendStringInfoString(&flags, ", transientPlan");
405 rhaas@postgresql.org 401 [ - + ]:CBC 7 : if (plannedstmt->dependsOnRole)
389 drowley@postgresql.o 402 :UBC 0 : appendStringInfoString(&flags, ", dependsOnRole");
405 rhaas@postgresql.org 403 [ + + ]:CBC 7 : if (plannedstmt->parallelModeNeeded)
389 drowley@postgresql.o 404 : 1 : appendStringInfoString(&flags, ", parallelModeNeeded");
405 rhaas@postgresql.org 405 [ - + ]: 7 : if (flags.len == 0)
389 drowley@postgresql.o 406 :UBC 0 : appendStringInfoString(&flags, ", none");
405 rhaas@postgresql.org 407 :CBC 7 : ExplainPropertyText("Flags", flags.data + 2, es);
408 : :
409 : : /* Various lists of integers. */
410 : 7 : overexplain_bitmapset("Subplans Needing Rewind",
411 : : plannedstmt->rewindPlanIDs, es);
412 : 7 : overexplain_intlist("Relation OIDs",
413 : : plannedstmt->relationOids, es);
414 : 7 : overexplain_intlist("Executor Parameter Types",
415 : : plannedstmt->paramExecTypes, es);
416 : :
417 : : /*
418 : : * Print the statement location. (If desired, we could alternatively print
419 : : * stmt_location and stmt_len as two separate fields.)
420 : : */
421 [ - + ]: 7 : if (plannedstmt->stmt_location == -1)
405 rhaas@postgresql.org 422 :UBC 0 : ExplainPropertyText("Parse Location", "Unknown", es);
405 rhaas@postgresql.org 423 [ + + ]:CBC 7 : else if (plannedstmt->stmt_len == 0)
424 : 6 : ExplainPropertyText("Parse Location",
425 : 6 : psprintf("%d to end", plannedstmt->stmt_location),
426 : : es);
427 : : else
405 rhaas@postgresql.org 428 :GBC 1 : ExplainPropertyText("Parse Location",
429 : 1 : psprintf("%d for %d bytes",
430 : : plannedstmt->stmt_location,
431 : : plannedstmt->stmt_len),
432 : : es);
433 : :
434 : : /* Done with this group. */
405 rhaas@postgresql.org 435 [ + + ]:CBC 7 : if (es->format == EXPLAIN_FORMAT_TEXT)
436 : 6 : es->indent--;
437 : 7 : ExplainCloseGroup("PlannedStmt", "PlannedStmt", true, es);
438 : 7 : }
439 : :
440 : : /*
441 : : * Provide detailed information about the contents of the PlannedStmt's
442 : : * range table.
443 : : */
444 : : static void
445 : 9 : overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
446 : : {
447 : : Index rti;
84 rhaas@postgresql.org 448 :GNC 9 : ListCell *lc_subrtinfo = list_head(plannedstmt->subrtinfos);
449 : 9 : SubPlanRTInfo *rtinfo = NULL;
450 : :
451 : : /* Open group, one entry per RangeTblEntry */
405 rhaas@postgresql.org 452 :CBC 9 : ExplainOpenGroup("Range Table", "Range Table", false, es);
453 : :
454 : : /* Iterate over the range table */
455 [ + + ]: 43 : for (rti = 1; rti <= list_length(plannedstmt->rtable); ++rti)
456 : : {
457 : 34 : RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
458 : 34 : char *kind = NULL;
459 : : char *relkind;
460 : : SubPlanRTInfo *next_rtinfo;
461 : :
462 : : /* Advance to next SubPlanRTInfo, if it's time. */
84 rhaas@postgresql.org 463 [ + + ]:GNC 34 : if (lc_subrtinfo != NULL)
464 : : {
465 : 15 : next_rtinfo = lfirst(lc_subrtinfo);
466 [ + + ]: 15 : if (rti > next_rtinfo->rtoffset)
467 : : {
468 : 4 : rtinfo = next_rtinfo;
469 : 4 : lc_subrtinfo = lnext(plannedstmt->subrtinfos, lc_subrtinfo);
470 : : }
471 : : }
472 : :
473 : : /* NULL entries are possible; skip them */
405 rhaas@postgresql.org 474 [ - + ]:CBC 34 : if (rte == NULL)
405 rhaas@postgresql.org 475 :UBC 0 : continue;
476 : :
477 : : /* Translate rtekind to a string */
405 rhaas@postgresql.org 478 [ + + - - :CBC 34 : switch (rte->rtekind)
- - - - +
+ - - ]
479 : : {
480 : 24 : case RTE_RELATION:
481 : 24 : kind = "relation";
482 : 24 : break;
405 rhaas@postgresql.org 483 :GBC 5 : case RTE_SUBQUERY:
484 : 5 : kind = "subquery";
485 : 5 : break;
405 rhaas@postgresql.org 486 :UBC 0 : case RTE_JOIN:
487 : 0 : kind = "join";
488 : 0 : break;
489 : 0 : case RTE_FUNCTION:
490 : 0 : kind = "function";
491 : 0 : break;
492 : 0 : case RTE_TABLEFUNC:
493 : 0 : kind = "tablefunc";
494 : 0 : break;
495 : 0 : case RTE_VALUES:
496 : 0 : kind = "values";
497 : 0 : break;
498 : 0 : case RTE_CTE:
499 : 0 : kind = "cte";
500 : 0 : break;
501 : 0 : case RTE_NAMEDTUPLESTORE:
502 : 0 : kind = "namedtuplestore";
503 : 0 : break;
405 rhaas@postgresql.org 504 :CBC 2 : case RTE_RESULT:
505 : 2 : kind = "result";
506 : 2 : break;
507 : 3 : case RTE_GROUP:
508 : 3 : kind = "group";
509 : 3 : break;
50 peter@eisentraut.org 510 :UNC 0 : case RTE_GRAPH_TABLE:
511 : :
512 : : /*
513 : : * We should not see RTE of this kind here since property
514 : : * graph RTE gets converted to subquery RTE in
515 : : * rewriteGraphTable(). In case we decide not to do the
516 : : * conversion and leave RTE kind unchanged in future, print
517 : : * correct name of RTE kind.
518 : : */
519 : 0 : kind = "graph_table";
520 : 0 : break;
521 : : }
522 : :
523 : : /* Begin group for this specific RTE */
405 rhaas@postgresql.org 524 :CBC 34 : ExplainOpenGroup("Range Table Entry", NULL, true, es);
525 : :
526 : : /*
527 : : * In text format, the summary line displays the range table index and
528 : : * rtekind, plus indications if rte->inh and/or rte->inFromCl are set.
529 : : * In other formats, we display those as separate properties.
530 : : */
531 [ + + ]: 34 : if (es->format == EXPLAIN_FORMAT_TEXT)
532 : : {
533 : 26 : ExplainIndentText(es);
534 : 52 : appendStringInfo(es->str, "RTI %u (%s%s%s):\n", rti, kind,
535 [ + + ]: 26 : rte->inh ? ", inherited" : "",
536 [ + + ]: 26 : rte->inFromCl ? ", in-from-clause" : "");
537 : 26 : es->indent++;
538 : : }
539 : : else
540 : : {
541 : 8 : ExplainPropertyUInteger("RTI", NULL, rti, es);
542 : 8 : ExplainPropertyText("Kind", kind, es);
543 : 8 : ExplainPropertyBool("Inherited", rte->inh, es);
544 : 8 : ExplainPropertyBool("In From Clause", rte->inFromCl, es);
545 : : }
546 : :
547 : : /*
548 : : * Indicate which subplan is the origin of which RTE. Note dummy
549 : : * subplans. Here again, we crunch more onto one line in text format.
550 : : */
84 rhaas@postgresql.org 551 [ + + ]:GNC 34 : if (rtinfo != NULL)
552 : : {
553 [ + - ]: 6 : if (es->format == EXPLAIN_FORMAT_TEXT)
554 : : {
555 [ + - ]: 6 : if (!rtinfo->dummy)
556 : 6 : ExplainPropertyText("Subplan", rtinfo->plan_name, es);
557 : : else
84 rhaas@postgresql.org 558 :UNC 0 : ExplainPropertyText("Subplan",
559 : 0 : psprintf("%s (dummy)",
560 : : rtinfo->plan_name), es);
561 : : }
562 : : else
563 : : {
564 : 0 : ExplainPropertyText("Subplan", rtinfo->plan_name, es);
565 : 0 : ExplainPropertyBool("Subplan Is Dummy", rtinfo->dummy, es);
566 : : }
567 : : }
568 : :
569 : : /* rte->alias is optional; rte->eref is requested */
405 rhaas@postgresql.org 570 [ + + ]:CBC 34 : if (rte->alias != NULL)
571 : 16 : overexplain_alias("Alias", rte->alias, es);
572 : 34 : overexplain_alias("Eref", rte->eref, es);
573 : :
574 : : /*
575 : : * We adhere to the usual EXPLAIN convention that schema names are
576 : : * displayed only in verbose mode, and we emit nothing if there is no
577 : : * relation OID.
578 : : */
579 [ + + ]: 34 : if (rte->relid != 0)
580 : : {
581 : : const char *relname;
582 : : const char *qualname;
583 : :
584 : 25 : relname = quote_identifier(get_rel_name(rte->relid));
585 : :
586 [ - + ]: 25 : if (es->verbose)
587 : : {
405 rhaas@postgresql.org 588 :UBC 0 : Oid nspoid = get_rel_namespace(rte->relid);
589 : : char *nspname;
590 : :
591 : 0 : nspname = get_namespace_name_or_temp(nspoid);
592 : 0 : qualname = psprintf("%s.%s", quote_identifier(nspname),
593 : : relname);
594 : : }
595 : : else
405 rhaas@postgresql.org 596 :CBC 25 : qualname = relname;
597 : :
598 : 25 : ExplainPropertyText("Relation", qualname, es);
599 : : }
600 : :
601 : : /* Translate relkind, if any, to a string */
602 [ + - - - : 34 : switch (rte->relkind)
- - - - +
- + + - ]
603 : : {
604 : 15 : case RELKIND_RELATION:
605 : 15 : relkind = "relation";
606 : 15 : break;
405 rhaas@postgresql.org 607 :UBC 0 : case RELKIND_INDEX:
608 : 0 : relkind = "index";
609 : 0 : break;
610 : 0 : case RELKIND_SEQUENCE:
611 : 0 : relkind = "sequence";
612 : 0 : break;
613 : 0 : case RELKIND_TOASTVALUE:
614 : 0 : relkind = "toastvalue";
615 : 0 : break;
616 : 0 : case RELKIND_VIEW:
617 : 0 : relkind = "view";
618 : 0 : break;
619 : 0 : case RELKIND_MATVIEW:
620 : 0 : relkind = "matview";
621 : 0 : break;
622 : 0 : case RELKIND_COMPOSITE_TYPE:
623 : 0 : relkind = "composite_type";
624 : 0 : break;
625 : 0 : case RELKIND_FOREIGN_TABLE:
626 : 0 : relkind = "foreign_table";
627 : 0 : break;
405 rhaas@postgresql.org 628 :CBC 9 : case RELKIND_PARTITIONED_TABLE:
381 michael@paquier.xyz 629 : 9 : relkind = "partitioned_table";
405 rhaas@postgresql.org 630 : 9 : break;
405 rhaas@postgresql.org 631 :UBC 0 : case RELKIND_PARTITIONED_INDEX:
381 michael@paquier.xyz 632 : 0 : relkind = "partitioned_index";
405 rhaas@postgresql.org 633 : 0 : break;
50 peter@eisentraut.org 634 :GNC 1 : case RELKIND_PROPGRAPH:
635 : 1 : relkind = "property_graph";
636 : 1 : break;
405 rhaas@postgresql.org 637 :CBC 9 : case '\0':
638 : 9 : relkind = NULL;
639 : 9 : break;
405 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 */
405 rhaas@postgresql.org 646 [ + + ]:CBC 34 : if (relkind != NULL)
647 : 25 : ExplainPropertyText("Relation Kind", relkind, es);
648 : :
649 : : /* If there is a lock mode, show it */
650 [ + + ]: 34 : if (rte->rellockmode != 0)
651 : 25 : 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 [ + + ]: 34 : if (rte->perminfoindex != 0)
662 : 12 : ExplainPropertyInteger("Permission Info Index", NULL,
663 : 12 : 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 [ + + - + ]: 34 : 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 [ - + ]: 34 : if (rte->rtekind == RTE_JOIN)
680 : : {
681 : : char *jointype;
682 : :
405 rhaas@postgresql.org 683 [ # # # # :UBC 0 : switch (rte->jointype)
# # # #
# ]
684 : : {
685 : 0 : case JOIN_INNER:
686 : 0 : jointype = "Inner";
687 : 0 : break;
688 : 0 : case JOIN_LEFT:
689 : 0 : jointype = "Left";
690 : 0 : break;
691 : 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 */
715 : 0 : ExplainPropertyText("Join Type", jointype, es);
716 : :
717 : : /* # of JOIN USING columns */
718 [ # # # # ]: 0 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->joinmergedcols != 0)
719 : 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 : : */
405 rhaas@postgresql.org 732 [ - + ]:CBC 34 : if (rte->rtekind == RTE_FUNCTION)
405 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 : : */
405 rhaas@postgresql.org 738 [ - + ]:CBC 34 : if (rte->rtekind == RTE_CTE)
739 : : {
405 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 : : */
405 rhaas@postgresql.org 753 [ - + ]:CBC 34 : if (rte->rtekind == RTE_NAMEDTUPLESTORE)
754 : : {
405 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 : : * rewriteGraphTable() clears graph_pattern and graph_table_columns
761 : : * fields, so skip them. No graph table specific fields are required
762 : : * to be printed.
763 : : */
764 : :
765 : : /*
766 : : * add_rte_to_flat_rtable will clear groupexprs and securityQuals, so
767 : : * skip that field. We have handled inFromCl above, so the only thing
768 : : * left to handle here is rte->lateral.
769 : : */
405 rhaas@postgresql.org 770 [ + + + + ]:CBC 34 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->lateral)
771 : 11 : ExplainPropertyBool("Lateral", rte->lateral, es);
772 : :
773 : : /* Done with this RTE */
774 [ + + ]: 34 : if (es->format == EXPLAIN_FORMAT_TEXT)
775 : 26 : es->indent--;
776 : 34 : ExplainCloseGroup("Range Table Entry", NULL, true, es);
777 : : }
778 : :
779 : : /* Close the Range Table array before emitting PlannedStmt-level fields. */
19 amitlan@postgresql.o 780 : 9 : ExplainCloseGroup("Range Table", "Range Table", false, es);
781 : :
782 : : /*
783 : : * Print PlannedStmt fields that contain RTIs. These are properties of
784 : : * the PlannedStmt, not of individual RTEs, so they belong outside the
785 : : * Range Table array.
786 : : */
405 rhaas@postgresql.org 787 [ + + ]: 9 : if (es->format != EXPLAIN_FORMAT_TEXT ||
788 [ + + ]: 7 : !bms_is_empty(plannedstmt->unprunableRelids))
789 : 8 : overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
790 : : es);
791 [ + + ]: 9 : if (es->format != EXPLAIN_FORMAT_TEXT ||
36 melanieplageman@gmai 792 [ + + ]:GNC 7 : !bms_is_empty(plannedstmt->resultRelationRelids))
793 : 3 : overexplain_bitmapset("Result RTIs", plannedstmt->resultRelationRelids,
794 : : es);
405 rhaas@postgresql.org 795 :CBC 9 : }
796 : :
797 : : /*
798 : : * Emit a text property describing the contents of an Alias.
799 : : *
800 : : * Column lists can be quite long here, so perhaps we should have an option
801 : : * to limit the display length by # of column or # of characters, but for
802 : : * now, just display everything.
803 : : */
804 : : static void
805 : 50 : overexplain_alias(const char *qlabel, Alias *alias, ExplainState *es)
806 : : {
807 : : StringInfoData buf;
808 : 50 : bool first = true;
809 : :
810 [ - + ]: 50 : Assert(alias != NULL);
811 : :
812 : 50 : initStringInfo(&buf);
813 : 50 : appendStringInfo(&buf, "%s (", quote_identifier(alias->aliasname));
814 : :
815 [ + + + + : 223 : foreach_node(String, cn, alias->colnames)
+ + ]
816 : : {
817 [ + + ]: 123 : appendStringInfo(&buf, "%s%s",
818 : : first ? "" : ", ",
819 : 123 : quote_identifier(cn->sval));
820 : 123 : first = false;
821 : : }
822 : :
823 : 50 : appendStringInfoChar(&buf, ')');
824 : 50 : ExplainPropertyText(qlabel, buf.data, es);
825 : 50 : pfree(buf.data);
826 : 50 : }
827 : :
828 : : /*
829 : : * Emit a text property describing the contents of a bitmapset -- either a
830 : : * space-separated list of integer members, or the word "none" if the bitmapset
831 : : * is empty.
832 : : */
833 : : static void
834 : 49 : overexplain_bitmapset(const char *qlabel, Bitmapset *bms, ExplainState *es)
835 : : {
836 : 49 : int x = -1;
837 : :
838 : : StringInfoData buf;
839 : :
840 [ + + ]: 49 : if (bms_is_empty(bms))
841 : : {
842 : 19 : ExplainPropertyText(qlabel, "none", es);
843 : 19 : return;
844 : : }
845 : :
846 : 30 : initStringInfo(&buf);
847 [ + + ]: 77 : while ((x = bms_next_member(bms, x)) >= 0)
848 : 47 : appendStringInfo(&buf, " %d", x);
849 [ - + ]: 30 : Assert(buf.data[0] == ' ');
850 : 30 : ExplainPropertyText(qlabel, buf.data + 1, es);
851 : 30 : pfree(buf.data);
852 : : }
853 : :
854 : : /*
855 : : * Emit a text property describing the contents of a list of bitmapsets.
856 : : * If a bitmapset contains exactly 1 member, we just print an integer;
857 : : * otherwise, we surround the list of members by parentheses.
858 : : *
859 : : * If there are no bitmapsets in the list, we print the word "none".
860 : : */
861 : : static void
84 rhaas@postgresql.org 862 :GNC 6 : overexplain_bitmapset_list(const char *qlabel, List *bms_list,
863 : : ExplainState *es)
864 : : {
865 : : StringInfoData buf;
866 : :
867 : 6 : initStringInfo(&buf);
868 : :
869 [ - + - - : 12 : foreach_node(Bitmapset, bms, bms_list)
+ + ]
870 : : {
84 rhaas@postgresql.org 871 [ # # ]:UNC 0 : if (bms_membership(bms) == BMS_SINGLETON)
872 : 0 : appendStringInfo(&buf, " %d", bms_singleton_member(bms));
873 : : else
874 : : {
875 : 0 : int x = -1;
876 : 0 : bool first = true;
877 : :
878 : 0 : appendStringInfoString(&buf, " (");
879 [ # # ]: 0 : while ((x = bms_next_member(bms, x)) >= 0)
880 : : {
881 [ # # ]: 0 : if (first)
882 : 0 : first = false;
883 : : else
884 : 0 : appendStringInfoChar(&buf, ' ');
885 : 0 : appendStringInfo(&buf, "%d", x);
886 : : }
887 : 0 : appendStringInfoChar(&buf, ')');
888 : : }
889 : : }
890 : :
84 rhaas@postgresql.org 891 [ + - ]:GNC 6 : if (buf.len == 0)
892 : : {
893 : 6 : ExplainPropertyText(qlabel, "none", es);
894 : 6 : return;
895 : : }
896 : :
84 rhaas@postgresql.org 897 [ # # ]:UNC 0 : Assert(buf.data[0] == ' ');
898 : 0 : ExplainPropertyText(qlabel, buf.data + 1, es);
899 : 0 : pfree(buf.data);
900 : : }
901 : :
902 : : /*
903 : : * Emit a text property describing the contents of a list of integers, OIDs,
904 : : * or XIDs -- either a space-separated list of integer members, or the word
905 : : * "none" if the list is empty.
906 : : */
907 : : static void
405 rhaas@postgresql.org 908 :CBC 14 : overexplain_intlist(const char *qlabel, List *list, ExplainState *es)
909 : : {
910 : : StringInfoData buf;
911 : :
912 : 14 : initStringInfo(&buf);
913 : :
914 [ + + ]: 14 : if (list == NIL)
915 : : {
916 : 7 : ExplainPropertyText(qlabel, "none", es);
917 : 7 : return;
918 : : }
919 : :
920 [ - + ]: 7 : if (IsA(list, IntList))
921 : : {
405 rhaas@postgresql.org 922 [ # # # # :LBC (3) : foreach_int(i, list)
# # ]
923 : (1) : appendStringInfo(&buf, " %d", i);
924 : : }
405 rhaas@postgresql.org 925 [ + - ]:CBC 7 : else if (IsA(list, OidList))
926 : : {
927 [ + - + + : 33 : foreach_oid(o, list)
+ + ]
928 : 19 : appendStringInfo(&buf, " %u", o);
929 : : }
405 rhaas@postgresql.org 930 [ # # ]:UBC 0 : else if (IsA(list, XidList))
931 : : {
932 [ # # # # : 0 : foreach_xid(x, list)
# # ]
933 : 0 : appendStringInfo(&buf, " %u", x);
934 : : }
935 : : else
936 : : {
389 drowley@postgresql.o 937 : 0 : appendStringInfoString(&buf, " not an integer list");
405 rhaas@postgresql.org 938 : 0 : Assert(false);
939 : : }
940 : :
405 rhaas@postgresql.org 941 [ + - ]:CBC 7 : if (buf.len > 0)
942 : 7 : ExplainPropertyText(qlabel, buf.data + 1, es);
943 : :
944 : 7 : pfree(buf.data);
945 : : }
|