LCOV - code coverage report
Current view: top level - src/backend/commands - explain_state.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 104 127 81.9 %
Date: 2025-12-11 21:18:53 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * explain_state.c
       4             :  *    Code for initializing and accessing ExplainState objects
       5             :  *
       6             :  * Portions Copyright (c) 1996-2025, 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       24620 : NewExplainState(void)
      62             : {
      63       24620 :     ExplainState *es = palloc0_object(ExplainState);
      64             : 
      65             :     /* Set default options (most fields can be left as zeroes). */
      66       24620 :     es->costs = true;
      67             :     /* Prepare output buffer. */
      68       24620 :     es->str = makeStringInfo();
      69             : 
      70       24620 :     return es;
      71             : }
      72             : 
      73             : /*
      74             :  * Parse a list of EXPLAIN options and update an ExplainState accordingly.
      75             :  */
      76             : void
      77       24600 : ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
      78             : {
      79             :     ListCell   *lc;
      80       24600 :     bool        timing_set = false;
      81       24600 :     bool        buffers_set = false;
      82       24600 :     bool        summary_set = false;
      83             : 
      84             :     /* Parse options list. */
      85       48144 :     foreach(lc, options)
      86             :     {
      87       23552 :         DefElem    *opt = (DefElem *) lfirst(lc);
      88             : 
      89       23552 :         if (strcmp(opt->defname, "analyze") == 0)
      90        3504 :             es->analyze = defGetBoolean(opt);
      91       20048 :         else if (strcmp(opt->defname, "verbose") == 0)
      92        2730 :             es->verbose = defGetBoolean(opt);
      93       17318 :         else if (strcmp(opt->defname, "costs") == 0)
      94       14110 :             es->costs = defGetBoolean(opt);
      95        3208 :         else if (strcmp(opt->defname, "buffers") == 0)
      96             :         {
      97         984 :             buffers_set = true;
      98         984 :             es->buffers = defGetBoolean(opt);
      99             :         }
     100        2224 :         else if (strcmp(opt->defname, "wal") == 0)
     101           0 :             es->wal = defGetBoolean(opt);
     102        2224 :         else if (strcmp(opt->defname, "settings") == 0)
     103          12 :             es->settings = defGetBoolean(opt);
     104        2212 :         else if (strcmp(opt->defname, "generic_plan") == 0)
     105          18 :             es->generic = defGetBoolean(opt);
     106        2194 :         else if (strcmp(opt->defname, "timing") == 0)
     107             :         {
     108         908 :             timing_set = true;
     109         908 :             es->timing = defGetBoolean(opt);
     110             :         }
     111        1286 :         else if (strcmp(opt->defname, "summary") == 0)
     112             :         {
     113         884 :             summary_set = true;
     114         884 :             es->summary = defGetBoolean(opt);
     115             :         }
     116         402 :         else if (strcmp(opt->defname, "memory") == 0)
     117          30 :             es->memory = defGetBoolean(opt);
     118         372 :         else if (strcmp(opt->defname, "serialize") == 0)
     119             :         {
     120          30 :             if (opt->arg)
     121             :             {
     122          12 :                 char       *p = defGetString(opt);
     123             : 
     124          12 :                 if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
     125           0 :                     es->serialize = EXPLAIN_SERIALIZE_NONE;
     126          12 :                 else if (strcmp(p, "text") == 0)
     127           6 :                     es->serialize = EXPLAIN_SERIALIZE_TEXT;
     128           6 :                 else if (strcmp(p, "binary") == 0)
     129           6 :                     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             :             }
     137             :             else
     138             :             {
     139             :                 /* SERIALIZE without an argument is taken as 'text' */
     140          18 :                 es->serialize = EXPLAIN_SERIALIZE_TEXT;
     141             :             }
     142             :         }
     143         342 :         else if (strcmp(opt->defname, "format") == 0)
     144             :         {
     145         312 :             char       *p = defGetString(opt);
     146             : 
     147         312 :             if (strcmp(p, "text") == 0)
     148          12 :                 es->format = EXPLAIN_FORMAT_TEXT;
     149         300 :             else if (strcmp(p, "xml") == 0)
     150           8 :                 es->format = EXPLAIN_FORMAT_XML;
     151         292 :             else if (strcmp(p, "json") == 0)
     152         280 :                 es->format = EXPLAIN_FORMAT_JSON;
     153          12 :             else if (strcmp(p, "yaml") == 0)
     154          12 :                 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             :         }
     162          30 :         else if (!ApplyExtensionExplainOption(es, opt, pstate))
     163           8 :             ereport(ERROR,
     164             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     165             :                      errmsg("unrecognized %s option \"%s\"",
     166             :                             "EXPLAIN", opt->defname),
     167             :                      parser_errposition(pstate, opt->location)));
     168             :     }
     169             : 
     170             :     /* check that WAL is used with EXPLAIN ANALYZE */
     171       24592 :     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       24592 :     es->timing = (timing_set) ? es->timing : es->analyze;
     178             : 
     179             :     /* if the buffers was not set explicitly, set default value */
     180       24592 :     es->buffers = (buffers_set) ? es->buffers : es->analyze;
     181             : 
     182             :     /* check that timing is used with EXPLAIN ANALYZE */
     183       24592 :     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       24592 :     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       24592 :     if (es->generic && es->analyze)
     196           6 :         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       24586 :     es->summary = (summary_set) ? es->summary : es->analyze;
     203             : 
     204             :     /* plugin specific option validation */
     205       24586 :     if (explain_validate_options_hook)
     206           0 :         (*explain_validate_options_hook) (es, options, pstate);
     207       24586 : }
     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           2 : GetExplainExtensionId(const char *extension_name)
     222             : {
     223             :     /* Search for an existing extension by this name; if found, return ID. */
     224           2 :     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           2 :     if (ExplainExtensionNameArray == NULL)
     230             :     {
     231           2 :         ExplainExtensionNamesAllocated = 16;
     232           2 :         ExplainExtensionNameArray = (const char **)
     233           2 :             MemoryContextAlloc(TopMemoryContext,
     234             :                                ExplainExtensionNamesAllocated
     235             :                                * sizeof(char *));
     236             :     }
     237             : 
     238             :     /* If there's an array but it's currently full, expand it. */
     239           2 :     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             :     }
     247             : 
     248             :     /* Assign and return new ID. */
     249           2 :     ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
     250           2 :     return ExplainExtensionNamesAssigned++;
     251             : }
     252             : 
     253             : /*
     254             :  * Get extension-specific state from an ExplainState.
     255             :  *
     256             :  * See comments for SetExplainExtensionState, below.
     257             :  */
     258             : void *
     259         100 : GetExplainExtensionState(ExplainState *es, int extension_id)
     260             : {
     261             :     Assert(extension_id >= 0);
     262             : 
     263         100 :     if (extension_id >= es->extension_state_allocated)
     264          18 :         return NULL;
     265             : 
     266          82 :     return es->extension_state[extension_id];
     267             : }
     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          18 : SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
     279             : {
     280             :     Assert(extension_id >= 0);
     281             : 
     282             :     /* If there is no array yet, create one. */
     283          18 :     if (es->extension_state == NULL)
     284             :     {
     285          18 :         es->extension_state_allocated =
     286          18 :             Max(16, pg_nextpower2_32(extension_id + 1));
     287          18 :         es->extension_state =
     288          18 :             palloc0(es->extension_state_allocated * sizeof(void *));
     289             :     }
     290             : 
     291             :     /* If there's an array but it's currently full, expand it. */
     292          18 :     if (extension_id >= es->extension_state_allocated)
     293             :     {
     294             :         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             :     }
     300             : 
     301          18 :     es->extension_state[extension_id] = opaque;
     302          18 : }
     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           4 : RegisterExtensionExplainOption(const char *option_name,
     316             :                                ExplainOptionHandler handler)
     317             : {
     318             :     ExplainExtensionOption *exopt;
     319             : 
     320             :     /* Search for an existing option by this name; if found, update handler. */
     321           6 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     322             :     {
     323           2 :         if (strcmp(ExplainExtensionOptionArray[i].option_name,
     324             :                    option_name) == 0)
     325             :         {
     326           0 :             ExplainExtensionOptionArray[i].option_handler = handler;
     327           0 :             return;
     328             :         }
     329             :     }
     330             : 
     331             :     /* If there is no array yet, create one. */
     332           4 :     if (ExplainExtensionOptionArray == NULL)
     333             :     {
     334           2 :         ExplainExtensionOptionsAllocated = 16;
     335           2 :         ExplainExtensionOptionArray = (ExplainExtensionOption *)
     336           2 :             MemoryContextAlloc(TopMemoryContext,
     337             :                                ExplainExtensionOptionsAllocated
     338             :                                * sizeof(char *));
     339             :     }
     340             : 
     341             :     /* If there's an array but it's currently full, expand it. */
     342           4 :     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             :     }
     350             : 
     351             :     /* Assign and return new ID. */
     352           4 :     exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
     353           4 :     exopt->option_name = option_name;
     354           4 :     exopt->option_handler = handler;
     355             : }
     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          30 : ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
     365             : {
     366          44 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     367             :     {
     368          36 :         if (strcmp(ExplainExtensionOptionArray[i].option_name,
     369          36 :                    opt->defname) == 0)
     370             :         {
     371          22 :             ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
     372          22 :             return true;
     373             :         }
     374             :     }
     375             : 
     376           8 :     return false;
     377             : }

Generated by: LCOV version 1.16