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 : }
|