LCOV - code coverage report
Current view: top level - src/backend/commands - explain_state.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 103 128 80.5 %
Date: 2025-04-01 14:15:22 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       23502 : NewExplainState(void)
      62             : {
      63       23502 :     ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
      64             : 
      65             :     /* Set default options (most fields can be left as zeroes). */
      66       23502 :     es->costs = true;
      67             :     /* Prepare output buffer. */
      68       23502 :     es->str = makeStringInfo();
      69             : 
      70       23502 :     return es;
      71             : }
      72             : 
      73             : /*
      74             :  * Parse a list of EXPLAIN options and update an ExplainState accordingly.
      75             :  */
      76             : void
      77       23482 : ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
      78             : {
      79             :     ListCell   *lc;
      80       23482 :     bool        timing_set = false;
      81       23482 :     bool        buffers_set = false;
      82       23482 :     bool        summary_set = false;
      83             : 
      84             :     /* Parse options list. */
      85       45430 :     foreach(lc, options)
      86             :     {
      87       21956 :         DefElem    *opt = (DefElem *) lfirst(lc);
      88             : 
      89       21956 :         if (strcmp(opt->defname, "analyze") == 0)
      90        3436 :             es->analyze = defGetBoolean(opt);
      91       18520 :         else if (strcmp(opt->defname, "verbose") == 0)
      92        2404 :             es->verbose = defGetBoolean(opt);
      93       16116 :         else if (strcmp(opt->defname, "costs") == 0)
      94       13076 :             es->costs = defGetBoolean(opt);
      95        3040 :         else if (strcmp(opt->defname, "buffers") == 0)
      96             :         {
      97         928 :             buffers_set = true;
      98         928 :             es->buffers = defGetBoolean(opt);
      99             :         }
     100        2112 :         else if (strcmp(opt->defname, "wal") == 0)
     101           0 :             es->wal = defGetBoolean(opt);
     102        2112 :         else if (strcmp(opt->defname, "settings") == 0)
     103          12 :             es->settings = defGetBoolean(opt);
     104        2100 :         else if (strcmp(opt->defname, "generic_plan") == 0)
     105          18 :             es->generic = defGetBoolean(opt);
     106        2082 :         else if (strcmp(opt->defname, "timing") == 0)
     107             :         {
     108         852 :             timing_set = true;
     109         852 :             es->timing = defGetBoolean(opt);
     110             :         }
     111        1230 :         else if (strcmp(opt->defname, "summary") == 0)
     112             :         {
     113         828 :             summary_set = true;
     114         828 :             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 EXPLAIN option \"%s\": \"%s\"",
     134             :                                     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 EXPLAIN option \"%s\": \"%s\"",
     159             :                                 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 EXPLAIN option \"%s\"",
     166             :                             opt->defname),
     167             :                      parser_errposition(pstate, opt->location)));
     168             :     }
     169             : 
     170             :     /* check that WAL is used with EXPLAIN ANALYZE */
     171       23474 :     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       23474 :     es->timing = (timing_set) ? es->timing : es->analyze;
     178             : 
     179             :     /* if the buffers was not set explicitly, set default value */
     180       23474 :     es->buffers = (buffers_set) ? es->buffers : es->analyze;
     181             : 
     182             :     /* check that timing is used with EXPLAIN ANALYZE */
     183       23474 :     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       23474 :     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       23474 :     if (es->generic && es->analyze)
     196           6 :         ereport(ERROR,
     197             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     198             :                  errmsg("EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together")));
     199             : 
     200             :     /* if the summary was not set explicitly, set default value */
     201       23468 :     es->summary = (summary_set) ? es->summary : es->analyze;
     202             : 
     203             :     /* plugin specific option validation */
     204       23468 :     if (explain_validate_options_hook)
     205           0 :         (*explain_validate_options_hook) (es, options, pstate);
     206       23468 : }
     207             : 
     208             : /*
     209             :  * Map the name of an EXPLAIN extension to an integer ID.
     210             :  *
     211             :  * Within the lifetime of a particular backend, the same name will be mapped
     212             :  * to the same ID every time. IDs are not stable across backends. Use the ID
     213             :  * that you get from this function to call GetExplainExtensionState and
     214             :  * SetExplainExtensionState.
     215             :  *
     216             :  * extension_name is assumed to be a constant string or allocated in storage
     217             :  * that will never be freed.
     218             :  */
     219             : int
     220           2 : GetExplainExtensionId(const char *extension_name)
     221             : {
     222             :     /* Search for an existing extension by this name; if found, return ID. */
     223           2 :     for (int i = 0; i < ExplainExtensionNamesAssigned; ++i)
     224           0 :         if (strcmp(ExplainExtensionNameArray[i], extension_name) == 0)
     225           0 :             return i;
     226             : 
     227             :     /* If there is no array yet, create one. */
     228           2 :     if (ExplainExtensionNameArray == NULL)
     229             :     {
     230           2 :         ExplainExtensionNamesAllocated = 16;
     231           2 :         ExplainExtensionNameArray = (const char **)
     232           2 :             MemoryContextAlloc(TopMemoryContext,
     233             :                                ExplainExtensionNamesAllocated
     234             :                                * sizeof(char *));
     235             :     }
     236             : 
     237             :     /* If there's an array but it's currently full, expand it. */
     238           2 :     if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
     239             :     {
     240           0 :         int         i = pg_nextpower2_32(ExplainExtensionNamesAssigned + 1);
     241             : 
     242           0 :         ExplainExtensionNameArray = (const char **)
     243           0 :             repalloc(ExplainExtensionNameArray, i * sizeof(char *));
     244           0 :         ExplainExtensionNamesAllocated = i;
     245             :     }
     246             : 
     247             :     /* Assign and return new ID. */
     248           2 :     ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
     249           2 :     return ExplainExtensionNamesAssigned++;
     250             : }
     251             : 
     252             : /*
     253             :  * Get extension-specific state from an ExplainState.
     254             :  *
     255             :  * See comments for SetExplainExtensionState, below.
     256             :  */
     257             : void *
     258         100 : GetExplainExtensionState(ExplainState *es, int extension_id)
     259             : {
     260             :     Assert(extension_id >= 0);
     261             : 
     262         100 :     if (extension_id >= es->extension_state_allocated)
     263          18 :         return NULL;
     264             : 
     265          82 :     return es->extension_state[extension_id];
     266             : }
     267             : 
     268             : /*
     269             :  * Store extension-specific state into an ExplainState.
     270             :  *
     271             :  * To use this function, first obtain an integer extension_id using
     272             :  * GetExplainExtensionId. Then use this function to store an opaque pointer
     273             :  * in the ExplainState. Later, you can retrieve the opaque pointer using
     274             :  * GetExplainExtensionState.
     275             :  */
     276             : void
     277          18 : SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
     278             : {
     279             :     Assert(extension_id >= 0);
     280             : 
     281             :     /* If there is no array yet, create one. */
     282          18 :     if (es->extension_state == NULL)
     283             :     {
     284          18 :         es->extension_state_allocated = 16;
     285          18 :         es->extension_state =
     286          18 :             palloc0(es->extension_state_allocated * sizeof(void *));
     287             :     }
     288             : 
     289             :     /* If there's an array but it's currently full, expand it. */
     290          18 :     if (extension_id >= es->extension_state_allocated)
     291             :     {
     292             :         int         i;
     293             : 
     294           0 :         i = pg_nextpower2_32(es->extension_state_allocated + 1);
     295           0 :         es->extension_state = (void **)
     296           0 :             repalloc0(es->extension_state,
     297           0 :                       es->extension_state_allocated * sizeof(void *),
     298             :                       i * sizeof(void *));
     299           0 :         es->extension_state_allocated = i;
     300             :     }
     301             : 
     302          18 :     es->extension_state[extension_id] = opaque;
     303          18 : }
     304             : 
     305             : /*
     306             :  * Register a new EXPLAIN option.
     307             :  *
     308             :  * When option_name is used as an EXPLAIN option, handler will be called and
     309             :  * should update the ExplainState passed to it. See comments at top of file
     310             :  * for a more detailed explanation.
     311             :  *
     312             :  * option_name is assumed to be a constant string or allocated in storage
     313             :  * that will never be freed.
     314             :  */
     315             : void
     316           4 : RegisterExtensionExplainOption(const char *option_name,
     317             :                                ExplainOptionHandler handler)
     318             : {
     319             :     ExplainExtensionOption *exopt;
     320             : 
     321             :     /* Search for an existing option by this name; if found, update handler. */
     322           6 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     323             :     {
     324           2 :         if (strcmp(ExplainExtensionOptionArray[i].option_name,
     325             :                    option_name) == 0)
     326             :         {
     327           0 :             ExplainExtensionOptionArray[i].option_handler = handler;
     328           0 :             return;
     329             :         }
     330             :     }
     331             : 
     332             :     /* If there is no array yet, create one. */
     333           4 :     if (ExplainExtensionOptionArray == NULL)
     334             :     {
     335           2 :         ExplainExtensionOptionsAllocated = 16;
     336           2 :         ExplainExtensionOptionArray = (ExplainExtensionOption *)
     337           2 :             MemoryContextAlloc(TopMemoryContext,
     338             :                                ExplainExtensionOptionsAllocated
     339             :                                * sizeof(char *));
     340             :     }
     341             : 
     342             :     /* If there's an array but it's currently full, expand it. */
     343           4 :     if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
     344             :     {
     345           0 :         int         i = pg_nextpower2_32(ExplainExtensionOptionsAssigned + 1);
     346             : 
     347           0 :         ExplainExtensionOptionArray = (ExplainExtensionOption *)
     348           0 :             repalloc(ExplainExtensionOptionArray, i * sizeof(char *));
     349           0 :         ExplainExtensionOptionsAllocated = i;
     350             :     }
     351             : 
     352             :     /* Assign and return new ID. */
     353           4 :     exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
     354           4 :     exopt->option_name = option_name;
     355           4 :     exopt->option_handler = handler;
     356             : }
     357             : 
     358             : /*
     359             :  * Apply an EXPLAIN option registered by an extension.
     360             :  *
     361             :  * If no extension has registered the named option, returns false. Otherwise,
     362             :  * calls the appropriate handler function and then returns true.
     363             :  */
     364             : bool
     365          30 : ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
     366             : {
     367          44 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     368             :     {
     369          36 :         if (strcmp(ExplainExtensionOptionArray[i].option_name,
     370          36 :                    opt->defname) == 0)
     371             :         {
     372          22 :             ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
     373          22 :             return true;
     374             :         }
     375             :     }
     376             : 
     377           8 :     return false;
     378             : }

Generated by: LCOV version 1.14