LCOV - code coverage report
Current view: top level - src/backend/commands - explain_state.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 79.0 % 157 124
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 7 7
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 56.8 % 148 84

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * explain_state.c
       4                 :             :  *        Code for initializing and accessing ExplainState objects
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994-5, Regents of the University of California
       8                 :             :  *
       9                 :             :  * In-core options have hard-coded fields inside ExplainState; e.g. if
      10                 :             :  * the user writes EXPLAIN (BUFFERS) then ExplainState's "buffers" member
      11                 :             :  * will be set to true. Extensions can also register options using
      12                 :             :  * RegisterExtensionExplainOption; so that e.g. EXPLAIN (BICYCLE 'red')
      13                 :             :  * will invoke a designated handler that knows what the legal values are
      14                 :             :  * for the BICYCLE option. However, it's not enough for an extension to be
      15                 :             :  * able to parse new options: it also needs a place to store the results
      16                 :             :  * of that parsing, and an ExplainState has no 'bicycle' field.
      17                 :             :  *
      18                 :             :  * To solve this problem, an ExplainState can contain an array of opaque
      19                 :             :  * pointers, one per extension. An extension can use GetExplainExtensionId
      20                 :             :  * to acquire an integer ID to acquire an offset into this array that is
      21                 :             :  * reserved for its exclusive use, and then use GetExplainExtensionState
      22                 :             :  * and SetExplainExtensionState to read and write its own private state
      23                 :             :  * within an ExplainState.
      24                 :             :  *
      25                 :             :  * Note that there is no requirement that the name of the option match
      26                 :             :  * the name of the extension; e.g. a pg_explain_conveyance extension could
      27                 :             :  * implement options for BICYCLE, MONORAIL, etc.
      28                 :             :  *
      29                 :             :  * IDENTIFICATION
      30                 :             :  *        src/backend/commands/explain_state.c
      31                 :             :  *
      32                 :             :  *-------------------------------------------------------------------------
      33                 :             :  */
      34                 :             : #include "postgres.h"
      35                 :             : 
      36                 :             : #include "commands/defrem.h"
      37                 :             : #include "commands/explain.h"
      38                 :             : #include "commands/explain_state.h"
      39                 :             : 
      40                 :             : /* Hook to perform additional EXPLAIN options validation */
      41                 :             : explain_validate_options_hook_type explain_validate_options_hook = NULL;
      42                 :             : 
      43                 :             : typedef struct
      44                 :             : {
      45                 :             :         const char *option_name;
      46                 :             :         ExplainOptionHandler option_handler;
      47                 :             : } ExplainExtensionOption;
      48                 :             : 
      49                 :             : static const char **ExplainExtensionNameArray = NULL;
      50                 :             : static int      ExplainExtensionNamesAssigned = 0;
      51                 :             : static int      ExplainExtensionNamesAllocated = 0;
      52                 :             : 
      53                 :             : static ExplainExtensionOption *ExplainExtensionOptionArray = NULL;
      54                 :             : static int      ExplainExtensionOptionsAssigned = 0;
      55                 :             : static int      ExplainExtensionOptionsAllocated = 0;
      56                 :             : 
      57                 :             : /*
      58                 :             :  * Create a new ExplainState struct initialized with default options.
      59                 :             :  */
      60                 :             : ExplainState *
      61                 :        3633 : NewExplainState(void)
      62                 :             : {
      63                 :        3633 :         ExplainState *es = palloc0_object(ExplainState);
      64                 :             : 
      65                 :             :         /* Set default options (most fields can be left as zeroes). */
      66                 :        3633 :         es->costs = true;
      67                 :             :         /* Prepare output buffer. */
      68                 :        3633 :         es->str = makeStringInfo();
      69                 :             : 
      70                 :        7266 :         return es;
      71                 :        3633 : }
      72                 :             : 
      73                 :             : /*
      74                 :             :  * Parse a list of EXPLAIN options and update an ExplainState accordingly.
      75                 :             :  */
      76                 :             : void
      77                 :        3633 : ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
      78                 :             : {
      79                 :        3633 :         ListCell   *lc;
      80                 :        3633 :         bool            timing_set = false;
      81                 :        3633 :         bool            buffers_set = false;
      82                 :        3633 :         bool            summary_set = false;
      83                 :             : 
      84                 :             :         /* Parse options list. */
      85   [ +  +  +  +  :        7487 :         foreach(lc, options)
                   +  + ]
      86                 :             :         {
      87                 :        3854 :                 DefElem    *opt = (DefElem *) lfirst(lc);
      88                 :             : 
      89         [ +  + ]:        3854 :                 if (strcmp(opt->defname, "analyze") == 0)
      90                 :         566 :                         es->analyze = defGetBoolean(opt);
      91         [ +  + ]:        3288 :                 else if (strcmp(opt->defname, "verbose") == 0)
      92                 :         350 :                         es->verbose = defGetBoolean(opt);
      93         [ +  + ]:        2938 :                 else if (strcmp(opt->defname, "costs") == 0)
      94                 :        2333 :                         es->costs = defGetBoolean(opt);
      95         [ +  + ]:         605 :                 else if (strcmp(opt->defname, "buffers") == 0)
      96                 :             :                 {
      97                 :         146 :                         buffers_set = true;
      98                 :         146 :                         es->buffers = defGetBoolean(opt);
      99                 :         146 :                 }
     100         [ +  - ]:         459 :                 else if (strcmp(opt->defname, "wal") == 0)
     101                 :           0 :                         es->wal = defGetBoolean(opt);
     102         [ +  + ]:         459 :                 else if (strcmp(opt->defname, "settings") == 0)
     103                 :           2 :                         es->settings = defGetBoolean(opt);
     104         [ +  + ]:         457 :                 else if (strcmp(opt->defname, "generic_plan") == 0)
     105                 :           3 :                         es->generic = defGetBoolean(opt);
     106         [ +  + ]:         454 :                 else if (strcmp(opt->defname, "timing") == 0)
     107                 :             :                 {
     108                 :         140 :                         timing_set = true;
     109                 :         140 :                         es->timing = defGetBoolean(opt);
     110                 :         140 :                 }
     111         [ +  + ]:         314 :                 else if (strcmp(opt->defname, "summary") == 0)
     112                 :             :                 {
     113                 :         136 :                         summary_set = true;
     114                 :         136 :                         es->summary = defGetBoolean(opt);
     115                 :         136 :                 }
     116         [ +  + ]:         178 :                 else if (strcmp(opt->defname, "memory") == 0)
     117                 :           5 :                         es->memory = defGetBoolean(opt);
     118         [ +  + ]:         173 :                 else if (strcmp(opt->defname, "serialize") == 0)
     119                 :             :                 {
     120         [ +  + ]:           5 :                         if (opt->arg)
     121                 :             :                         {
     122                 :           2 :                                 char       *p = defGetString(opt);
     123                 :             : 
     124   [ +  -  -  + ]:           2 :                                 if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
     125                 :           0 :                                         es->serialize = EXPLAIN_SERIALIZE_NONE;
     126         [ +  + ]:           2 :                                 else if (strcmp(p, "text") == 0)
     127                 :           1 :                                         es->serialize = EXPLAIN_SERIALIZE_TEXT;
     128         [ -  + ]:           1 :                                 else if (strcmp(p, "binary") == 0)
     129                 :           1 :                                         es->serialize = EXPLAIN_SERIALIZE_BINARY;
     130                 :             :                                 else
     131   [ #  #  #  # ]:           0 :                                         ereport(ERROR,
     132                 :             :                                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     133                 :             :                                                          errmsg("unrecognized value for %s option \"%s\": \"%s\"",
     134                 :             :                                                                         "EXPLAIN", opt->defname, p),
     135                 :             :                                                          parser_errposition(pstate, opt->location)));
     136                 :           2 :                         }
     137                 :             :                         else
     138                 :             :                         {
     139                 :             :                                 /* SERIALIZE without an argument is taken as 'text' */
     140                 :           3 :                                 es->serialize = EXPLAIN_SERIALIZE_TEXT;
     141                 :             :                         }
     142                 :           5 :                 }
     143         [ +  + ]:         168 :                 else if (strcmp(opt->defname, "format") == 0)
     144                 :             :                 {
     145                 :          46 :                         char       *p = defGetString(opt);
     146                 :             : 
     147         [ +  + ]:          46 :                         if (strcmp(p, "text") == 0)
     148                 :           2 :                                 es->format = EXPLAIN_FORMAT_TEXT;
     149         [ +  + ]:          44 :                         else if (strcmp(p, "xml") == 0)
     150                 :           1 :                                 es->format = EXPLAIN_FORMAT_XML;
     151         [ +  + ]:          43 :                         else if (strcmp(p, "json") == 0)
     152                 :          41 :                                 es->format = EXPLAIN_FORMAT_JSON;
     153         [ -  + ]:           2 :                         else if (strcmp(p, "yaml") == 0)
     154                 :           2 :                                 es->format = EXPLAIN_FORMAT_YAML;
     155                 :             :                         else
     156   [ #  #  #  # ]:           0 :                                 ereport(ERROR,
     157                 :             :                                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     158                 :             :                                                  errmsg("unrecognized value for %s option \"%s\": \"%s\"",
     159                 :             :                                                                 "EXPLAIN", opt->defname, p),
     160                 :             :                                                  parser_errposition(pstate, opt->location)));
     161                 :          46 :                 }
     162         [ -  + ]:         122 :                 else if (!ApplyExtensionExplainOption(es, opt, pstate))
     163   [ #  #  #  # ]:           0 :                         ereport(ERROR,
     164                 :             :                                         (errcode(ERRCODE_SYNTAX_ERROR),
     165                 :             :                                          errmsg("unrecognized %s option \"%s\"",
     166                 :             :                                                         "EXPLAIN", opt->defname),
     167                 :             :                                          parser_errposition(pstate, opt->location)));
     168                 :        3854 :         }
     169                 :             : 
     170                 :             :         /* check that WAL is used with EXPLAIN ANALYZE */
     171   [ -  +  #  # ]:        3633 :         if (es->wal && !es->analyze)
     172   [ #  #  #  # ]:           0 :                 ereport(ERROR,
     173                 :             :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     174                 :             :                                  errmsg("EXPLAIN option %s requires ANALYZE", "WAL")));
     175                 :             : 
     176                 :             :         /* if the timing was not set explicitly, set default value */
     177         [ +  + ]:        3633 :         es->timing = (timing_set) ? es->timing : es->analyze;
     178                 :             : 
     179                 :             :         /* if the buffers was not set explicitly, set default value */
     180         [ +  + ]:        3633 :         es->buffers = (buffers_set) ? es->buffers : es->analyze;
     181                 :             : 
     182                 :             :         /* check that timing is used with EXPLAIN ANALYZE */
     183   [ +  +  +  - ]:        3633 :         if (es->timing && !es->analyze)
     184   [ #  #  #  # ]:           0 :                 ereport(ERROR,
     185                 :             :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     186                 :             :                                  errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
     187                 :             : 
     188                 :             :         /* check that serialize is used with EXPLAIN ANALYZE */
     189   [ +  +  +  - ]:        3633 :         if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
     190   [ #  #  #  # ]:           0 :                 ereport(ERROR,
     191                 :             :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     192                 :             :                                  errmsg("EXPLAIN option %s requires ANALYZE", "SERIALIZE")));
     193                 :             : 
     194                 :             :         /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
     195   [ +  +  +  + ]:        3633 :         if (es->generic && es->analyze)
     196   [ +  -  +  - ]:           1 :                 ereport(ERROR,
     197                 :             :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     198                 :             :                                  errmsg("%s options %s and %s cannot be used together",
     199                 :             :                                                 "EXPLAIN", "ANALYZE", "GENERIC_PLAN")));
     200                 :             : 
     201                 :             :         /* if the summary was not set explicitly, set default value */
     202         [ +  + ]:        3632 :         es->summary = (summary_set) ? es->summary : es->analyze;
     203                 :             : 
     204                 :             :         /* plugin specific option validation */
     205         [ +  - ]:        3632 :         if (explain_validate_options_hook)
     206                 :           0 :                 (*explain_validate_options_hook) (es, options, pstate);
     207                 :        3632 : }
     208                 :             : 
     209                 :             : /*
     210                 :             :  * Map the name of an EXPLAIN extension to an integer ID.
     211                 :             :  *
     212                 :             :  * Within the lifetime of a particular backend, the same name will be mapped
     213                 :             :  * to the same ID every time. IDs are not stable across backends. Use the ID
     214                 :             :  * that you get from this function to call GetExplainExtensionState and
     215                 :             :  * SetExplainExtensionState.
     216                 :             :  *
     217                 :             :  * extension_name is assumed to be a constant string or allocated in storage
     218                 :             :  * that will never be freed.
     219                 :             :  */
     220                 :             : int
     221                 :          13 : GetExplainExtensionId(const char *extension_name)
     222                 :             : {
     223                 :             :         /* Search for an existing extension by this name; if found, return ID. */
     224   [ -  +  -  -  :          13 :         for (int i = 0; i < ExplainExtensionNamesAssigned; ++i)
                      + ]
     225         [ #  # ]:           0 :                 if (strcmp(ExplainExtensionNameArray[i], extension_name) == 0)
     226                 :           0 :                         return i;
     227                 :             : 
     228                 :             :         /* If there is no array yet, create one. */
     229         [ -  + ]:          13 :         if (ExplainExtensionNameArray == NULL)
     230                 :             :         {
     231                 :          13 :                 ExplainExtensionNamesAllocated = 16;
     232                 :          13 :                 ExplainExtensionNameArray = (const char **)
     233                 :          26 :                         MemoryContextAlloc(TopMemoryContext,
     234                 :          13 :                                                            ExplainExtensionNamesAllocated
     235                 :          13 :                                                            * sizeof(char *));
     236                 :          13 :         }
     237                 :             : 
     238                 :             :         /* If there's an array but it's currently full, expand it. */
     239         [ +  - ]:          13 :         if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
     240                 :             :         {
     241                 :           0 :                 int                     i = pg_nextpower2_32(ExplainExtensionNamesAssigned + 1);
     242                 :             : 
     243                 :           0 :                 ExplainExtensionNameArray = (const char **)
     244                 :           0 :                         repalloc(ExplainExtensionNameArray, i * sizeof(char *));
     245                 :           0 :                 ExplainExtensionNamesAllocated = i;
     246                 :           0 :         }
     247                 :             : 
     248                 :             :         /* Assign and return new ID. */
     249                 :          13 :         ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
     250                 :          13 :         return ExplainExtensionNamesAssigned++;
     251                 :          13 : }
     252                 :             : 
     253                 :             : /*
     254                 :             :  * Get extension-specific state from an ExplainState.
     255                 :             :  *
     256                 :             :  * See comments for SetExplainExtensionState, below.
     257                 :             :  */
     258                 :             : void *
     259                 :        3866 : GetExplainExtensionState(ExplainState *es, int extension_id)
     260                 :             : {
     261         [ +  - ]:        3866 :         Assert(extension_id >= 0);
     262                 :             : 
     263         [ +  + ]:        3866 :         if (extension_id >= es->extension_state_allocated)
     264                 :        3625 :                 return NULL;
     265                 :             : 
     266                 :         241 :         return es->extension_state[extension_id];
     267                 :        3866 : }
     268                 :             : 
     269                 :             : /*
     270                 :             :  * Store extension-specific state into an ExplainState.
     271                 :             :  *
     272                 :             :  * To use this function, first obtain an integer extension_id using
     273                 :             :  * GetExplainExtensionId. Then use this function to store an opaque pointer
     274                 :             :  * in the ExplainState. Later, you can retrieve the opaque pointer using
     275                 :             :  * GetExplainExtensionState.
     276                 :             :  */
     277                 :             : void
     278                 :         122 : SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
     279                 :             : {
     280         [ +  - ]:         122 :         Assert(extension_id >= 0);
     281                 :             : 
     282                 :             :         /* If there is no array yet, create one. */
     283         [ -  + ]:         122 :         if (es->extension_state == NULL)
     284                 :             :         {
     285                 :         122 :                 es->extension_state_allocated =
     286         [ +  - ]:         122 :                         Max(16, pg_nextpower2_32(extension_id + 1));
     287                 :         122 :                 es->extension_state =
     288                 :         122 :                         palloc0(es->extension_state_allocated * sizeof(void *));
     289                 :         122 :         }
     290                 :             : 
     291                 :             :         /* If there's an array but it's currently full, expand it. */
     292         [ +  - ]:         122 :         if (extension_id >= es->extension_state_allocated)
     293                 :             :         {
     294                 :           0 :                 int                     i;
     295                 :             : 
     296                 :           0 :                 i = pg_nextpower2_32(extension_id + 1);
     297                 :           0 :                 es->extension_state = repalloc0_array(es->extension_state, void *, es->extension_state_allocated, i);
     298                 :           0 :                 es->extension_state_allocated = i;
     299                 :           0 :         }
     300                 :             : 
     301                 :         122 :         es->extension_state[extension_id] = opaque;
     302                 :         122 : }
     303                 :             : 
     304                 :             : /*
     305                 :             :  * Register a new EXPLAIN option.
     306                 :             :  *
     307                 :             :  * When option_name is used as an EXPLAIN option, handler will be called and
     308                 :             :  * should update the ExplainState passed to it. See comments at top of file
     309                 :             :  * for a more detailed explanation.
     310                 :             :  *
     311                 :             :  * option_name is assumed to be a constant string or allocated in storage
     312                 :             :  * that will never be freed.
     313                 :             :  */
     314                 :             : void
     315                 :          13 : RegisterExtensionExplainOption(const char *option_name,
     316                 :             :                                                            ExplainOptionHandler handler)
     317                 :             : {
     318                 :          13 :         ExplainExtensionOption *exopt;
     319                 :             : 
     320                 :             :         /* Search for an existing option by this name; if found, update handler. */
     321   [ -  +  -  + ]:          13 :         for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     322                 :             :         {
     323                 :           0 :                 if (strcmp(ExplainExtensionOptionArray[i].option_name,
     324   [ #  #  #  # ]:           0 :                                    option_name) == 0)
     325                 :             :                 {
     326                 :           0 :                         ExplainExtensionOptionArray[i].option_handler = handler;
     327                 :           0 :                         return;
     328                 :             :                 }
     329                 :           0 :         }
     330                 :             : 
     331                 :             :         /* If there is no array yet, create one. */
     332         [ -  + ]:          13 :         if (ExplainExtensionOptionArray == NULL)
     333                 :             :         {
     334                 :          13 :                 ExplainExtensionOptionsAllocated = 16;
     335                 :          13 :                 ExplainExtensionOptionArray = (ExplainExtensionOption *)
     336                 :          26 :                         MemoryContextAlloc(TopMemoryContext,
     337                 :          13 :                                                            ExplainExtensionOptionsAllocated
     338                 :          13 :                                                            * sizeof(char *));
     339                 :          13 :         }
     340                 :             : 
     341                 :             :         /* If there's an array but it's currently full, expand it. */
     342         [ +  - ]:          13 :         if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
     343                 :             :         {
     344                 :           0 :                 int                     i = pg_nextpower2_32(ExplainExtensionOptionsAssigned + 1);
     345                 :             : 
     346                 :           0 :                 ExplainExtensionOptionArray = (ExplainExtensionOption *)
     347                 :           0 :                         repalloc(ExplainExtensionOptionArray, i * sizeof(char *));
     348                 :           0 :                 ExplainExtensionOptionsAllocated = i;
     349                 :           0 :         }
     350                 :             : 
     351                 :             :         /* Assign and return new ID. */
     352                 :          13 :         exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
     353                 :          13 :         exopt->option_name = option_name;
     354                 :          13 :         exopt->option_handler = handler;
     355         [ -  + ]:          13 : }
     356                 :             : 
     357                 :             : /*
     358                 :             :  * Apply an EXPLAIN option registered by an extension.
     359                 :             :  *
     360                 :             :  * If no extension has registered the named option, returns false. Otherwise,
     361                 :             :  * calls the appropriate handler function and then returns true.
     362                 :             :  */
     363                 :             : bool
     364                 :         122 : ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
     365                 :             : {
     366   [ +  -  -  +  :         244 :         for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
                      - ]
     367                 :             :         {
     368                 :         244 :                 if (strcmp(ExplainExtensionOptionArray[i].option_name,
     369   [ +  -  +  - ]:         244 :                                    opt->defname) == 0)
     370                 :             :                 {
     371                 :         122 :                         ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
     372                 :         122 :                         return true;
     373                 :             :                 }
     374                 :           0 :         }
     375                 :             : 
     376                 :           0 :         return false;
     377                 :         122 : }
        

Generated by: LCOV version 2.3.2-1