Age Owner Branch data TLA 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 *
273 rhaas@postgresql.org 61 :CBC 12343 : NewExplainState(void)
62 : : {
6 michael@paquier.xyz 63 :GNC 12343 : ExplainState *es = palloc0_object(ExplainState);
64 : :
65 : : /* Set default options (most fields can be left as zeroes). */
273 rhaas@postgresql.org 66 :CBC 12343 : es->costs = true;
67 : : /* Prepare output buffer. */
68 : 12343 : es->str = makeStringInfo();
69 : :
70 : 12343 : return es;
71 : : }
72 : :
73 : : /*
74 : : * Parse a list of EXPLAIN options and update an ExplainState accordingly.
75 : : */
76 : : void
77 : 12333 : ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
78 : : {
79 : : ListCell *lc;
80 : 12333 : bool timing_set = false;
81 : 12333 : bool buffers_set = false;
82 : 12333 : bool summary_set = false;
83 : :
84 : : /* Parse options list. */
85 [ + + + + : 24138 : foreach(lc, options)
+ + ]
86 : : {
87 : 11809 : DefElem *opt = (DefElem *) lfirst(lc);
88 : :
89 [ + + ]: 11809 : if (strcmp(opt->defname, "analyze") == 0)
90 : 1752 : es->analyze = defGetBoolean(opt);
91 [ + + ]: 10057 : else if (strcmp(opt->defname, "verbose") == 0)
92 : 1365 : es->verbose = defGetBoolean(opt);
93 [ + + ]: 8692 : else if (strcmp(opt->defname, "costs") == 0)
94 : 7088 : es->costs = defGetBoolean(opt);
95 [ + + ]: 1604 : else if (strcmp(opt->defname, "buffers") == 0)
96 : : {
97 : 492 : buffers_set = true;
98 : 492 : es->buffers = defGetBoolean(opt);
99 : : }
100 [ - + ]: 1112 : else if (strcmp(opt->defname, "wal") == 0)
273 rhaas@postgresql.org 101 :UBC 0 : es->wal = defGetBoolean(opt);
273 rhaas@postgresql.org 102 [ + + ]:CBC 1112 : else if (strcmp(opt->defname, "settings") == 0)
103 : 6 : es->settings = defGetBoolean(opt);
104 [ + + ]: 1106 : else if (strcmp(opt->defname, "generic_plan") == 0)
105 : 9 : es->generic = defGetBoolean(opt);
106 [ + + ]: 1097 : else if (strcmp(opt->defname, "timing") == 0)
107 : : {
108 : 454 : timing_set = true;
109 : 454 : es->timing = defGetBoolean(opt);
110 : : }
111 [ + + ]: 643 : else if (strcmp(opt->defname, "summary") == 0)
112 : : {
113 : 442 : summary_set = true;
114 : 442 : es->summary = defGetBoolean(opt);
115 : : }
116 [ + + ]: 201 : else if (strcmp(opt->defname, "memory") == 0)
117 : 15 : es->memory = defGetBoolean(opt);
118 [ + + ]: 186 : else if (strcmp(opt->defname, "serialize") == 0)
119 : : {
120 [ + + ]: 15 : if (opt->arg)
121 : : {
122 : 6 : char *p = defGetString(opt);
123 : :
124 [ + - - + ]: 6 : if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
273 rhaas@postgresql.org 125 :UBC 0 : es->serialize = EXPLAIN_SERIALIZE_NONE;
273 rhaas@postgresql.org 126 [ + + ]:CBC 6 : else if (strcmp(p, "text") == 0)
127 : 3 : es->serialize = EXPLAIN_SERIALIZE_TEXT;
128 [ + - ]: 3 : else if (strcmp(p, "binary") == 0)
129 : 3 : es->serialize = EXPLAIN_SERIALIZE_BINARY;
130 : : else
273 rhaas@postgresql.org 131 [ # # ]:UBC 0 : ereport(ERROR,
132 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
133 : : errmsg("unrecognized value for %s option \"%s\": \"%s\"",
134 : : "EXPLAIN", opt->defname, p),
135 : : parser_errposition(pstate, opt->location)));
136 : : }
137 : : else
138 : : {
139 : : /* SERIALIZE without an argument is taken as 'text' */
273 rhaas@postgresql.org 140 :CBC 9 : es->serialize = EXPLAIN_SERIALIZE_TEXT;
141 : : }
142 : : }
143 [ + + ]: 171 : else if (strcmp(opt->defname, "format") == 0)
144 : : {
145 : 156 : char *p = defGetString(opt);
146 : :
147 [ + + ]: 156 : if (strcmp(p, "text") == 0)
148 : 6 : es->format = EXPLAIN_FORMAT_TEXT;
149 [ + + ]: 150 : else if (strcmp(p, "xml") == 0)
150 : 4 : es->format = EXPLAIN_FORMAT_XML;
151 [ + + ]: 146 : else if (strcmp(p, "json") == 0)
152 : 140 : es->format = EXPLAIN_FORMAT_JSON;
153 [ + - ]: 6 : else if (strcmp(p, "yaml") == 0)
154 : 6 : es->format = EXPLAIN_FORMAT_YAML;
155 : : else
273 rhaas@postgresql.org 156 [ # # ]:UBC 0 : ereport(ERROR,
157 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
158 : : errmsg("unrecognized value for %s option \"%s\": \"%s\"",
159 : : "EXPLAIN", opt->defname, p),
160 : : parser_errposition(pstate, opt->location)));
161 : : }
273 rhaas@postgresql.org 162 [ + + ]:CBC 15 : else if (!ApplyExtensionExplainOption(es, opt, pstate))
163 [ + - ]: 4 : ereport(ERROR,
164 : : (errcode(ERRCODE_SYNTAX_ERROR),
165 : : errmsg("unrecognized %s option \"%s\"",
166 : : "EXPLAIN", opt->defname),
167 : : parser_errposition(pstate, opt->location)));
168 : : }
169 : :
170 : : /* check that WAL is used with EXPLAIN ANALYZE */
171 [ - + - - ]: 12329 : if (es->wal && !es->analyze)
273 rhaas@postgresql.org 172 [ # # ]:UBC 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 */
273 rhaas@postgresql.org 177 [ + + ]:CBC 12329 : es->timing = (timing_set) ? es->timing : es->analyze;
178 : :
179 : : /* if the buffers was not set explicitly, set default value */
180 [ + + ]: 12329 : es->buffers = (buffers_set) ? es->buffers : es->analyze;
181 : :
182 : : /* check that timing is used with EXPLAIN ANALYZE */
183 [ + + - + ]: 12329 : if (es->timing && !es->analyze)
273 rhaas@postgresql.org 184 [ # # ]:UBC 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 */
273 rhaas@postgresql.org 189 [ + + - + ]:CBC 12329 : if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
273 rhaas@postgresql.org 190 [ # # ]:UBC 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 */
273 rhaas@postgresql.org 195 [ + + + + ]:CBC 12329 : if (es->generic && es->analyze)
196 [ + - ]: 3 : ereport(ERROR,
197 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
198 : : errmsg("%s options %s and %s cannot be used together",
199 : : "EXPLAIN", "ANALYZE", "GENERIC_PLAN")));
200 : :
201 : : /* if the summary was not set explicitly, set default value */
202 [ + + ]: 12326 : es->summary = (summary_set) ? es->summary : es->analyze;
203 : :
204 : : /* plugin specific option validation */
271 205 [ - + ]: 12326 : if (explain_validate_options_hook)
271 rhaas@postgresql.org 206 :UBC 0 : (*explain_validate_options_hook) (es, options, pstate);
273 rhaas@postgresql.org 207 :CBC 12326 : }
208 : :
209 : : /*
210 : : * Map the name of an EXPLAIN extension to an integer ID.
211 : : *
212 : : * Within the lifetime of a particular backend, the same name will be mapped
213 : : * to the same ID every time. IDs are not stable across backends. Use the ID
214 : : * that you get from this function to call GetExplainExtensionState and
215 : : * SetExplainExtensionState.
216 : : *
217 : : * extension_name is assumed to be a constant string or allocated in storage
218 : : * that will never be freed.
219 : : */
220 : : int
221 : 1 : GetExplainExtensionId(const char *extension_name)
222 : : {
223 : : /* Search for an existing extension by this name; if found, return ID. */
224 [ - + ]: 1 : for (int i = 0; i < ExplainExtensionNamesAssigned; ++i)
273 rhaas@postgresql.org 225 [ # # ]:UBC 0 : if (strcmp(ExplainExtensionNameArray[i], extension_name) == 0)
226 : 0 : return i;
227 : :
228 : : /* If there is no array yet, create one. */
273 rhaas@postgresql.org 229 [ + - ]:CBC 1 : if (ExplainExtensionNameArray == NULL)
230 : : {
231 : 1 : ExplainExtensionNamesAllocated = 16;
232 : 1 : ExplainExtensionNameArray = (const char **)
233 : 1 : MemoryContextAlloc(TopMemoryContext,
234 : : ExplainExtensionNamesAllocated
235 : : * sizeof(char *));
236 : : }
237 : :
238 : : /* If there's an array but it's currently full, expand it. */
239 [ - + ]: 1 : if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
240 : : {
273 rhaas@postgresql.org 241 :UBC 0 : int i = pg_nextpower2_32(ExplainExtensionNamesAssigned + 1);
242 : :
243 : 0 : ExplainExtensionNameArray = (const char **)
244 : 0 : repalloc(ExplainExtensionNameArray, i * sizeof(char *));
245 : 0 : ExplainExtensionNamesAllocated = i;
246 : : }
247 : :
248 : : /* Assign and return new ID. */
273 rhaas@postgresql.org 249 :CBC 1 : ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
250 : 1 : return ExplainExtensionNamesAssigned++;
251 : : }
252 : :
253 : : /*
254 : : * Get extension-specific state from an ExplainState.
255 : : *
256 : : * See comments for SetExplainExtensionState, below.
257 : : */
258 : : void *
259 : 50 : GetExplainExtensionState(ExplainState *es, int extension_id)
260 : : {
261 [ - + ]: 50 : Assert(extension_id >= 0);
262 : :
263 [ + + ]: 50 : if (extension_id >= es->extension_state_allocated)
264 : 9 : return NULL;
265 : :
266 : 41 : return es->extension_state[extension_id];
267 : : }
268 : :
269 : : /*
270 : : * Store extension-specific state into an ExplainState.
271 : : *
272 : : * To use this function, first obtain an integer extension_id using
273 : : * GetExplainExtensionId. Then use this function to store an opaque pointer
274 : : * in the ExplainState. Later, you can retrieve the opaque pointer using
275 : : * GetExplainExtensionState.
276 : : */
277 : : void
278 : 9 : SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
279 : : {
280 [ - + ]: 9 : Assert(extension_id >= 0);
281 : :
282 : : /* If there is no array yet, create one. */
283 [ + - ]: 9 : if (es->extension_state == NULL)
284 : : {
82 285 : 9 : es->extension_state_allocated =
286 [ - + ]: 9 : Max(16, pg_nextpower2_32(extension_id + 1));
273 287 : 9 : es->extension_state =
288 : 9 : palloc0(es->extension_state_allocated * sizeof(void *));
289 : : }
290 : :
291 : : /* If there's an array but it's currently full, expand it. */
292 [ - + ]: 9 : if (extension_id >= es->extension_state_allocated)
293 : : {
294 : : int i;
295 : :
82 rhaas@postgresql.org 296 :UBC 0 : i = pg_nextpower2_32(extension_id + 1);
6 michael@paquier.xyz 297 :UNC 0 : es->extension_state = repalloc0_array(es->extension_state, void *, es->extension_state_allocated, i);
273 rhaas@postgresql.org 298 :UBC 0 : es->extension_state_allocated = i;
299 : : }
300 : :
273 rhaas@postgresql.org 301 :CBC 9 : es->extension_state[extension_id] = opaque;
302 : 9 : }
303 : :
304 : : /*
305 : : * Register a new EXPLAIN option.
306 : : *
307 : : * When option_name is used as an EXPLAIN option, handler will be called and
308 : : * should update the ExplainState passed to it. See comments at top of file
309 : : * for a more detailed explanation.
310 : : *
311 : : * option_name is assumed to be a constant string or allocated in storage
312 : : * that will never be freed.
313 : : */
314 : : void
315 : 2 : RegisterExtensionExplainOption(const char *option_name,
316 : : ExplainOptionHandler handler)
317 : : {
318 : : ExplainExtensionOption *exopt;
319 : :
320 : : /* Search for an existing option by this name; if found, update handler. */
321 [ + + ]: 3 : for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
322 : : {
323 [ - + ]: 1 : if (strcmp(ExplainExtensionOptionArray[i].option_name,
324 : : option_name) == 0)
325 : : {
273 rhaas@postgresql.org 326 :UBC 0 : ExplainExtensionOptionArray[i].option_handler = handler;
327 : 0 : return;
328 : : }
329 : : }
330 : :
331 : : /* If there is no array yet, create one. */
273 rhaas@postgresql.org 332 [ + + ]:CBC 2 : if (ExplainExtensionOptionArray == NULL)
333 : : {
334 : 1 : ExplainExtensionOptionsAllocated = 16;
335 : 1 : ExplainExtensionOptionArray = (ExplainExtensionOption *)
336 : 1 : MemoryContextAlloc(TopMemoryContext,
337 : : ExplainExtensionOptionsAllocated
338 : : * sizeof(char *));
339 : : }
340 : :
341 : : /* If there's an array but it's currently full, expand it. */
342 [ - + ]: 2 : if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
343 : : {
273 rhaas@postgresql.org 344 :UBC 0 : int i = pg_nextpower2_32(ExplainExtensionOptionsAssigned + 1);
345 : :
346 : 0 : ExplainExtensionOptionArray = (ExplainExtensionOption *)
347 : 0 : repalloc(ExplainExtensionOptionArray, i * sizeof(char *));
348 : 0 : ExplainExtensionOptionsAllocated = i;
349 : : }
350 : :
351 : : /* Assign and return new ID. */
273 rhaas@postgresql.org 352 :CBC 2 : exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
353 : 2 : exopt->option_name = option_name;
354 : 2 : exopt->option_handler = handler;
355 : : }
356 : :
357 : : /*
358 : : * Apply an EXPLAIN option registered by an extension.
359 : : *
360 : : * If no extension has registered the named option, returns false. Otherwise,
361 : : * calls the appropriate handler function and then returns true.
362 : : */
363 : : bool
364 : 15 : ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
365 : : {
366 [ + + ]: 22 : for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
367 : : {
368 : 18 : if (strcmp(ExplainExtensionOptionArray[i].option_name,
369 [ + + ]: 18 : opt->defname) == 0)
370 : : {
371 : 11 : ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
372 : 11 : return true;
373 : : }
374 : : }
375 : :
376 : 4 : return false;
377 : : }
|