Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/commands/explain_state.c
Line
Count
Source
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
NewExplainState(void)
65
0
{
66
0
  ExplainState *es = palloc0_object(ExplainState);
67
68
  /* Set default options (most fields can be left as zeroes). */
69
0
  es->costs = true;
70
  /* Prepare output buffer. */
71
0
  es->str = makeStringInfo();
72
73
0
  return es;
74
0
}
75
76
/*
77
 * Parse a list of EXPLAIN options and update an ExplainState accordingly.
78
 */
79
void
80
ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
81
0
{
82
0
  ListCell   *lc;
83
0
  bool    timing_set = false;
84
0
  bool    buffers_set = false;
85
0
  bool    summary_set = false;
86
87
  /* Parse options list. */
88
0
  foreach(lc, options)
89
0
  {
90
0
    DefElem    *opt = (DefElem *) lfirst(lc);
91
92
0
    if (strcmp(opt->defname, "analyze") == 0)
93
0
      es->analyze = defGetBoolean(opt);
94
0
    else if (strcmp(opt->defname, "verbose") == 0)
95
0
      es->verbose = defGetBoolean(opt);
96
0
    else if (strcmp(opt->defname, "costs") == 0)
97
0
      es->costs = defGetBoolean(opt);
98
0
    else if (strcmp(opt->defname, "buffers") == 0)
99
0
    {
100
0
      buffers_set = true;
101
0
      es->buffers = defGetBoolean(opt);
102
0
    }
103
0
    else if (strcmp(opt->defname, "wal") == 0)
104
0
      es->wal = defGetBoolean(opt);
105
0
    else if (strcmp(opt->defname, "settings") == 0)
106
0
      es->settings = defGetBoolean(opt);
107
0
    else if (strcmp(opt->defname, "generic_plan") == 0)
108
0
      es->generic = defGetBoolean(opt);
109
0
    else if (strcmp(opt->defname, "timing") == 0)
110
0
    {
111
0
      timing_set = true;
112
0
      es->timing = defGetBoolean(opt);
113
0
    }
114
0
    else if (strcmp(opt->defname, "summary") == 0)
115
0
    {
116
0
      summary_set = true;
117
0
      es->summary = defGetBoolean(opt);
118
0
    }
119
0
    else if (strcmp(opt->defname, "memory") == 0)
120
0
      es->memory = defGetBoolean(opt);
121
0
    else if (strcmp(opt->defname, "serialize") == 0)
122
0
    {
123
0
      if (opt->arg)
124
0
      {
125
0
        char     *p = defGetString(opt);
126
127
0
        if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
128
0
          es->serialize = EXPLAIN_SERIALIZE_NONE;
129
0
        else if (strcmp(p, "text") == 0)
130
0
          es->serialize = EXPLAIN_SERIALIZE_TEXT;
131
0
        else if (strcmp(p, "binary") == 0)
132
0
          es->serialize = EXPLAIN_SERIALIZE_BINARY;
133
0
        else
134
0
          ereport(ERROR,
135
0
              (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
136
0
               errmsg("unrecognized value for %s option \"%s\": \"%s\"",
137
0
                  "EXPLAIN", opt->defname, p),
138
0
               parser_errposition(pstate, opt->location)));
139
0
      }
140
0
      else
141
0
      {
142
        /* SERIALIZE without an argument is taken as 'text' */
143
0
        es->serialize = EXPLAIN_SERIALIZE_TEXT;
144
0
      }
145
0
    }
146
0
    else if (strcmp(opt->defname, "format") == 0)
147
0
    {
148
0
      char     *p = defGetString(opt);
149
150
0
      if (strcmp(p, "text") == 0)
151
0
        es->format = EXPLAIN_FORMAT_TEXT;
152
0
      else if (strcmp(p, "xml") == 0)
153
0
        es->format = EXPLAIN_FORMAT_XML;
154
0
      else if (strcmp(p, "json") == 0)
155
0
        es->format = EXPLAIN_FORMAT_JSON;
156
0
      else if (strcmp(p, "yaml") == 0)
157
0
        es->format = EXPLAIN_FORMAT_YAML;
158
0
      else
159
0
        ereport(ERROR,
160
0
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
161
0
             errmsg("unrecognized value for %s option \"%s\": \"%s\"",
162
0
                "EXPLAIN", opt->defname, p),
163
0
             parser_errposition(pstate, opt->location)));
164
0
    }
165
0
    else if (strcmp(opt->defname, "io") == 0)
166
0
      es->io = defGetBoolean(opt);
167
0
    else if (!ApplyExtensionExplainOption(es, opt, pstate))
168
0
      ereport(ERROR,
169
0
          (errcode(ERRCODE_SYNTAX_ERROR),
170
0
           errmsg("unrecognized %s option \"%s\"",
171
0
              "EXPLAIN", opt->defname),
172
0
           parser_errposition(pstate, opt->location)));
173
0
  }
174
175
  /* check that WAL is used with EXPLAIN ANALYZE */
176
0
  if (es->wal && !es->analyze)
177
0
    ereport(ERROR,
178
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
179
0
         errmsg("EXPLAIN option %s requires ANALYZE", "WAL")));
180
181
  /* if the timing was not set explicitly, set default value */
182
0
  es->timing = (timing_set) ? es->timing : es->analyze;
183
184
  /* if the buffers was not set explicitly, set default value */
185
0
  es->buffers = (buffers_set) ? es->buffers : es->analyze;
186
187
  /* check that timing is used with EXPLAIN ANALYZE */
188
0
  if (es->timing && !es->analyze)
189
0
    ereport(ERROR,
190
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
191
0
         errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
192
193
  /* check that IO is used with EXPLAIN ANALYZE */
194
0
  if (es->io && !es->analyze)
195
0
    ereport(ERROR,
196
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
197
0
         errmsg("EXPLAIN option %s requires ANALYZE", "IO")));
198
199
  /* check that serialize is used with EXPLAIN ANALYZE */
200
0
  if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
201
0
    ereport(ERROR,
202
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
203
0
         errmsg("EXPLAIN option %s requires ANALYZE", "SERIALIZE")));
204
205
  /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
206
0
  if (es->generic && es->analyze)
207
0
    ereport(ERROR,
208
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
209
0
         errmsg("%s options %s and %s cannot be used together",
210
0
            "EXPLAIN", "ANALYZE", "GENERIC_PLAN")));
211
212
  /* if the summary was not set explicitly, set default value */
213
0
  es->summary = (summary_set) ? es->summary : es->analyze;
214
215
  /* plugin specific option validation */
216
0
  if (explain_validate_options_hook)
217
0
    (*explain_validate_options_hook) (es, options, pstate);
218
0
}
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
GetExplainExtensionId(const char *extension_name)
233
0
{
234
  /* Search for an existing extension by this name; if found, return ID. */
235
0
  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
0
  if (ExplainExtensionNameArray == NULL)
241
0
  {
242
0
    ExplainExtensionNamesAllocated = 16;
243
0
    ExplainExtensionNameArray = (const char **)
244
0
      MemoryContextAlloc(TopMemoryContext,
245
0
                 ExplainExtensionNamesAllocated
246
0
                 * sizeof(char *));
247
0
  }
248
249
  /* If there's an array but it's currently full, expand it. */
250
0
  if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
251
0
  {
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
0
  }
258
259
  /* Assign and return new ID. */
260
0
  ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
261
0
  return ExplainExtensionNamesAssigned++;
262
0
}
263
264
/*
265
 * Get extension-specific state from an ExplainState.
266
 *
267
 * See comments for SetExplainExtensionState, below.
268
 */
269
void *
270
GetExplainExtensionState(ExplainState *es, int extension_id)
271
0
{
272
0
  Assert(extension_id >= 0);
273
274
0
  if (extension_id >= es->extension_state_allocated)
275
0
    return NULL;
276
277
0
  return es->extension_state[extension_id];
278
0
}
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
SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
290
0
{
291
0
  Assert(extension_id >= 0);
292
293
  /* If there is no array yet, create one. */
294
0
  if (es->extension_state == NULL)
295
0
  {
296
0
    es->extension_state_allocated =
297
0
      Max(16, pg_nextpower2_32(extension_id + 1));
298
0
    es->extension_state =
299
0
      palloc0(es->extension_state_allocated * sizeof(void *));
300
0
  }
301
302
  /* If there's an array but it's currently full, expand it. */
303
0
  if (extension_id >= es->extension_state_allocated)
304
0
  {
305
0
    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
0
  }
311
312
0
  es->extension_state[extension_id] = opaque;
313
0
}
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
RegisterExtensionExplainOption(const char *option_name,
333
                 ExplainOptionHandler handler,
334
                 ExplainOptionGUCCheckHandler guc_check_handler)
335
0
{
336
0
  ExplainExtensionOption *exopt;
337
338
0
  Assert(handler != NULL);
339
0
  Assert(guc_check_handler != NULL);
340
341
  /* Search for an existing option by this name; if found, update handler. */
342
0
  for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
343
0
  {
344
0
    if (strcmp(ExplainExtensionOptionArray[i].option_name,
345
0
           option_name) == 0)
346
0
    {
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
0
    }
353
0
  }
354
355
  /* If there is no array yet, create one. */
356
0
  if (ExplainExtensionOptionArray == NULL)
357
0
  {
358
0
    ExplainExtensionOptionsAllocated = 16;
359
0
    ExplainExtensionOptionArray = (ExplainExtensionOption *)
360
0
      MemoryContextAlloc(TopMemoryContext,
361
0
                 ExplainExtensionOptionsAllocated
362
0
                 * sizeof(ExplainExtensionOption));
363
0
  }
364
365
  /* If there's an array but it's currently full, expand it. */
366
0
  if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
367
0
  {
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
0
  }
374
375
  /* Assign and return new ID. */
376
0
  exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
377
0
  exopt->option_name = option_name;
378
0
  exopt->option_handler = handler;
379
0
  exopt->guc_check_handler = guc_check_handler;
380
0
}
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
ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
390
0
{
391
0
  for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
392
0
  {
393
0
    if (strcmp(ExplainExtensionOptionArray[i].option_name,
394
0
           opt->defname) == 0)
395
0
    {
396
0
      ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
397
0
      return true;
398
0
    }
399
0
  }
400
401
0
  return false;
402
0
}
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
GUCCheckExplainExtensionOption(const char *option_name,
414
                 const char *option_value,
415
                 NodeTag option_type)
416
0
{
417
0
  for (int i = 0; i < ExplainExtensionOptionsAssigned; i++)
418
0
  {
419
0
    ExplainExtensionOption *exopt = &ExplainExtensionOptionArray[i];
420
421
0
    if (strcmp(exopt->option_name, option_name) == 0)
422
0
      return exopt->guc_check_handler(option_name, option_value,
423
0
                      option_type);
424
0
  }
425
426
  /* Unrecognized option name. */
427
0
  GUC_check_errmsg("unrecognized EXPLAIN option \"%s\"", option_name);
428
0
  return false;
429
0
}
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
GUCCheckBooleanExplainOption(const char *option_name,
455
               const char *option_value,
456
               NodeTag option_type)
457
0
{
458
0
  bool    valid = false;
459
460
0
  if (option_value == NULL)
461
0
  {
462
    /* defGetBoolean treats no argument as valid */
463
0
    valid = true;
464
0
  }
465
0
  else if (option_type == T_String)
466
0
  {
467
    /* defGetBoolean accepts exactly these string values */
468
0
    if (pg_strcasecmp(option_value, "true") == 0 ||
469
0
      pg_strcasecmp(option_value, "false") == 0 ||
470
0
      pg_strcasecmp(option_value, "on") == 0 ||
471
0
      pg_strcasecmp(option_value, "off") == 0)
472
0
      valid = true;
473
0
  }
474
0
  else if (option_type == T_Integer)
475
0
  {
476
0
    long    value;
477
0
    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
0
    errno = 0;
484
0
    value = strtol(option_value, &end, 0);
485
0
    if (errno == 0 && *end == '\0' && end != option_value &&
486
0
      value == (int) value && (value == 0 || value == 1))
487
0
      valid = true;
488
0
  }
489
490
0
  if (!valid)
491
0
  {
492
0
    GUC_check_errmsg("EXPLAIN option \"%s\" requires a Boolean value",
493
0
             option_name);
494
0
    return false;
495
0
  }
496
497
0
  return true;
498
0
}