LCOV - code coverage report
Current view: top level - src/backend/commands - explain_state.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 83.9 % 161 135
Test Date: 2026-05-02 16:16:32 Functions: 100.0 % 9 9
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-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              : #include "utils/builtins.h"
      40              : #include "utils/guc.h"
      41              : 
      42              : /* Hook to perform additional EXPLAIN options validation */
      43              : explain_validate_options_hook_type explain_validate_options_hook = NULL;
      44              : 
      45              : typedef struct
      46              : {
      47              :     const char *option_name;
      48              :     ExplainOptionHandler option_handler;
      49              :     ExplainOptionGUCCheckHandler guc_check_handler;
      50              : } ExplainExtensionOption;
      51              : 
      52              : static const char **ExplainExtensionNameArray = NULL;
      53              : static int  ExplainExtensionNamesAssigned = 0;
      54              : static int  ExplainExtensionNamesAllocated = 0;
      55              : 
      56              : static ExplainExtensionOption *ExplainExtensionOptionArray = NULL;
      57              : static int  ExplainExtensionOptionsAssigned = 0;
      58              : static int  ExplainExtensionOptionsAllocated = 0;
      59              : 
      60              : /*
      61              :  * Create a new ExplainState struct initialized with default options.
      62              :  */
      63              : ExplainState *
      64        16453 : NewExplainState(void)
      65              : {
      66        16453 :     ExplainState *es = palloc0_object(ExplainState);
      67              : 
      68              :     /* Set default options (most fields can be left as zeroes). */
      69        16453 :     es->costs = true;
      70              :     /* Prepare output buffer. */
      71        16453 :     es->str = makeStringInfo();
      72              : 
      73        16453 :     return es;
      74              : }
      75              : 
      76              : /*
      77              :  * Parse a list of EXPLAIN options and update an ExplainState accordingly.
      78              :  */
      79              : void
      80        16441 : ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
      81              : {
      82              :     ListCell   *lc;
      83        16441 :     bool        timing_set = false;
      84        16441 :     bool        buffers_set = false;
      85        16441 :     bool        summary_set = false;
      86              : 
      87              :     /* Parse options list. */
      88        32684 :     foreach(lc, options)
      89              :     {
      90        16247 :         DefElem    *opt = (DefElem *) lfirst(lc);
      91              : 
      92        16247 :         if (strcmp(opt->defname, "analyze") == 0)
      93         2362 :             es->analyze = defGetBoolean(opt);
      94        13885 :         else if (strcmp(opt->defname, "verbose") == 0)
      95         1751 :             es->verbose = defGetBoolean(opt);
      96        12134 :         else if (strcmp(opt->defname, "costs") == 0)
      97         9863 :             es->costs = defGetBoolean(opt);
      98         2271 :         else if (strcmp(opt->defname, "buffers") == 0)
      99              :         {
     100          650 :             buffers_set = true;
     101          650 :             es->buffers = defGetBoolean(opt);
     102              :         }
     103         1621 :         else if (strcmp(opt->defname, "wal") == 0)
     104            0 :             es->wal = defGetBoolean(opt);
     105         1621 :         else if (strcmp(opt->defname, "settings") == 0)
     106            8 :             es->settings = defGetBoolean(opt);
     107         1613 :         else if (strcmp(opt->defname, "generic_plan") == 0)
     108           12 :             es->generic = defGetBoolean(opt);
     109         1601 :         else if (strcmp(opt->defname, "timing") == 0)
     110              :         {
     111          606 :             timing_set = true;
     112          606 :             es->timing = defGetBoolean(opt);
     113              :         }
     114          995 :         else if (strcmp(opt->defname, "summary") == 0)
     115              :         {
     116          590 :             summary_set = true;
     117          590 :             es->summary = defGetBoolean(opt);
     118              :         }
     119          405 :         else if (strcmp(opt->defname, "memory") == 0)
     120           20 :             es->memory = defGetBoolean(opt);
     121          385 :         else if (strcmp(opt->defname, "serialize") == 0)
     122              :         {
     123           20 :             if (opt->arg)
     124              :             {
     125            8 :                 char       *p = defGetString(opt);
     126              : 
     127            8 :                 if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
     128            0 :                     es->serialize = EXPLAIN_SERIALIZE_NONE;
     129            8 :                 else if (strcmp(p, "text") == 0)
     130            4 :                     es->serialize = EXPLAIN_SERIALIZE_TEXT;
     131            4 :                 else if (strcmp(p, "binary") == 0)
     132            4 :                     es->serialize = EXPLAIN_SERIALIZE_BINARY;
     133              :                 else
     134            0 :                     ereport(ERROR,
     135              :                             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     136              :                              errmsg("unrecognized value for %s option \"%s\": \"%s\"",
     137              :                                     "EXPLAIN", opt->defname, p),
     138              :                              parser_errposition(pstate, opt->location)));
     139              :             }
     140              :             else
     141              :             {
     142              :                 /* SERIALIZE without an argument is taken as 'text' */
     143           12 :                 es->serialize = EXPLAIN_SERIALIZE_TEXT;
     144              :             }
     145              :         }
     146          365 :         else if (strcmp(opt->defname, "format") == 0)
     147              :         {
     148          204 :             char       *p = defGetString(opt);
     149              : 
     150          204 :             if (strcmp(p, "text") == 0)
     151            9 :                 es->format = EXPLAIN_FORMAT_TEXT;
     152          195 :             else if (strcmp(p, "xml") == 0)
     153            5 :                 es->format = EXPLAIN_FORMAT_XML;
     154          190 :             else if (strcmp(p, "json") == 0)
     155          182 :                 es->format = EXPLAIN_FORMAT_JSON;
     156            8 :             else if (strcmp(p, "yaml") == 0)
     157            8 :                 es->format = EXPLAIN_FORMAT_YAML;
     158              :             else
     159            0 :                 ereport(ERROR,
     160              :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     161              :                          errmsg("unrecognized value for %s option \"%s\": \"%s\"",
     162              :                                 "EXPLAIN", opt->defname, p),
     163              :                          parser_errposition(pstate, opt->location)));
     164              :         }
     165          161 :         else if (strcmp(opt->defname, "io") == 0)
     166            8 :             es->io = defGetBoolean(opt);
     167          153 :         else if (!ApplyExtensionExplainOption(es, opt, pstate))
     168            4 :             ereport(ERROR,
     169              :                     (errcode(ERRCODE_SYNTAX_ERROR),
     170              :                      errmsg("unrecognized %s option \"%s\"",
     171              :                             "EXPLAIN", opt->defname),
     172              :                      parser_errposition(pstate, opt->location)));
     173              :     }
     174              : 
     175              :     /* check that WAL is used with EXPLAIN ANALYZE */
     176        16437 :     if (es->wal && !es->analyze)
     177            0 :         ereport(ERROR,
     178              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     179              :                  errmsg("EXPLAIN option %s requires ANALYZE", "WAL")));
     180              : 
     181              :     /* if the timing was not set explicitly, set default value */
     182        16437 :     es->timing = (timing_set) ? es->timing : es->analyze;
     183              : 
     184              :     /* if the buffers was not set explicitly, set default value */
     185        16437 :     es->buffers = (buffers_set) ? es->buffers : es->analyze;
     186              : 
     187              :     /* check that timing is used with EXPLAIN ANALYZE */
     188        16437 :     if (es->timing && !es->analyze)
     189            0 :         ereport(ERROR,
     190              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     191              :                  errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
     192              : 
     193              :     /* check that IO is used with EXPLAIN ANALYZE */
     194        16437 :     if (es->io && !es->analyze)
     195            0 :         ereport(ERROR,
     196              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     197              :                  errmsg("EXPLAIN option %s requires ANALYZE", "IO")));
     198              : 
     199              :     /* check that serialize is used with EXPLAIN ANALYZE */
     200        16437 :     if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
     201            0 :         ereport(ERROR,
     202              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     203              :                  errmsg("EXPLAIN option %s requires ANALYZE", "SERIALIZE")));
     204              : 
     205              :     /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
     206        16437 :     if (es->generic && es->analyze)
     207            4 :         ereport(ERROR,
     208              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     209              :                  errmsg("%s options %s and %s cannot be used together",
     210              :                         "EXPLAIN", "ANALYZE", "GENERIC_PLAN")));
     211              : 
     212              :     /* if the summary was not set explicitly, set default value */
     213        16433 :     es->summary = (summary_set) ? es->summary : es->analyze;
     214              : 
     215              :     /* plugin specific option validation */
     216        16433 :     if (explain_validate_options_hook)
     217            0 :         (*explain_validate_options_hook) (es, options, pstate);
     218        16433 : }
     219              : 
     220              : /*
     221              :  * Map the name of an EXPLAIN extension to an integer ID.
     222              :  *
     223              :  * Within the lifetime of a particular backend, the same name will be mapped
     224              :  * to the same ID every time. IDs are not stable across backends. Use the ID
     225              :  * that you get from this function to call GetExplainExtensionState and
     226              :  * SetExplainExtensionState.
     227              :  *
     228              :  * extension_name is assumed to be a constant string or allocated in storage
     229              :  * that will never be freed.
     230              :  */
     231              : int
     232           35 : GetExplainExtensionId(const char *extension_name)
     233              : {
     234              :     /* Search for an existing extension by this name; if found, return ID. */
     235           35 :     for (int i = 0; i < ExplainExtensionNamesAssigned; ++i)
     236            0 :         if (strcmp(ExplainExtensionNameArray[i], extension_name) == 0)
     237            0 :             return i;
     238              : 
     239              :     /* If there is no array yet, create one. */
     240           35 :     if (ExplainExtensionNameArray == NULL)
     241              :     {
     242           35 :         ExplainExtensionNamesAllocated = 16;
     243           35 :         ExplainExtensionNameArray = (const char **)
     244           35 :             MemoryContextAlloc(TopMemoryContext,
     245              :                                ExplainExtensionNamesAllocated
     246              :                                * sizeof(char *));
     247              :     }
     248              : 
     249              :     /* If there's an array but it's currently full, expand it. */
     250           35 :     if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
     251              :     {
     252            0 :         int         i = pg_nextpower2_32(ExplainExtensionNamesAssigned + 1);
     253              : 
     254            0 :         ExplainExtensionNameArray = (const char **)
     255            0 :             repalloc(ExplainExtensionNameArray, i * sizeof(char *));
     256            0 :         ExplainExtensionNamesAllocated = i;
     257              :     }
     258              : 
     259              :     /* Assign and return new ID. */
     260           35 :     ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
     261           35 :     return ExplainExtensionNamesAssigned++;
     262              : }
     263              : 
     264              : /*
     265              :  * Get extension-specific state from an ExplainState.
     266              :  *
     267              :  * See comments for SetExplainExtensionState, below.
     268              :  */
     269              : void *
     270         7636 : GetExplainExtensionState(ExplainState *es, int extension_id)
     271              : {
     272              :     Assert(extension_id >= 0);
     273              : 
     274         7636 :     if (extension_id >= es->extension_state_allocated)
     275         7306 :         return NULL;
     276              : 
     277          330 :     return es->extension_state[extension_id];
     278              : }
     279              : 
     280              : /*
     281              :  * Store extension-specific state into an ExplainState.
     282              :  *
     283              :  * To use this function, first obtain an integer extension_id using
     284              :  * GetExplainExtensionId. Then use this function to store an opaque pointer
     285              :  * in the ExplainState. Later, you can retrieve the opaque pointer using
     286              :  * GetExplainExtensionState.
     287              :  */
     288              : void
     289          148 : SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
     290              : {
     291              :     Assert(extension_id >= 0);
     292              : 
     293              :     /* If there is no array yet, create one. */
     294          148 :     if (es->extension_state == NULL)
     295              :     {
     296          148 :         es->extension_state_allocated =
     297          148 :             Max(16, pg_nextpower2_32(extension_id + 1));
     298          148 :         es->extension_state =
     299          148 :             palloc0(es->extension_state_allocated * sizeof(void *));
     300              :     }
     301              : 
     302              :     /* If there's an array but it's currently full, expand it. */
     303          148 :     if (extension_id >= es->extension_state_allocated)
     304              :     {
     305              :         int         i;
     306              : 
     307            0 :         i = pg_nextpower2_32(extension_id + 1);
     308            0 :         es->extension_state = repalloc0_array(es->extension_state, void *, es->extension_state_allocated, i);
     309            0 :         es->extension_state_allocated = i;
     310              :     }
     311              : 
     312          148 :     es->extension_state[extension_id] = opaque;
     313          148 : }
     314              : 
     315              : /*
     316              :  * Register a new EXPLAIN option.
     317              :  *
     318              :  * option_name is assumed to be a constant string or allocated in storage
     319              :  * that will never be freed.
     320              :  *
     321              :  * When option_name is used as an EXPLAIN option, handler will be called and
     322              :  * should update the ExplainState passed to it. See comments at top of file
     323              :  * for a more detailed explanation.
     324              :  *
     325              :  * guc_check_handler is a function that can be safely called from a
     326              :  * GUC check hook to validate a proposed value for a custom EXPLAIN option.
     327              :  * Boolean-valued options can pass GUCCheckBooleanExplainOption. See the
     328              :  * comments for GUCCheckBooleanExplainOption for further information on
     329              :  * how a guc_check_handler should behave.
     330              :  */
     331              : void
     332           51 : RegisterExtensionExplainOption(const char *option_name,
     333              :                                ExplainOptionHandler handler,
     334              :                                ExplainOptionGUCCheckHandler guc_check_handler)
     335              : {
     336              :     ExplainExtensionOption *exopt;
     337              : 
     338              :     Assert(handler != NULL);
     339              :     Assert(guc_check_handler != NULL);
     340              : 
     341              :     /* Search for an existing option by this name; if found, update handler. */
     342           67 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     343              :     {
     344           16 :         if (strcmp(ExplainExtensionOptionArray[i].option_name,
     345              :                    option_name) == 0)
     346              :         {
     347            0 :             exopt = &ExplainExtensionOptionArray[i];
     348              : 
     349            0 :             exopt->option_handler = handler;
     350            0 :             exopt->guc_check_handler = guc_check_handler;
     351            0 :             return;
     352              :         }
     353              :     }
     354              : 
     355              :     /* If there is no array yet, create one. */
     356           51 :     if (ExplainExtensionOptionArray == NULL)
     357              :     {
     358           35 :         ExplainExtensionOptionsAllocated = 16;
     359           35 :         ExplainExtensionOptionArray = (ExplainExtensionOption *)
     360           35 :             MemoryContextAlloc(TopMemoryContext,
     361              :                                ExplainExtensionOptionsAllocated
     362              :                                * sizeof(ExplainExtensionOption));
     363              :     }
     364              : 
     365              :     /* If there's an array but it's currently full, expand it. */
     366           51 :     if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
     367              :     {
     368            0 :         int         i = pg_nextpower2_32(ExplainExtensionOptionsAssigned + 1);
     369              : 
     370            0 :         ExplainExtensionOptionArray = (ExplainExtensionOption *)
     371            0 :             repalloc(ExplainExtensionOptionArray, i * sizeof(ExplainExtensionOption));
     372            0 :         ExplainExtensionOptionsAllocated = i;
     373              :     }
     374              : 
     375              :     /* Assign and return new ID. */
     376           51 :     exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
     377           51 :     exopt->option_name = option_name;
     378           51 :     exopt->option_handler = handler;
     379           51 :     exopt->guc_check_handler = guc_check_handler;
     380              : }
     381              : 
     382              : /*
     383              :  * Apply an EXPLAIN option registered by an extension.
     384              :  *
     385              :  * If no extension has registered the named option, returns false. Otherwise,
     386              :  * calls the appropriate handler function and then returns true.
     387              :  */
     388              : bool
     389          154 : ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
     390              : {
     391          165 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
     392              :     {
     393          161 :         if (strcmp(ExplainExtensionOptionArray[i].option_name,
     394          161 :                    opt->defname) == 0)
     395              :         {
     396          150 :             ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
     397          150 :             return true;
     398              :         }
     399              :     }
     400              : 
     401            4 :     return false;
     402              : }
     403              : 
     404              : /*
     405              :  * Determine whether an EXPLAIN extension option will be accepted without
     406              :  * error. Returns true if so, and false if not. See the comments for
     407              :  * GUCCheckBooleanExplainOption for more details.
     408              :  *
     409              :  * The caller need not know that the option_name is valid; this function
     410              :  * will indicate that the option is unrecognized if that is the case.
     411              :  */
     412              : bool
     413           23 : GUCCheckExplainExtensionOption(const char *option_name,
     414              :                                const char *option_value,
     415              :                                NodeTag option_type)
     416              : {
     417           32 :     for (int i = 0; i < ExplainExtensionOptionsAssigned; i++)
     418              :     {
     419           31 :         ExplainExtensionOption *exopt = &ExplainExtensionOptionArray[i];
     420              : 
     421           31 :         if (strcmp(exopt->option_name, option_name) == 0)
     422           22 :             return exopt->guc_check_handler(option_name, option_value,
     423              :                                             option_type);
     424              :     }
     425              : 
     426              :     /* Unrecognized option name. */
     427            1 :     GUC_check_errmsg("unrecognized EXPLAIN option \"%s\"", option_name);
     428            1 :     return false;
     429              : }
     430              : 
     431              : /*
     432              :  * guc_check_handler for Boolean-valued EXPLAIN extension options.
     433              :  *
     434              :  * After receiving a "true" value from this or any other GUC check handler
     435              :  * for an EXPLAIN extension option, the caller is entitled to assume that
     436              :  * a suitably constructed DefElem passed to the main option handler will
     437              :  * not cause an error. To construct this DefElem, the caller should set
     438              :  * the DefElem's defname to option_name. If option_value is NULL, arg
     439              :  * should be NULL. Otherwise, arg should be of the type given by
     440              :  * option_type, with option_value as the associated value. The only option
     441              :  * types that should be passed are T_String, T_Float, and T_Integer; in
     442              :  * the last case, the caller will need to perform a string-to-integer
     443              :  * conversion.
     444              :  *
     445              :  * A guc_check_handler should not throw an error, and should not allocate
     446              :  * memory.  If it returns false to indicate that the option_value is not
     447              :  * acceptable, it may use GUC_check_errmsg(), GUC_check_errdetail(), etc.
     448              :  * to clarify the nature of the problem.
     449              :  *
     450              :  * Since we're concerned with Boolean options here, the logic below must
     451              :  * exactly match the semantics of defGetBoolean.
     452              :  */
     453              : bool
     454           22 : GUCCheckBooleanExplainOption(const char *option_name,
     455              :                              const char *option_value,
     456              :                              NodeTag option_type)
     457              : {
     458           22 :     bool        valid = false;
     459              : 
     460           22 :     if (option_value == NULL)
     461              :     {
     462              :         /* defGetBoolean treats no argument as valid */
     463           12 :         valid = true;
     464              :     }
     465           10 :     else if (option_type == T_String)
     466              :     {
     467              :         /* defGetBoolean accepts exactly these string values */
     468           12 :         if (pg_strcasecmp(option_value, "true") == 0 ||
     469            8 :             pg_strcasecmp(option_value, "false") == 0 ||
     470            6 :             pg_strcasecmp(option_value, "on") == 0 ||
     471            3 :             pg_strcasecmp(option_value, "off") == 0)
     472            5 :             valid = true;
     473              :     }
     474            3 :     else if (option_type == T_Integer)
     475              :     {
     476              :         long        value;
     477              :         char       *end;
     478              : 
     479              :         /*
     480              :          * defGetBoolean accepts only 0 and 1, but those can be spelled in
     481              :          * various ways (e.g. 01, 0x01).
     482              :          */
     483            2 :         errno = 0;
     484            2 :         value = strtol(option_value, &end, 0);
     485            2 :         if (errno == 0 && *end == '\0' && end != option_value &&
     486            2 :             value == (int) value && (value == 0 || value == 1))
     487            1 :             valid = true;
     488              :     }
     489              : 
     490           22 :     if (!valid)
     491              :     {
     492            4 :         GUC_check_errmsg("EXPLAIN option \"%s\" requires a Boolean value",
     493              :                          option_name);
     494            4 :         return false;
     495              :     }
     496              : 
     497           18 :     return true;
     498              : }
        

Generated by: LCOV version 2.0-1