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