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