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