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