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