LCOV - code coverage report
Current view: top level - contrib/pg_overexplain - pg_overexplain.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 209 342 61.1 %
Date: 2025-10-10 17:18:49 Functions: 12 12 100.0 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.16