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