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