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 :
24 15 : 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
70 15 : _PG_init(void)
71 : {
72 : /* Get an ID that we can use to cache data in an ExplainState. */
73 15 : es_extension_id = GetExplainExtensionId("pg_overexplain");
74 :
75 : /* Register the new EXPLAIN options implemented by this module. */
76 15 : RegisterExtensionExplainOption("debug", overexplain_debug_handler,
77 : GUCCheckBooleanExplainOption);
78 15 : 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 15 : prev_explain_per_node_hook = explain_per_node_hook;
84 15 : explain_per_node_hook = overexplain_per_node_hook;
85 15 : prev_explain_per_plan_hook = explain_per_plan_hook;
86 15 : explain_per_plan_hook = overexplain_per_plan_hook;
87 15 : }
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 15 : overexplain_ensure_options(ExplainState *es)
95 : {
96 : overexplain_options *options;
97 :
98 15 : options = GetExplainExtensionState(es, es_extension_id);
99 :
100 15 : if (options == NULL)
101 : {
102 13 : options = palloc0_object(overexplain_options);
103 13 : SetExplainExtensionState(es, es_extension_id, options);
104 : }
105 :
106 15 : 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 8 : overexplain_range_table_handler(ExplainState *es, DefElem *opt,
125 : ParseState *pstate)
126 : {
127 8 : overexplain_options *options = overexplain_ensure_options(es);
128 :
129 8 : options->range_table = defGetBoolean(opt);
130 8 : }
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 54 : 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 54 : Plan *plan = planstate->plan;
144 :
145 54 : if (prev_explain_per_node_hook)
146 0 : (*prev_explain_per_node_hook) (planstate, ancestors, relationship,
147 : plan_name, es);
148 :
149 54 : options = GetExplainExtensionState(es, es_extension_id);
150 54 : if (options == NULL)
151 10 : return;
152 :
153 : /*
154 : * If the "debug" option was given, display miscellaneous fields from the
155 : * "Plan" node that would not otherwise be displayed.
156 : */
157 44 : 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 44 : if (options->range_table)
197 : {
198 27 : bool opened_elided_nodes = false;
199 :
200 27 : switch (nodeTag(plan))
201 : {
202 13 : 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 13 : ExplainPropertyInteger("Scan RTI", NULL,
217 13 : ((Scan *) plan)->scanrelid, es);
218 13 : break;
219 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;
229 1 : case T_ModifyTable:
230 1 : ExplainPropertyInteger("Nominal RTI", NULL,
231 1 : ((ModifyTable *) plan)->nominalRelation, es);
232 1 : ExplainPropertyInteger("Exclude Relation RTI", NULL,
233 1 : ((ModifyTable *) plan)->exclRelRTI, es);
234 1 : break;
235 5 : case T_Append:
236 5 : overexplain_bitmapset("Append RTIs",
237 : ((Append *) plan)->apprelids,
238 : es);
239 5 : overexplain_bitmapset_list("Child Append RTIs",
240 : ((Append *) plan)->child_append_relid_sets,
241 : es);
242 5 : break;
243 0 : case T_MergeAppend:
244 0 : overexplain_bitmapset("Append RTIs",
245 : ((MergeAppend *) plan)->apprelids,
246 : es);
247 0 : overexplain_bitmapset_list("Child Append RTIs",
248 : ((MergeAppend *) plan)->child_append_relid_sets,
249 : es);
250 0 : break;
251 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 ||
259 0 : ((Result *) plan)->relids != NULL)
260 2 : overexplain_bitmapset("RTIs",
261 : ((Result *) plan)->relids,
262 : es);
263 2 : break;
264 6 : default:
265 6 : break;
266 : }
267 :
268 81 : 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;
286 0 : case T_MergeAppend:
287 0 : elidednodetag = "MergeAppend";
288 0 : break;
289 4 : case T_SubqueryScan:
290 4 : elidednodetag = "SubqueryScan";
291 4 : break;
292 0 : default:
293 0 : elidednodetag = psprintf("%d", n->elided_type);
294 0 : break;
295 : }
296 :
297 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 27 : 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
314 23 : 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 :
323 23 : if (prev_explain_per_plan_hook)
324 0 : (*prev_explain_per_plan_hook) (plannedstmt, into, es, queryString,
325 : params, queryEnv);
326 :
327 23 : options = GetExplainExtensionState(es, es_extension_id);
328 23 : if (options == NULL)
329 10 : return;
330 :
331 13 : if (options->debug)
332 7 : overexplain_debug(plannedstmt, es);
333 :
334 13 : if (options->range_table)
335 8 : 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);
357 6 : appendStringInfoString(es->str, "PlannedStmt:\n");
358 6 : es->indent++;
359 : }
360 :
361 : /* Print the command type. */
362 7 : switch (plannedstmt->commandType)
363 : {
364 0 : case CMD_UNKNOWN:
365 0 : commandType = "unknown";
366 0 : break;
367 6 : case CMD_SELECT:
368 6 : commandType = "select";
369 6 : break;
370 0 : case CMD_UPDATE:
371 0 : commandType = "update";
372 0 : break;
373 1 : case CMD_INSERT:
374 1 : commandType = "insert";
375 1 : break;
376 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 : }
389 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)
394 1 : appendStringInfoString(&flags, ", hasReturning");
395 7 : if (plannedstmt->hasModifyingCTE)
396 0 : appendStringInfoString(&flags, ", hasModifyingCTE");
397 7 : if (plannedstmt->canSetTag)
398 7 : appendStringInfoString(&flags, ", canSetTag");
399 7 : if (plannedstmt->transientPlan)
400 0 : appendStringInfoString(&flags, ", transientPlan");
401 7 : if (plannedstmt->dependsOnRole)
402 0 : appendStringInfoString(&flags, ", dependsOnRole");
403 7 : if (plannedstmt->parallelModeNeeded)
404 1 : appendStringInfoString(&flags, ", parallelModeNeeded");
405 7 : if (flags.len == 0)
406 0 : appendStringInfoString(&flags, ", none");
407 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)
422 0 : ExplainPropertyText("Parse Location", "Unknown", es);
423 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
428 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. */
435 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 8 : overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
446 : {
447 : Index rti;
448 8 : ListCell *lc_subrtinfo = list_head(plannedstmt->subrtinfos);
449 8 : SubPlanRTInfo *rtinfo = NULL;
450 :
451 : /* Open group, one entry per RangeTblEntry */
452 8 : ExplainOpenGroup("Range Table", "Range Table", false, es);
453 :
454 : /* Iterate over the range table */
455 38 : for (rti = 1; rti <= list_length(plannedstmt->rtable); ++rti)
456 : {
457 30 : RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
458 30 : char *kind = NULL;
459 : char *relkind;
460 : SubPlanRTInfo *next_rtinfo;
461 :
462 : /* Advance to next SubRTInfo, if it's time. */
463 30 : 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 */
474 30 : if (rte == NULL)
475 0 : continue;
476 :
477 : /* Translate rtekind to a string */
478 30 : switch (rte->rtekind)
479 : {
480 21 : case RTE_RELATION:
481 21 : kind = "relation";
482 21 : break;
483 5 : case RTE_SUBQUERY:
484 5 : kind = "subquery";
485 5 : break;
486 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;
504 2 : case RTE_RESULT:
505 2 : kind = "result";
506 2 : break;
507 2 : case RTE_GROUP:
508 2 : kind = "group";
509 2 : break;
510 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 RTEkind 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 */
524 30 : 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 30 : 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 4 : ExplainPropertyUInteger("RTI", NULL, rti, es);
542 4 : ExplainPropertyText("Kind", kind, es);
543 4 : ExplainPropertyBool("Inherited", rte->inh, es);
544 4 : 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 : */
551 30 : 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
558 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 */
570 30 : if (rte->alias != NULL)
571 14 : overexplain_alias("Alias", rte->alias, es);
572 30 : 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 30 : if (rte->relid != 0)
580 : {
581 : const char *relname;
582 : const char *qualname;
583 :
584 22 : relname = quote_identifier(get_rel_name(rte->relid));
585 :
586 22 : if (es->verbose)
587 : {
588 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
596 22 : qualname = relname;
597 :
598 22 : ExplainPropertyText("Relation", qualname, es);
599 : }
600 :
601 : /* Translate relkind, if any, to a string */
602 30 : switch (rte->relkind)
603 : {
604 13 : case RELKIND_RELATION:
605 13 : relkind = "relation";
606 13 : break;
607 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;
628 8 : case RELKIND_PARTITIONED_TABLE:
629 8 : relkind = "partitioned_table";
630 8 : break;
631 0 : case RELKIND_PARTITIONED_INDEX:
632 0 : relkind = "partitioned_index";
633 0 : break;
634 1 : case RELKIND_PROPGRAPH:
635 1 : relkind = "property_graph";
636 1 : break;
637 8 : case '\0':
638 8 : relkind = NULL;
639 8 : break;
640 0 : default:
641 0 : relkind = psprintf("%c", rte->relkind);
642 0 : break;
643 : }
644 :
645 : /* If there is a relkind, show it */
646 30 : if (relkind != NULL)
647 22 : ExplainPropertyText("Relation Kind", relkind, es);
648 :
649 : /* If there is a lock mode, show it */
650 30 : if (rte->rellockmode != 0)
651 22 : 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 30 : if (rte->perminfoindex != 0)
662 11 : ExplainPropertyInteger("Permission Info Index", NULL,
663 11 : rte->perminfoindex, es);
664 :
665 : /*
666 : * add_rte_to_flat_rtable will clear rte->tablesample and
667 : * rte->subquery in the finished plan, so skip those fields.
668 : *
669 : * However, the security_barrier flag is not shown by the core code,
670 : * so let's print it here.
671 : */
672 30 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->security_barrier)
673 4 : 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 30 : if (rte->rtekind == RTE_JOIN)
680 : {
681 : char *jointype;
682 :
683 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 : */
732 30 : if (rte->rtekind == RTE_FUNCTION)
733 0 : ExplainPropertyBool("WITH ORDINALITY", rte->funcordinality, es);
734 :
735 : /*
736 : * If this is a CTE, print out CTE-related properties.
737 : */
738 30 : if (rte->rtekind == RTE_CTE)
739 : {
740 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 : */
753 30 : if (rte->rtekind == RTE_NAMEDTUPLESTORE)
754 : {
755 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 : */
770 30 : if (es->format != EXPLAIN_FORMAT_TEXT || rte->lateral)
771 7 : ExplainPropertyBool("Lateral", rte->lateral, es);
772 :
773 : /* Done with this RTE */
774 30 : if (es->format == EXPLAIN_FORMAT_TEXT)
775 26 : es->indent--;
776 30 : ExplainCloseGroup("Range Table Entry", NULL, true, es);
777 : }
778 :
779 : /* Print PlannedStmt fields that contain RTIs. */
780 8 : if (es->format != EXPLAIN_FORMAT_TEXT ||
781 7 : !bms_is_empty(plannedstmt->unprunableRelids))
782 7 : overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
783 : es);
784 8 : if (es->format != EXPLAIN_FORMAT_TEXT ||
785 7 : !bms_is_empty(plannedstmt->resultRelationRelids))
786 2 : overexplain_bitmapset("Result RTIs", plannedstmt->resultRelationRelids,
787 : es);
788 :
789 : /* Close group, we're all done */
790 8 : ExplainCloseGroup("Range Table", "Range Table", false, es);
791 8 : }
792 :
793 : /*
794 : * Emit a text property describing the contents of an Alias.
795 : *
796 : * Column lists can be quite long here, so perhaps we should have an option
797 : * to limit the display length by # of column or # of characters, but for
798 : * now, just display everything.
799 : */
800 : static void
801 44 : overexplain_alias(const char *qlabel, Alias *alias, ExplainState *es)
802 : {
803 : StringInfoData buf;
804 44 : bool first = true;
805 :
806 : Assert(alias != NULL);
807 :
808 44 : initStringInfo(&buf);
809 44 : appendStringInfo(&buf, "%s (", quote_identifier(alias->aliasname));
810 :
811 195 : foreach_node(String, cn, alias->colnames)
812 : {
813 107 : appendStringInfo(&buf, "%s%s",
814 : first ? "" : ", ",
815 107 : quote_identifier(cn->sval));
816 107 : first = false;
817 : }
818 :
819 44 : appendStringInfoChar(&buf, ')');
820 44 : ExplainPropertyText(qlabel, buf.data, es);
821 44 : pfree(buf.data);
822 44 : }
823 :
824 : /*
825 : * Emit a text property describing the contents of a bitmapset -- either a
826 : * space-separated list of integer members, or the word "none" if the bitmapset
827 : * is empty.
828 : */
829 : static void
830 46 : overexplain_bitmapset(const char *qlabel, Bitmapset *bms, ExplainState *es)
831 : {
832 46 : int x = -1;
833 :
834 : StringInfoData buf;
835 :
836 46 : if (bms_is_empty(bms))
837 : {
838 18 : ExplainPropertyText(qlabel, "none", es);
839 18 : return;
840 : }
841 :
842 28 : initStringInfo(&buf);
843 71 : while ((x = bms_next_member(bms, x)) >= 0)
844 43 : appendStringInfo(&buf, " %d", x);
845 : Assert(buf.data[0] == ' ');
846 28 : ExplainPropertyText(qlabel, buf.data + 1, es);
847 28 : pfree(buf.data);
848 : }
849 :
850 : /*
851 : * Emit a text property describing the contents of a list of bitmapsets.
852 : * If a bitmapset contains exactly 1 member, we just print an integer;
853 : * otherwise, we surround the list of members by parentheses.
854 : *
855 : * If there are no bitmapsets in the list, we print the word "none".
856 : */
857 : static void
858 5 : overexplain_bitmapset_list(const char *qlabel, List *bms_list,
859 : ExplainState *es)
860 : {
861 : StringInfoData buf;
862 :
863 5 : initStringInfo(&buf);
864 :
865 10 : foreach_node(Bitmapset, bms, bms_list)
866 : {
867 0 : if (bms_membership(bms) == BMS_SINGLETON)
868 0 : appendStringInfo(&buf, " %d", bms_singleton_member(bms));
869 : else
870 : {
871 0 : int x = -1;
872 0 : bool first = true;
873 :
874 0 : appendStringInfoString(&buf, " (");
875 0 : while ((x = bms_next_member(bms, x)) >= 0)
876 : {
877 0 : if (first)
878 0 : first = false;
879 : else
880 0 : appendStringInfoChar(&buf, ' ');
881 0 : appendStringInfo(&buf, "%d", x);
882 : }
883 0 : appendStringInfoChar(&buf, ')');
884 : }
885 : }
886 :
887 5 : if (buf.len == 0)
888 : {
889 5 : ExplainPropertyText(qlabel, "none", es);
890 5 : return;
891 : }
892 :
893 : Assert(buf.data[0] == ' ');
894 0 : ExplainPropertyText(qlabel, buf.data + 1, es);
895 0 : pfree(buf.data);
896 : }
897 :
898 : /*
899 : * Emit a text property describing the contents of a list of integers, OIDs,
900 : * or XIDs -- either a space-separated list of integer members, or the word
901 : * "none" if the list is empty.
902 : */
903 : static void
904 14 : overexplain_intlist(const char *qlabel, List *list, ExplainState *es)
905 : {
906 : StringInfoData buf;
907 :
908 14 : initStringInfo(&buf);
909 :
910 14 : if (list == NIL)
911 : {
912 7 : ExplainPropertyText(qlabel, "none", es);
913 7 : return;
914 : }
915 :
916 7 : if (IsA(list, IntList))
917 : {
918 0 : foreach_int(i, list)
919 0 : appendStringInfo(&buf, " %d", i);
920 : }
921 7 : else if (IsA(list, OidList))
922 : {
923 33 : foreach_oid(o, list)
924 19 : appendStringInfo(&buf, " %u", o);
925 : }
926 0 : else if (IsA(list, XidList))
927 : {
928 0 : foreach_xid(x, list)
929 0 : appendStringInfo(&buf, " %u", x);
930 : }
931 : else
932 : {
933 0 : appendStringInfoString(&buf, " not an integer list");
934 : Assert(false);
935 : }
936 :
937 7 : if (buf.len > 0)
938 7 : ExplainPropertyText(qlabel, buf.data + 1, es);
939 :
940 7 : pfree(buf.data);
941 : }
|