LCOV - code coverage report
Current view: top level - contrib/pg_overexplain - pg_overexplain.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 65.0 % 417 271
Test Date: 2026-07-26 08:15:28 Functions: 100.0 % 13 13
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 64.2 % 240 154

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

Generated by: LCOV version 2.0-1