Coverage Report

Created: 2025-06-15 06:31

/src/postgres/src/backend/commands/explain_state.c
Line
Count
Source (jump to first uncovered line)
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
NewExplainState(void)
62
0
{
63
0
  ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
64
65
  /* Set default options (most fields can be left as zeroes). */
66
0
  es->costs = true;
67
  /* Prepare output buffer. */
68
0
  es->str = makeStringInfo();
69
70
0
  return es;
71
0
}
72
73
/*
74
 * Parse a list of EXPLAIN options and update an ExplainState accordingly.
75
 */
76
void
77
ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
78
0
{
79
0
  ListCell   *lc;
80
0
  bool    timing_set = false;
81
0
  bool    buffers_set = false;
82
0
  bool    summary_set = false;
83
84
  /* Parse options list. */
85
0
  foreach(lc, options)
86
0
  {
87
0
    DefElem    *opt = (DefElem *) lfirst(lc);
88
89
0
    if (strcmp(opt->defname, "analyze") == 0)
90
0
      es->analyze = defGetBoolean(opt);
91
0
    else if (strcmp(opt->defname, "verbose") == 0)
92
0
      es->verbose = defGetBoolean(opt);
93
0
    else if (strcmp(opt->defname, "costs") == 0)
94
0
      es->costs = defGetBoolean(opt);
95
0
    else if (strcmp(opt->defname, "buffers") == 0)
96
0
    {
97
0
      buffers_set = true;
98
0
      es->buffers = defGetBoolean(opt);
99
0
    }
100
0
    else if (strcmp(opt->defname, "wal") == 0)
101
0
      es->wal = defGetBoolean(opt);
102
0
    else if (strcmp(opt->defname, "settings") == 0)
103
0
      es->settings = defGetBoolean(opt);
104
0
    else if (strcmp(opt->defname, "generic_plan") == 0)
105
0
      es->generic = defGetBoolean(opt);
106
0
    else if (strcmp(opt->defname, "timing") == 0)
107
0
    {
108
0
      timing_set = true;
109
0
      es->timing = defGetBoolean(opt);
110
0
    }
111
0
    else if (strcmp(opt->defname, "summary") == 0)
112
0
    {
113
0
      summary_set = true;
114
0
      es->summary = defGetBoolean(opt);
115
0
    }
116
0
    else if (strcmp(opt->defname, "memory") == 0)
117
0
      es->memory = defGetBoolean(opt);
118
0
    else if (strcmp(opt->defname, "serialize") == 0)
119
0
    {
120
0
      if (opt->arg)
121
0
      {
122
0
        char     *p = defGetString(opt);
123
124
0
        if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
125
0
          es->serialize = EXPLAIN_SERIALIZE_NONE;
126
0
        else if (strcmp(p, "text") == 0)
127
0
          es->serialize = EXPLAIN_SERIALIZE_TEXT;
128
0
        else if (strcmp(p, "binary") == 0)
129
0
          es->serialize = EXPLAIN_SERIALIZE_BINARY;
130
0
        else
131
0
          ereport(ERROR,
132
0
              (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
133
0
               errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
134
0
                  opt->defname, p),
135
0
               parser_errposition(pstate, opt->location)));
136
0
      }
137
0
      else
138
0
      {
139
        /* SERIALIZE without an argument is taken as 'text' */
140
0
        es->serialize = EXPLAIN_SERIALIZE_TEXT;
141
0
      }
142
0
    }
143
0
    else if (strcmp(opt->defname, "format") == 0)
144
0
    {
145
0
      char     *p = defGetString(opt);
146
147
0
      if (strcmp(p, "text") == 0)
148
0
        es->format = EXPLAIN_FORMAT_TEXT;
149
0
      else if (strcmp(p, "xml") == 0)
150
0
        es->format = EXPLAIN_FORMAT_XML;
151
0
      else if (strcmp(p, "json") == 0)
152
0
        es->format = EXPLAIN_FORMAT_JSON;
153
0
      else if (strcmp(p, "yaml") == 0)
154
0
        es->format = EXPLAIN_FORMAT_YAML;
155
0
      else
156
0
        ereport(ERROR,
157
0
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
158
0
             errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
159
0
                opt->defname, p),
160
0
             parser_errposition(pstate, opt->location)));
161
0
    }
162
0
    else if (!ApplyExtensionExplainOption(es, opt, pstate))
163
0
      ereport(ERROR,
164
0
          (errcode(ERRCODE_SYNTAX_ERROR),
165
0
           errmsg("unrecognized EXPLAIN option \"%s\"",
166
0
              opt->defname),
167
0
           parser_errposition(pstate, opt->location)));
168
0
  }
169
170
  /* check that WAL is used with EXPLAIN ANALYZE */
171
0
  if (es->wal && !es->analyze)
172
0
    ereport(ERROR,
173
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
174
0
         errmsg("EXPLAIN option %s requires ANALYZE", "WAL")));
175
176
  /* if the timing was not set explicitly, set default value */
177
0
  es->timing = (timing_set) ? es->timing : es->analyze;
178
179
  /* if the buffers was not set explicitly, set default value */
180
0
  es->buffers = (buffers_set) ? es->buffers : es->analyze;
181
182
  /* check that timing is used with EXPLAIN ANALYZE */
183
0
  if (es->timing && !es->analyze)
184
0
    ereport(ERROR,
185
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
186
0
         errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
187
188
  /* check that serialize is used with EXPLAIN ANALYZE */
189
0
  if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
190
0
    ereport(ERROR,
191
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
192
0
         errmsg("EXPLAIN option %s requires ANALYZE", "SERIALIZE")));
193
194
  /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
195
0
  if (es->generic && es->analyze)
196
0
    ereport(ERROR,
197
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
198
0
         errmsg("EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together")));
199
200
  /* if the summary was not set explicitly, set default value */
201
0
  es->summary = (summary_set) ? es->summary : es->analyze;
202
203
  /* plugin specific option validation */
204
0
  if (explain_validate_options_hook)
205
0
    (*explain_validate_options_hook) (es, options, pstate);
206
0
}
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
GetExplainExtensionId(const char *extension_name)
221
0
{
222
  /* Search for an existing extension by this name; if found, return ID. */
223
0
  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
0
  if (ExplainExtensionNameArray == NULL)
229
0
  {
230
0
    ExplainExtensionNamesAllocated = 16;
231
0
    ExplainExtensionNameArray = (const char **)
232
0
      MemoryContextAlloc(TopMemoryContext,
233
0
                 ExplainExtensionNamesAllocated
234
0
                 * sizeof(char *));
235
0
  }
236
237
  /* If there's an array but it's currently full, expand it. */
238
0
  if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
239
0
  {
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
0
  }
246
247
  /* Assign and return new ID. */
248
0
  ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
249
0
  return ExplainExtensionNamesAssigned++;
250
0
}
251
252
/*
253
 * Get extension-specific state from an ExplainState.
254
 *
255
 * See comments for SetExplainExtensionState, below.
256
 */
257
void *
258
GetExplainExtensionState(ExplainState *es, int extension_id)
259
0
{
260
0
  Assert(extension_id >= 0);
261
262
0
  if (extension_id >= es->extension_state_allocated)
263
0
    return NULL;
264
265
0
  return es->extension_state[extension_id];
266
0
}
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
SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
278
0
{
279
0
  Assert(extension_id >= 0);
280
281
  /* If there is no array yet, create one. */
282
0
  if (es->extension_state == NULL)
283
0
  {
284
0
    es->extension_state_allocated = 16;
285
0
    es->extension_state =
286
0
      palloc0(es->extension_state_allocated * sizeof(void *));
287
0
  }
288
289
  /* If there's an array but it's currently full, expand it. */
290
0
  if (extension_id >= es->extension_state_allocated)
291
0
  {
292
0
    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
0
            i * sizeof(void *));
299
0
    es->extension_state_allocated = i;
300
0
  }
301
302
0
  es->extension_state[extension_id] = opaque;
303
0
}
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
RegisterExtensionExplainOption(const char *option_name,
317
                 ExplainOptionHandler handler)
318
0
{
319
0
  ExplainExtensionOption *exopt;
320
321
  /* Search for an existing option by this name; if found, update handler. */
322
0
  for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
323
0
  {
324
0
    if (strcmp(ExplainExtensionOptionArray[i].option_name,
325
0
           option_name) == 0)
326
0
    {
327
0
      ExplainExtensionOptionArray[i].option_handler = handler;
328
0
      return;
329
0
    }
330
0
  }
331
332
  /* If there is no array yet, create one. */
333
0
  if (ExplainExtensionOptionArray == NULL)
334
0
  {
335
0
    ExplainExtensionOptionsAllocated = 16;
336
0
    ExplainExtensionOptionArray = (ExplainExtensionOption *)
337
0
      MemoryContextAlloc(TopMemoryContext,
338
0
                 ExplainExtensionOptionsAllocated
339
0
                 * sizeof(char *));
340
0
  }
341
342
  /* If there's an array but it's currently full, expand it. */
343
0
  if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
344
0
  {
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
0
  }
351
352
  /* Assign and return new ID. */
353
0
  exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
354
0
  exopt->option_name = option_name;
355
0
  exopt->option_handler = handler;
356
0
}
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
ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
366
0
{
367
0
  for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
368
0
  {
369
0
    if (strcmp(ExplainExtensionOptionArray[i].option_name,
370
0
           opt->defname) == 0)
371
0
    {
372
0
      ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
373
0
      return true;
374
0
    }
375
0
  }
376
377
0
  return false;
378
0
}