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 : :
484 rhaas@postgresql.org 24 :CBC 13 : 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
486 70 : 13 : _PG_init(void)
71 : : {
72 : : /* Get an ID that we can use to cache data in an ExplainState. */
73 : 13 : es_extension_id = GetExplainExtensionId("pg_overexplain");
74 : :
75 : : /* Register the new EXPLAIN options implemented by this module. */
110 76 : 13 : RegisterExtensionExplainOption("debug", overexplain_debug_handler,
77 : : GUCCheckBooleanExplainOption);
486 78 : 13 : 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 : 13 : prev_explain_per_node_hook = explain_per_node_hook;
84 : 13 : explain_per_node_hook = overexplain_per_node_hook;
85 : 13 : prev_explain_per_plan_hook = explain_per_plan_hook;
86 : 13 : explain_per_plan_hook = overexplain_per_plan_hook;
87 : 13 : }
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 : 17 : overexplain_ensure_options(ExplainState *es)
95 : : {
96 : : overexplain_options *options;
97 : :
98 : 17 : options = GetExplainExtensionState(es, es_extension_id);
99 : :
100 [ + + ]: 17 : if (options == NULL)
101 : : {
232 michael@paquier.xyz 102 : 15 : options = palloc0_object(overexplain_options);
486 rhaas@postgresql.org 103 : 15 : SetExplainExtensionState(es, es_extension_id, options);
104 : : }
105 : :
106 : 17 : 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 : 10 : overexplain_range_table_handler(ExplainState *es, DefElem *opt,
125 : : ParseState *pstate)
126 : : {
127 : 10 : overexplain_options *options = overexplain_ensure_options(es);
128 : :
129 : 10 : options->range_table = defGetBoolean(opt);
130 : 10 : }
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 : 61 : 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 : 61 : Plan *plan = planstate->plan;
144 : :
484 145 [ - + ]: 61 : if (prev_explain_per_node_hook)
484 rhaas@postgresql.org 146 :UBC 0 : (*prev_explain_per_node_hook) (planstate, ancestors, relationship,
147 : : plan_name, es);
148 : :
486 rhaas@postgresql.org 149 :CBC 61 : options = GetExplainExtensionState(es, es_extension_id);
150 [ + + ]: 61 : if (options == NULL)
151 : 9 : 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 [ + + ]: 52 : 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 [ + + ]: 52 : if (options->range_table)
197 : : {
165 198 : 35 : bool opened_elided_nodes = false;
199 : :
486 200 [ + - - + : 35 : switch (nodeTag(plan))
+ - + +
+ ]
201 : : {
202 : 17 : 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 : 17 : ExplainPropertyInteger("Scan RTI", NULL,
217 : 17 : ((Scan *) plan)->scanrelid, es);
218 : 17 : break;
486 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;
486 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);
165 239 : 6 : overexplain_bitmapset_list("Child Append RTIs",
240 : : ((Append *) plan)->child_append_relid_sets,
241 : : es);
486 242 : 6 : break;
486 rhaas@postgresql.org 243 :UBC 0 : case T_MergeAppend:
244 : 0 : overexplain_bitmapset("Append RTIs",
245 : : ((MergeAppend *) plan)->apprelids,
246 : : es);
165 247 : 0 : overexplain_bitmapset_list("Child Append RTIs",
248 : : ((MergeAppend *) plan)->child_append_relid_sets,
249 : : es);
486 250 : 0 : break;
305 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 ||
305 rhaas@postgresql.org 259 [ # # ]:UBC 0 : ((Result *) plan)->relids != NULL)
305 rhaas@postgresql.org 260 :CBC 2 : overexplain_bitmapset("RTIs",
261 : : ((Result *) plan)->relids,
262 : : es);
152 peter@eisentraut.org 263 : 2 : break;
17 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;
486 rhaas@postgresql.org 278 :CBC 6 : default:
279 : 6 : break;
280 : : }
281 : :
165 282 [ + + + + : 97 : foreach_node(ElidedNode, n, es->pstmt->elidedNodes)
+ + ]
283 : : {
284 : : char *elidednodetag;
285 : :
286 [ + + ]: 27 : if (n->plan_node_id != plan->plan_node_id)
287 : 20 : continue;
288 : :
289 [ + + ]: 7 : if (!opened_elided_nodes)
290 : : {
291 : 5 : ExplainOpenGroup("Elided Nodes", "Elided Nodes", false, es);
292 : 5 : opened_elided_nodes = true;
293 : : }
294 : :
295 [ + - + - ]: 7 : switch (n->elided_type)
296 : : {
297 : 3 : case T_Append:
298 : 3 : elidednodetag = "Append";
299 : 3 : break;
165 rhaas@postgresql.org 300 :UBC 0 : case T_MergeAppend:
301 : 0 : elidednodetag = "MergeAppend";
302 : 0 : break;
165 rhaas@postgresql.org 303 :CBC 4 : case T_SubqueryScan:
304 : 4 : elidednodetag = "SubqueryScan";
305 : 4 : break;
165 rhaas@postgresql.org 306 :UBC 0 : default:
307 : 0 : elidednodetag = psprintf("%d", n->elided_type);
308 : 0 : break;
309 : : }
310 : :
165 rhaas@postgresql.org 311 :CBC 7 : ExplainOpenGroup("Elided Node", NULL, true, es);
312 : 7 : ExplainPropertyText("Elided Node Type", elidednodetag, es);
313 : 7 : overexplain_bitmapset("Elided Node RTIs", n->relids, es);
314 : 7 : ExplainCloseGroup("Elided Node", NULL, true, es);
315 : : }
316 [ + + ]: 35 : if (opened_elided_nodes)
317 : 5 : 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
486 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 : :
484 337 [ - + ]: 24 : if (prev_explain_per_plan_hook)
484 rhaas@postgresql.org 338 :UBC 0 : (*prev_explain_per_plan_hook) (plannedstmt, into, es, queryString,
339 : : params, queryEnv);
340 : :
486 rhaas@postgresql.org 341 :CBC 24 : options = GetExplainExtensionState(es, es_extension_id);
342 [ + + ]: 24 : if (options == NULL)
343 : 9 : return;
344 : :
345 [ + + ]: 15 : if (options->debug)
346 : 7 : overexplain_debug(plannedstmt, es);
347 : :
348 [ + + ]: 15 : if (options->range_table)
349 : 10 : 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);
470 drowley@postgresql.o 371 : 6 : appendStringInfoString(es->str, "PlannedStmt:\n");
486 rhaas@postgresql.org 372 : 6 : es->indent++;
373 : : }
374 : :
375 : : /* Print the command type. */
376 [ - + - + : 7 : switch (plannedstmt->commandType)
- - - -
- ]
377 : : {
486 rhaas@postgresql.org 378 :UBC 0 : case CMD_UNKNOWN:
379 : 0 : commandType = "unknown";
380 : 0 : break;
486 rhaas@postgresql.org 381 :CBC 6 : case CMD_SELECT:
382 : 6 : commandType = "select";
383 : 6 : break;
486 rhaas@postgresql.org 384 :UBC 0 : case CMD_UPDATE:
385 : 0 : commandType = "update";
386 : 0 : break;
486 rhaas@postgresql.org 387 :CBC 1 : case CMD_INSERT:
388 : 1 : commandType = "insert";
389 : 1 : break;
486 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 : : }
486 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)
470 drowley@postgresql.o 408 : 1 : appendStringInfoString(&flags, ", hasReturning");
486 rhaas@postgresql.org 409 [ - + ]: 7 : if (plannedstmt->hasModifyingCTE)
470 drowley@postgresql.o 410 :UBC 0 : appendStringInfoString(&flags, ", hasModifyingCTE");
486 rhaas@postgresql.org 411 [ + - ]:CBC 7 : if (plannedstmt->canSetTag)
470 drowley@postgresql.o 412 : 7 : appendStringInfoString(&flags, ", canSetTag");
486 rhaas@postgresql.org 413 [ - + ]: 7 : if (plannedstmt->transientPlan)
470 drowley@postgresql.o 414 :UBC 0 : appendStringInfoString(&flags, ", transientPlan");
486 rhaas@postgresql.org 415 [ - + ]:CBC 7 : if (plannedstmt->dependsOnRole)
470 drowley@postgresql.o 416 :UBC 0 : appendStringInfoString(&flags, ", dependsOnRole");
486 rhaas@postgresql.org 417 [ + + ]:CBC 7 : if (plannedstmt->parallelModeNeeded)
470 drowley@postgresql.o 418 : 1 : appendStringInfoString(&flags, ", parallelModeNeeded");
486 rhaas@postgresql.org 419 [ - + ]: 7 : if (flags.len == 0)
470 drowley@postgresql.o 420 :UBC 0 : appendStringInfoString(&flags, ", none");
486 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)
486 rhaas@postgresql.org 436 :UBC 0 : ExplainPropertyText("Parse Location", "Unknown", es);
486 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 : 10 : overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
460 : : {
461 : : Index rti;
165 462 : 10 : ListCell *lc_subrtinfo = list_head(plannedstmt->subrtinfos);
463 : 10 : SubPlanRTInfo *rtinfo = NULL;
464 : :
465 : : /* Open group, one entry per RangeTblEntry */
486 466 : 10 : ExplainOpenGroup("Range Table", "Range Table", false, es);
467 : :
468 : : /* Iterate over the range table */
469 [ + + ]: 47 : for (rti = 1; rti <= list_length(plannedstmt->rtable); ++rti)
470 : : {
471 : 37 : RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
472 : 37 : char *kind = NULL;
473 : : char *relkind;
474 : : SubPlanRTInfo *next_rtinfo;
475 : :
476 : : /* Advance to next SubPlanRTInfo, if it's time. */
165 477 [ + + ]: 37 : if (lc_subrtinfo != NULL)
478 : : {
479 : 15 : next_rtinfo = lfirst(lc_subrtinfo);
480 [ + + ]: 15 : if (rti > next_rtinfo->rtoffset)
481 : : {
482 : 4 : rtinfo = next_rtinfo;
483 : 4 : lc_subrtinfo = lnext(plannedstmt->subrtinfos, lc_subrtinfo);
484 : : }
485 : : }
486 : :
487 : : /* NULL entries are possible; skip them */
486 488 [ - + ]: 37 : if (rte == NULL)
486 rhaas@postgresql.org 489 :UBC 0 : continue;
490 : :
491 : : /* Translate rtekind to a string */
486 rhaas@postgresql.org 492 [ + + + - :CBC 37 : switch (rte->rtekind)
- - - - +
+ - - ]
493 : : {
494 : 26 : case RTE_RELATION:
495 : 26 : kind = "relation";
496 : 26 : break;
497 : 5 : case RTE_SUBQUERY:
498 : 5 : kind = "subquery";
499 : 5 : break;
486 rhaas@postgresql.org 500 :GBC 1 : case RTE_JOIN:
501 : 1 : kind = "join";
502 : 1 : break;
486 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;
486 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;
131 peter@eisentraut.org 524 :UBC 0 : case RTE_GRAPH_TABLE:
525 : :
526 : : /*
527 : : * We should not see RTE of this kind here since property
528 : : * graph RTE gets converted to subquery RTE in
529 : : * rewriteGraphTable(). In case we decide not to do the
530 : : * conversion and leave RTE kind unchanged in future, print
531 : : * correct name of RTE kind.
532 : : */
533 : 0 : kind = "graph_table";
534 : 0 : break;
535 : : }
536 : :
537 : : /* Begin group for this specific RTE */
486 rhaas@postgresql.org 538 :CBC 37 : ExplainOpenGroup("Range Table Entry", NULL, true, es);
539 : :
540 : : /*
541 : : * In text format, the summary line displays the range table index and
542 : : * rtekind, plus indications if rte->inh and/or rte->inFromCl are set.
543 : : * In other formats, we display those as separate properties.
544 : : */
545 [ + + ]: 37 : if (es->format == EXPLAIN_FORMAT_TEXT)
546 : : {
547 : 29 : ExplainIndentText(es);
548 : 58 : appendStringInfo(es->str, "RTI %u (%s%s%s):\n", rti, kind,
549 [ + + ]: 29 : rte->inh ? ", inherited" : "",
550 [ + + ]: 29 : rte->inFromCl ? ", in-from-clause" : "");
551 : 29 : es->indent++;
552 : : }
553 : : else
554 : : {
555 : 8 : ExplainPropertyUInteger("RTI", NULL, rti, es);
556 : 8 : ExplainPropertyText("Kind", kind, es);
557 : 8 : ExplainPropertyBool("Inherited", rte->inh, es);
558 : 8 : ExplainPropertyBool("In From Clause", rte->inFromCl, es);
559 : : }
560 : :
561 : : /*
562 : : * Indicate which subplan is the origin of which RTE. Note dummy
563 : : * subplans. Here again, we crunch more onto one line in text format.
564 : : */
165 565 [ + + ]: 37 : if (rtinfo != NULL)
566 : : {
567 [ + - ]: 6 : if (es->format == EXPLAIN_FORMAT_TEXT)
568 : : {
569 [ + - ]: 6 : if (!rtinfo->dummy)
570 : 6 : ExplainPropertyText("Subplan", rtinfo->plan_name, es);
571 : : else
165 rhaas@postgresql.org 572 :UBC 0 : ExplainPropertyText("Subplan",
573 : 0 : psprintf("%s (dummy)",
574 : : rtinfo->plan_name), es);
575 : : }
576 : : else
577 : : {
578 : 0 : ExplainPropertyText("Subplan", rtinfo->plan_name, es);
579 : 0 : ExplainPropertyBool("Subplan Is Dummy", rtinfo->dummy, es);
580 : : }
581 : : }
582 : :
583 : : /* rte->alias is optional; rte->eref is requested */
486 rhaas@postgresql.org 584 [ + + ]:CBC 37 : if (rte->alias != NULL)
585 : 18 : overexplain_alias("Alias", rte->alias, es);
586 : 37 : overexplain_alias("Eref", rte->eref, es);
587 : :
588 : : /*
589 : : * We adhere to the usual EXPLAIN convention that schema names are
590 : : * displayed only in verbose mode, and we emit nothing if there is no
591 : : * relation OID.
592 : : */
593 [ + + ]: 37 : if (rte->relid != 0)
594 : : {
595 : : const char *relname;
596 : : const char *qualname;
597 : :
598 : 27 : relname = quote_identifier(get_rel_name(rte->relid));
599 : :
600 [ - + ]: 27 : if (es->verbose)
601 : : {
486 rhaas@postgresql.org 602 :UBC 0 : Oid nspoid = get_rel_namespace(rte->relid);
603 : : char *nspname;
604 : :
605 : 0 : nspname = get_namespace_name_or_temp(nspoid);
606 : 0 : qualname = psprintf("%s.%s", quote_identifier(nspname),
607 : : relname);
608 : : }
609 : : else
486 rhaas@postgresql.org 610 :CBC 27 : qualname = relname;
611 : :
612 : 27 : ExplainPropertyText("Relation", qualname, es);
613 : : }
614 : :
615 : : /* Translate relkind, if any, to a string */
616 [ + - - - : 37 : switch (rte->relkind)
- - - - +
- + + - ]
617 : : {
618 : 17 : case RELKIND_RELATION:
619 : 17 : relkind = "relation";
620 : 17 : break;
486 rhaas@postgresql.org 621 :UBC 0 : case RELKIND_INDEX:
622 : 0 : relkind = "index";
623 : 0 : break;
624 : 0 : case RELKIND_SEQUENCE:
625 : 0 : relkind = "sequence";
626 : 0 : break;
627 : 0 : case RELKIND_TOASTVALUE:
628 : 0 : relkind = "toastvalue";
629 : 0 : break;
630 : 0 : case RELKIND_VIEW:
631 : 0 : relkind = "view";
632 : 0 : break;
633 : 0 : case RELKIND_MATVIEW:
634 : 0 : relkind = "matview";
635 : 0 : break;
636 : 0 : case RELKIND_COMPOSITE_TYPE:
637 : 0 : relkind = "composite_type";
638 : 0 : break;
639 : 0 : case RELKIND_FOREIGN_TABLE:
640 : 0 : relkind = "foreign_table";
641 : 0 : break;
486 rhaas@postgresql.org 642 :CBC 9 : case RELKIND_PARTITIONED_TABLE:
462 michael@paquier.xyz 643 : 9 : relkind = "partitioned_table";
486 rhaas@postgresql.org 644 : 9 : break;
486 rhaas@postgresql.org 645 :UBC 0 : case RELKIND_PARTITIONED_INDEX:
462 michael@paquier.xyz 646 : 0 : relkind = "partitioned_index";
486 rhaas@postgresql.org 647 : 0 : break;
131 peter@eisentraut.org 648 :CBC 1 : case RELKIND_PROPGRAPH:
649 : 1 : relkind = "property_graph";
650 : 1 : break;
486 rhaas@postgresql.org 651 : 10 : case '\0':
652 : 10 : relkind = NULL;
653 : 10 : break;
486 rhaas@postgresql.org 654 :UBC 0 : default:
655 : 0 : relkind = psprintf("%c", rte->relkind);
656 : 0 : break;
657 : : }
658 : :
659 : : /* If there is a relkind, show it */
486 rhaas@postgresql.org 660 [ + + ]:CBC 37 : if (relkind != NULL)
661 : 27 : ExplainPropertyText("Relation Kind", relkind, es);
662 : :
663 : : /* If there is a lock mode, show it */
664 [ + + ]: 37 : if (rte->rellockmode != 0)
665 : 27 : ExplainPropertyText("Relation Lock Mode",
666 : : GetLockmodeName(DEFAULT_LOCKMETHOD,
667 : : rte->rellockmode), es);
668 : :
669 : : /*
670 : : * If there is a perminfoindex, show it. We don't try to display
671 : : * information from the RTEPermissionInfo node here because they are
672 : : * just indexes plannedstmt->permInfos which could be separately
673 : : * dumped if someone wants to add EXPLAIN (PERMISSIONS) or similar.
674 : : */
675 [ + + ]: 37 : if (rte->perminfoindex != 0)
676 : 14 : ExplainPropertyInteger("Permission Info Index", NULL,
677 : 14 : rte->perminfoindex, es);
678 : :
679 : : /*
680 : : * add_rte_to_flat_rtable will clear rte->tablesample and
681 : : * rte->subquery in the finished plan, so skip those fields.
682 : : *
683 : : * However, the security_barrier flag is not shown by the core code,
684 : : * so let's print it here.
685 : : */
686 [ + + - + ]: 37 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->security_barrier)
687 : 8 : ExplainPropertyBool("Security Barrier", rte->security_barrier, es);
688 : :
689 : : /*
690 : : * If this is a join, print out the fields that are specifically valid
691 : : * for joins.
692 : : */
693 [ + + ]: 37 : if (rte->rtekind == RTE_JOIN)
694 : : {
695 : : char *jointype;
696 : :
486 rhaas@postgresql.org 697 [ - + - - :GBC 1 : switch (rte->jointype)
- - - -
- ]
698 : : {
486 rhaas@postgresql.org 699 :UBC 0 : case JOIN_INNER:
700 : 0 : jointype = "Inner";
701 : 0 : break;
486 rhaas@postgresql.org 702 :GBC 1 : case JOIN_LEFT:
703 : 1 : jointype = "Left";
704 : 1 : break;
486 rhaas@postgresql.org 705 :UBC 0 : case JOIN_FULL:
706 : 0 : jointype = "Full";
707 : 0 : break;
708 : 0 : case JOIN_RIGHT:
709 : 0 : jointype = "Right";
710 : 0 : break;
711 : 0 : case JOIN_SEMI:
712 : 0 : jointype = "Semi";
713 : 0 : break;
714 : 0 : case JOIN_ANTI:
715 : 0 : jointype = "Anti";
716 : 0 : break;
717 : 0 : case JOIN_RIGHT_SEMI:
718 : 0 : jointype = "Right Semi";
719 : 0 : break;
720 : 0 : case JOIN_RIGHT_ANTI:
721 : 0 : jointype = "Right Anti";
722 : 0 : break;
723 : 0 : default:
724 : 0 : jointype = "???";
725 : 0 : break;
726 : : }
727 : :
728 : : /* Join type */
486 rhaas@postgresql.org 729 :GBC 1 : ExplainPropertyText("Join Type", jointype, es);
730 : :
731 : : /* # of JOIN USING columns */
732 [ + - - + ]: 1 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->joinmergedcols != 0)
486 rhaas@postgresql.org 733 :UBC 0 : ExplainPropertyInteger("JOIN USING Columns", NULL,
734 : 0 : rte->joinmergedcols, es);
735 : :
736 : : /*
737 : : * add_rte_to_flat_rtable will clear joinaliasvars, joinleftcols,
738 : : * joinrightcols, and join_using_alias here, so skip those fields.
739 : : */
740 : : }
741 : :
742 : : /*
743 : : * add_rte_to_flat_rtable will clear functions, tablefunc, and
744 : : * values_lists, but we can display funcordinality.
745 : : */
486 rhaas@postgresql.org 746 [ - + ]:CBC 37 : if (rte->rtekind == RTE_FUNCTION)
486 rhaas@postgresql.org 747 :UBC 0 : ExplainPropertyBool("WITH ORDINALITY", rte->funcordinality, es);
748 : :
749 : : /*
750 : : * If this is a CTE, print out CTE-related properties.
751 : : */
486 rhaas@postgresql.org 752 [ - + ]:CBC 37 : if (rte->rtekind == RTE_CTE)
753 : : {
486 rhaas@postgresql.org 754 :UBC 0 : ExplainPropertyText("CTE Name", rte->ctename, es);
755 : 0 : ExplainPropertyUInteger("CTE Levels Up", NULL, rte->ctelevelsup,
756 : : es);
757 : 0 : ExplainPropertyBool("CTE Self-Reference", rte->self_reference, es);
758 : : }
759 : :
760 : : /*
761 : : * add_rte_to_flat_rtable will clear coltypes, coltypmods, and
762 : : * colcollations, so skip those fields.
763 : : *
764 : : * If this is an ephemeral named relation, print out ENR-related
765 : : * properties.
766 : : */
486 rhaas@postgresql.org 767 [ - + ]:CBC 37 : if (rte->rtekind == RTE_NAMEDTUPLESTORE)
768 : : {
486 rhaas@postgresql.org 769 :UBC 0 : ExplainPropertyText("ENR Name", rte->enrname, es);
770 : 0 : ExplainPropertyFloat("ENR Tuples", NULL, rte->enrtuples, 0, es);
771 : : }
772 : :
773 : : /*
774 : : * rewriteGraphTable() clears graph_pattern and graph_table_columns
775 : : * fields, so skip them. No graph table specific fields are required
776 : : * to be printed.
777 : : */
778 : :
779 : : /*
780 : : * add_rte_to_flat_rtable will clear groupexprs and securityQuals, so
781 : : * skip that field. We have handled inFromCl above, so the only thing
782 : : * left to handle here is rte->lateral.
783 : : */
486 rhaas@postgresql.org 784 [ + + + + ]:CBC 37 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->lateral)
785 : 11 : ExplainPropertyBool("Lateral", rte->lateral, es);
786 : :
787 : : /* Done with this RTE */
788 [ + + ]: 37 : if (es->format == EXPLAIN_FORMAT_TEXT)
789 : 29 : es->indent--;
790 : 37 : ExplainCloseGroup("Range Table Entry", NULL, true, es);
791 : : }
792 : :
793 : : /* Close the Range Table array before emitting PlannedStmt-level fields. */
100 amitlan@postgresql.o 794 : 10 : ExplainCloseGroup("Range Table", "Range Table", false, es);
795 : :
796 : : /*
797 : : * Print PlannedStmt fields that contain RTIs. These are properties of
798 : : * the PlannedStmt, not of individual RTEs, so they belong outside the
799 : : * Range Table array.
800 : : */
486 rhaas@postgresql.org 801 [ + + ]: 10 : if (es->format != EXPLAIN_FORMAT_TEXT ||
802 [ + + ]: 8 : !bms_is_empty(plannedstmt->unprunableRelids))
803 : 9 : overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
804 : : es);
805 [ + + ]: 10 : if (es->format != EXPLAIN_FORMAT_TEXT ||
117 melanieplageman@gmai 806 [ + + ]: 8 : !bms_is_empty(plannedstmt->resultRelationRelids))
807 : 3 : overexplain_bitmapset("Result RTIs", plannedstmt->resultRelationRelids,
808 : : es);
486 rhaas@postgresql.org 809 : 10 : }
810 : :
811 : : /*
812 : : * Emit a text property describing the contents of an Alias.
813 : : *
814 : : * Column lists can be quite long here, so perhaps we should have an option
815 : : * to limit the display length by # of column or # of characters, but for
816 : : * now, just display everything.
817 : : */
818 : : static void
819 : 55 : overexplain_alias(const char *qlabel, Alias *alias, ExplainState *es)
820 : : {
821 : : StringInfoData buf;
822 : 55 : bool first = true;
823 : :
824 [ - + ]: 55 : Assert(alias != NULL);
825 : :
826 : 55 : initStringInfo(&buf);
827 : 55 : appendStringInfo(&buf, "%s (", quote_identifier(alias->aliasname));
828 : :
829 [ + + + + : 245 : foreach_node(String, cn, alias->colnames)
+ + ]
830 : : {
831 [ + + ]: 135 : appendStringInfo(&buf, "%s%s",
832 : : first ? "" : ", ",
833 : 135 : quote_identifier(cn->sval));
834 : 135 : first = false;
835 : : }
836 : :
837 : 55 : appendStringInfoChar(&buf, ')');
838 : 55 : ExplainPropertyText(qlabel, buf.data, es);
839 : 55 : pfree(buf.data);
840 : 55 : }
841 : :
842 : : /*
843 : : * Emit a text property describing the contents of a bitmapset -- either a
844 : : * space-separated list of integer members, or the word "none" if the bitmapset
845 : : * is empty.
846 : : */
847 : : static void
848 : 51 : overexplain_bitmapset(const char *qlabel, Bitmapset *bms, ExplainState *es)
849 : : {
850 : 51 : int x = -1;
851 : :
852 : : StringInfoData buf;
853 : :
854 [ + + ]: 51 : if (bms_is_empty(bms))
855 : : {
856 : 19 : ExplainPropertyText(qlabel, "none", es);
857 : 19 : return;
858 : : }
859 : :
860 : 32 : initStringInfo(&buf);
861 [ + + ]: 82 : while ((x = bms_next_member(bms, x)) >= 0)
862 : 50 : appendStringInfo(&buf, " %d", x);
863 [ - + ]: 32 : Assert(buf.data[0] == ' ');
864 : 32 : ExplainPropertyText(qlabel, buf.data + 1, es);
865 : 32 : pfree(buf.data);
866 : : }
867 : :
868 : : /*
869 : : * Emit a text property describing the contents of a list of bitmapsets.
870 : : * If a bitmapset contains exactly 1 member, we just print an integer;
871 : : * otherwise, we surround the list of members by parentheses.
872 : : *
873 : : * If there are no bitmapsets in the list, we print the word "none".
874 : : */
875 : : static void
165 876 : 6 : overexplain_bitmapset_list(const char *qlabel, List *bms_list,
877 : : ExplainState *es)
878 : : {
879 : : StringInfoData buf;
880 : :
881 : 6 : initStringInfo(&buf);
882 : :
883 [ - + - - : 12 : foreach_node(Bitmapset, bms, bms_list)
+ + ]
884 : : {
165 rhaas@postgresql.org 885 [ # # ]:UBC 0 : if (bms_membership(bms) == BMS_SINGLETON)
886 : 0 : appendStringInfo(&buf, " %d", bms_singleton_member(bms));
887 : : else
888 : : {
889 : 0 : int x = -1;
890 : 0 : bool first = true;
891 : :
892 : 0 : appendStringInfoString(&buf, " (");
893 [ # # ]: 0 : while ((x = bms_next_member(bms, x)) >= 0)
894 : : {
895 [ # # ]: 0 : if (first)
896 : 0 : first = false;
897 : : else
898 : 0 : appendStringInfoChar(&buf, ' ');
899 : 0 : appendStringInfo(&buf, "%d", x);
900 : : }
901 : 0 : appendStringInfoChar(&buf, ')');
902 : : }
903 : : }
904 : :
165 rhaas@postgresql.org 905 [ + - ]:CBC 6 : if (buf.len == 0)
906 : : {
907 : 6 : ExplainPropertyText(qlabel, "none", es);
908 : 6 : return;
909 : : }
910 : :
165 rhaas@postgresql.org 911 [ # # ]:UBC 0 : Assert(buf.data[0] == ' ');
912 : 0 : ExplainPropertyText(qlabel, buf.data + 1, es);
913 : 0 : pfree(buf.data);
914 : : }
915 : :
916 : : /*
917 : : * Emit a text property describing the contents of a list of integers, OIDs,
918 : : * or XIDs -- either a space-separated list of integer members, or the word
919 : : * "none" if the list is empty.
920 : : */
921 : : static void
486 rhaas@postgresql.org 922 :CBC 14 : overexplain_intlist(const char *qlabel, List *list, ExplainState *es)
923 : : {
924 : : StringInfoData buf;
925 : :
926 : 14 : initStringInfo(&buf);
927 : :
928 [ + + ]: 14 : if (list == NIL)
929 : : {
930 : 7 : ExplainPropertyText(qlabel, "none", es);
931 : 7 : return;
932 : : }
933 : :
934 [ - + ]: 7 : if (IsA(list, IntList))
935 : : {
486 rhaas@postgresql.org 936 [ # # # # :UBC 0 : foreach_int(i, list)
# # ]
937 : 0 : appendStringInfo(&buf, " %d", i);
938 : : }
486 rhaas@postgresql.org 939 [ + - ]:CBC 7 : else if (IsA(list, OidList))
940 : : {
941 [ + - + + : 33 : foreach_oid(o, list)
+ + ]
942 : 19 : appendStringInfo(&buf, " %u", o);
943 : : }
486 rhaas@postgresql.org 944 [ # # ]:UBC 0 : else if (IsA(list, XidList))
945 : : {
946 [ # # # # : 0 : foreach_xid(x, list)
# # ]
947 : 0 : appendStringInfo(&buf, " %u", x);
948 : : }
949 : : else
950 : : {
470 drowley@postgresql.o 951 : 0 : appendStringInfoString(&buf, " not an integer list");
486 rhaas@postgresql.org 952 : 0 : Assert(false);
953 : : }
954 : :
486 rhaas@postgresql.org 955 [ + - ]:CBC 7 : if (buf.len > 0)
956 : 7 : ExplainPropertyText(qlabel, buf.data + 1, es);
957 : :
958 : 7 : pfree(buf.data);
959 : : }
|