Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * auto_explain.c
4 : *
5 : *
6 : * Copyright (c) 2008-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/auto_explain/auto_explain.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include <limits.h>
16 :
17 : #include "access/parallel.h"
18 : #include "commands/defrem.h"
19 : #include "commands/explain.h"
20 : #include "commands/explain_format.h"
21 : #include "commands/explain_state.h"
22 : #include "common/pg_prng.h"
23 : #include "executor/instrument.h"
24 : #include "nodes/makefuncs.h"
25 : #include "nodes/value.h"
26 : #include "parser/scansup.h"
27 : #include "utils/guc.h"
28 : #include "utils/varlena.h"
29 :
30 15 : PG_MODULE_MAGIC_EXT(
31 : .name = "auto_explain",
32 : .version = PG_VERSION
33 : );
34 :
35 : /* GUC variables */
36 : static int auto_explain_log_min_duration = -1; /* msec or -1 */
37 : static int auto_explain_log_parameter_max_length = -1; /* bytes or -1 */
38 : static bool auto_explain_log_analyze = false;
39 : static bool auto_explain_log_verbose = false;
40 : static bool auto_explain_log_buffers = false;
41 : static bool auto_explain_log_wal = false;
42 : static bool auto_explain_log_triggers = false;
43 : static bool auto_explain_log_timing = true;
44 : static bool auto_explain_log_settings = false;
45 : static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
46 : static int auto_explain_log_level = LOG;
47 : static bool auto_explain_log_nested_statements = false;
48 : static double auto_explain_sample_rate = 1;
49 : static char *auto_explain_log_extension_options = NULL;
50 :
51 : /*
52 : * Parsed form of one option from auto_explain.log_extension_options.
53 : */
54 : typedef struct auto_explain_option
55 : {
56 : char *name;
57 : char *value;
58 : NodeTag type;
59 : } auto_explain_option;
60 :
61 : /*
62 : * Parsed form of the entirety of auto_explain.log_extension_options, stored
63 : * as GUC extra. The options[] array will have pointers into the string
64 : * following the array.
65 : */
66 : typedef struct auto_explain_extension_options
67 : {
68 : int noptions;
69 : auto_explain_option options[FLEXIBLE_ARRAY_MEMBER];
70 : /* a null-terminated copy of the GUC string follows the array */
71 : } auto_explain_extension_options;
72 :
73 : static auto_explain_extension_options *extension_options = NULL;
74 :
75 : static const struct config_enum_entry format_options[] = {
76 : {"text", EXPLAIN_FORMAT_TEXT, false},
77 : {"xml", EXPLAIN_FORMAT_XML, false},
78 : {"json", EXPLAIN_FORMAT_JSON, false},
79 : {"yaml", EXPLAIN_FORMAT_YAML, false},
80 : {NULL, 0, false}
81 : };
82 :
83 : static const struct config_enum_entry loglevel_options[] = {
84 : {"debug5", DEBUG5, false},
85 : {"debug4", DEBUG4, false},
86 : {"debug3", DEBUG3, false},
87 : {"debug2", DEBUG2, false},
88 : {"debug1", DEBUG1, false},
89 : {"debug", DEBUG2, true},
90 : {"info", INFO, false},
91 : {"notice", NOTICE, false},
92 : {"warning", WARNING, false},
93 : {"log", LOG, false},
94 : {NULL, 0, false}
95 : };
96 :
97 : /* Current nesting depth of ExecutorRun calls */
98 : static int nesting_level = 0;
99 :
100 : /* Is the current top-level query to be sampled? */
101 : static bool current_query_sampled = false;
102 :
103 : #define auto_explain_enabled() \
104 : (auto_explain_log_min_duration >= 0 && \
105 : (nesting_level == 0 || auto_explain_log_nested_statements) && \
106 : current_query_sampled)
107 :
108 : /* Saved hook values */
109 : static ExecutorStart_hook_type prev_ExecutorStart = NULL;
110 : static ExecutorRun_hook_type prev_ExecutorRun = NULL;
111 : static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
112 : static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
113 :
114 : static void explain_ExecutorStart(QueryDesc *queryDesc, int eflags);
115 : static void explain_ExecutorRun(QueryDesc *queryDesc,
116 : ScanDirection direction,
117 : uint64 count);
118 : static void explain_ExecutorFinish(QueryDesc *queryDesc);
119 : static void explain_ExecutorEnd(QueryDesc *queryDesc);
120 :
121 : static bool check_log_extension_options(char **newval, void **extra,
122 : GucSource source);
123 : static void assign_log_extension_options(const char *newval, void *extra);
124 : static void apply_extension_options(ExplainState *es,
125 : auto_explain_extension_options *ext);
126 : static char *auto_explain_scan_literal(char **endp, char **nextp);
127 : static int auto_explain_split_options(char *rawstring,
128 : auto_explain_option *options,
129 : int maxoptions, char **errmsg);
130 :
131 : /*
132 : * Module load callback
133 : */
134 : void
135 15 : _PG_init(void)
136 : {
137 : /* Define custom GUC variables. */
138 15 : DefineCustomIntVariable("auto_explain.log_min_duration",
139 : "Sets the minimum execution time above which plans will be logged.",
140 : "-1 disables logging plans. 0 means log all plans.",
141 : &auto_explain_log_min_duration,
142 : -1,
143 : -1, INT_MAX,
144 : PGC_SUSET,
145 : GUC_UNIT_MS,
146 : NULL,
147 : NULL,
148 : NULL);
149 :
150 15 : DefineCustomIntVariable("auto_explain.log_parameter_max_length",
151 : "Sets the maximum length of query parameter values to log.",
152 : "-1 means log values in full.",
153 : &auto_explain_log_parameter_max_length,
154 : -1,
155 : -1, INT_MAX,
156 : PGC_SUSET,
157 : GUC_UNIT_BYTE,
158 : NULL,
159 : NULL,
160 : NULL);
161 :
162 15 : DefineCustomBoolVariable("auto_explain.log_analyze",
163 : "Use EXPLAIN ANALYZE for plan logging.",
164 : NULL,
165 : &auto_explain_log_analyze,
166 : false,
167 : PGC_SUSET,
168 : 0,
169 : NULL,
170 : NULL,
171 : NULL);
172 :
173 15 : DefineCustomBoolVariable("auto_explain.log_settings",
174 : "Log modified configuration parameters affecting query planning.",
175 : NULL,
176 : &auto_explain_log_settings,
177 : false,
178 : PGC_SUSET,
179 : 0,
180 : NULL,
181 : NULL,
182 : NULL);
183 :
184 15 : DefineCustomBoolVariable("auto_explain.log_verbose",
185 : "Use EXPLAIN VERBOSE for plan logging.",
186 : NULL,
187 : &auto_explain_log_verbose,
188 : false,
189 : PGC_SUSET,
190 : 0,
191 : NULL,
192 : NULL,
193 : NULL);
194 :
195 15 : DefineCustomBoolVariable("auto_explain.log_buffers",
196 : "Log buffers usage.",
197 : NULL,
198 : &auto_explain_log_buffers,
199 : false,
200 : PGC_SUSET,
201 : 0,
202 : NULL,
203 : NULL,
204 : NULL);
205 :
206 15 : DefineCustomBoolVariable("auto_explain.log_wal",
207 : "Log WAL usage.",
208 : NULL,
209 : &auto_explain_log_wal,
210 : false,
211 : PGC_SUSET,
212 : 0,
213 : NULL,
214 : NULL,
215 : NULL);
216 :
217 15 : DefineCustomBoolVariable("auto_explain.log_triggers",
218 : "Include trigger statistics in plans.",
219 : "This has no effect unless log_analyze is also set.",
220 : &auto_explain_log_triggers,
221 : false,
222 : PGC_SUSET,
223 : 0,
224 : NULL,
225 : NULL,
226 : NULL);
227 :
228 15 : DefineCustomEnumVariable("auto_explain.log_format",
229 : "EXPLAIN format to be used for plan logging.",
230 : NULL,
231 : &auto_explain_log_format,
232 : EXPLAIN_FORMAT_TEXT,
233 : format_options,
234 : PGC_SUSET,
235 : 0,
236 : NULL,
237 : NULL,
238 : NULL);
239 :
240 15 : DefineCustomEnumVariable("auto_explain.log_level",
241 : "Log level for the plan.",
242 : NULL,
243 : &auto_explain_log_level,
244 : LOG,
245 : loglevel_options,
246 : PGC_SUSET,
247 : 0,
248 : NULL,
249 : NULL,
250 : NULL);
251 :
252 15 : DefineCustomBoolVariable("auto_explain.log_nested_statements",
253 : "Log nested statements.",
254 : NULL,
255 : &auto_explain_log_nested_statements,
256 : false,
257 : PGC_SUSET,
258 : 0,
259 : NULL,
260 : NULL,
261 : NULL);
262 :
263 15 : DefineCustomBoolVariable("auto_explain.log_timing",
264 : "Collect timing data, not just row counts.",
265 : NULL,
266 : &auto_explain_log_timing,
267 : true,
268 : PGC_SUSET,
269 : 0,
270 : NULL,
271 : NULL,
272 : NULL);
273 :
274 15 : DefineCustomStringVariable("auto_explain.log_extension_options",
275 : "Extension EXPLAIN options to be added.",
276 : NULL,
277 : &auto_explain_log_extension_options,
278 : NULL,
279 : PGC_SUSET,
280 : 0,
281 : check_log_extension_options,
282 : assign_log_extension_options,
283 : NULL);
284 :
285 15 : DefineCustomRealVariable("auto_explain.sample_rate",
286 : "Fraction of queries to process.",
287 : NULL,
288 : &auto_explain_sample_rate,
289 : 1.0,
290 : 0.0,
291 : 1.0,
292 : PGC_SUSET,
293 : 0,
294 : NULL,
295 : NULL,
296 : NULL);
297 :
298 15 : MarkGUCPrefixReserved("auto_explain");
299 :
300 : /* Install hooks. */
301 15 : prev_ExecutorStart = ExecutorStart_hook;
302 15 : ExecutorStart_hook = explain_ExecutorStart;
303 15 : prev_ExecutorRun = ExecutorRun_hook;
304 15 : ExecutorRun_hook = explain_ExecutorRun;
305 15 : prev_ExecutorFinish = ExecutorFinish_hook;
306 15 : ExecutorFinish_hook = explain_ExecutorFinish;
307 15 : prev_ExecutorEnd = ExecutorEnd_hook;
308 15 : ExecutorEnd_hook = explain_ExecutorEnd;
309 15 : }
310 :
311 : /*
312 : * ExecutorStart hook: start up logging if needed
313 : */
314 : static void
315 11 : explain_ExecutorStart(QueryDesc *queryDesc, int eflags)
316 : {
317 : /*
318 : * At the beginning of each top-level statement, decide whether we'll
319 : * sample this statement. If nested-statement explaining is enabled,
320 : * either all nested statements will be explained or none will.
321 : *
322 : * When in a parallel worker, we should do nothing, which we can implement
323 : * cheaply by pretending we decided not to sample the current statement.
324 : * If EXPLAIN is active in the parent session, data will be collected and
325 : * reported back to the parent, and it's no business of ours to interfere.
326 : */
327 11 : if (nesting_level == 0)
328 : {
329 11 : if (auto_explain_log_min_duration >= 0 && !IsParallelWorker())
330 11 : current_query_sampled = (pg_prng_double(&pg_global_prng_state) < auto_explain_sample_rate);
331 : else
332 0 : current_query_sampled = false;
333 : }
334 :
335 11 : if (auto_explain_enabled())
336 : {
337 : /* Enable per-node instrumentation iff log_analyze is required. */
338 11 : if (auto_explain_log_analyze && (eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0)
339 : {
340 11 : if (auto_explain_log_timing)
341 11 : queryDesc->instrument_options |= INSTRUMENT_TIMER;
342 : else
343 0 : queryDesc->instrument_options |= INSTRUMENT_ROWS;
344 11 : if (auto_explain_log_buffers)
345 0 : queryDesc->instrument_options |= INSTRUMENT_BUFFERS;
346 11 : if (auto_explain_log_wal)
347 0 : queryDesc->instrument_options |= INSTRUMENT_WAL;
348 : }
349 : }
350 :
351 11 : if (prev_ExecutorStart)
352 0 : prev_ExecutorStart(queryDesc, eflags);
353 : else
354 11 : standard_ExecutorStart(queryDesc, eflags);
355 :
356 11 : if (auto_explain_enabled())
357 : {
358 : /*
359 : * Set up to track total elapsed time in ExecutorRun. Make sure the
360 : * space is allocated in the per-query context so it will go away at
361 : * ExecutorEnd.
362 : */
363 11 : if (queryDesc->totaltime == NULL)
364 : {
365 : MemoryContext oldcxt;
366 :
367 11 : oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);
368 11 : queryDesc->totaltime = InstrAlloc(INSTRUMENT_ALL);
369 11 : MemoryContextSwitchTo(oldcxt);
370 : }
371 : }
372 11 : }
373 :
374 : /*
375 : * ExecutorRun hook: all we need do is track nesting depth
376 : */
377 : static void
378 11 : explain_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction,
379 : uint64 count)
380 : {
381 11 : nesting_level++;
382 11 : PG_TRY();
383 : {
384 11 : if (prev_ExecutorRun)
385 0 : prev_ExecutorRun(queryDesc, direction, count);
386 : else
387 11 : standard_ExecutorRun(queryDesc, direction, count);
388 : }
389 0 : PG_FINALLY();
390 : {
391 11 : nesting_level--;
392 : }
393 11 : PG_END_TRY();
394 11 : }
395 :
396 : /*
397 : * ExecutorFinish hook: all we need do is track nesting depth
398 : */
399 : static void
400 11 : explain_ExecutorFinish(QueryDesc *queryDesc)
401 : {
402 11 : nesting_level++;
403 11 : PG_TRY();
404 : {
405 11 : if (prev_ExecutorFinish)
406 0 : prev_ExecutorFinish(queryDesc);
407 : else
408 11 : standard_ExecutorFinish(queryDesc);
409 : }
410 0 : PG_FINALLY();
411 : {
412 11 : nesting_level--;
413 : }
414 11 : PG_END_TRY();
415 11 : }
416 :
417 : /*
418 : * ExecutorEnd hook: log results if needed
419 : */
420 : static void
421 11 : explain_ExecutorEnd(QueryDesc *queryDesc)
422 : {
423 11 : if (queryDesc->totaltime && auto_explain_enabled())
424 : {
425 : MemoryContext oldcxt;
426 : double msec;
427 :
428 : /*
429 : * Make sure we operate in the per-query context, so any cruft will be
430 : * discarded later during ExecutorEnd.
431 : */
432 11 : oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);
433 :
434 : /* Log plan if duration is exceeded. */
435 11 : msec = INSTR_TIME_GET_MILLISEC(queryDesc->totaltime->total);
436 11 : if (msec >= auto_explain_log_min_duration)
437 : {
438 11 : ExplainState *es = NewExplainState();
439 :
440 11 : es->analyze = (queryDesc->instrument_options && auto_explain_log_analyze);
441 11 : es->verbose = auto_explain_log_verbose;
442 11 : es->buffers = (es->analyze && auto_explain_log_buffers);
443 11 : es->wal = (es->analyze && auto_explain_log_wal);
444 11 : es->timing = (es->analyze && auto_explain_log_timing);
445 11 : es->summary = es->analyze;
446 : /* No support for MEMORY */
447 : /* es->memory = false; */
448 11 : es->format = auto_explain_log_format;
449 11 : es->settings = auto_explain_log_settings;
450 :
451 11 : apply_extension_options(es, extension_options);
452 :
453 11 : ExplainBeginOutput(es);
454 11 : ExplainQueryText(es, queryDesc);
455 11 : ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length);
456 11 : ExplainPrintPlan(es, queryDesc);
457 11 : if (es->analyze && auto_explain_log_triggers)
458 0 : ExplainPrintTriggers(es, queryDesc);
459 11 : if (es->costs)
460 11 : ExplainPrintJITSummary(es, queryDesc);
461 11 : if (explain_per_plan_hook)
462 11 : (*explain_per_plan_hook) (queryDesc->plannedstmt,
463 : NULL, es,
464 : queryDesc->sourceText,
465 : queryDesc->params,
466 11 : queryDesc->estate->es_queryEnv);
467 11 : ExplainEndOutput(es);
468 :
469 : /* Remove last line break */
470 11 : if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n')
471 8 : es->str->data[--es->str->len] = '\0';
472 :
473 : /* Fix JSON to output an object */
474 11 : if (auto_explain_log_format == EXPLAIN_FORMAT_JSON)
475 : {
476 3 : es->str->data[0] = '{';
477 3 : es->str->data[es->str->len - 1] = '}';
478 : }
479 :
480 : /*
481 : * Note: we rely on the existing logging of context or
482 : * debug_query_string to identify just which statement is being
483 : * reported. This isn't ideal but trying to do it here would
484 : * often result in duplication.
485 : */
486 11 : ereport(auto_explain_log_level,
487 : (errmsg("duration: %.3f ms plan:\n%s",
488 : msec, es->str->data),
489 : errhidestmt(true)));
490 : }
491 :
492 11 : MemoryContextSwitchTo(oldcxt);
493 : }
494 :
495 11 : if (prev_ExecutorEnd)
496 0 : prev_ExecutorEnd(queryDesc);
497 : else
498 11 : standard_ExecutorEnd(queryDesc);
499 11 : }
500 :
501 : /*
502 : * GUC check hook for auto_explain.log_extension_options.
503 : */
504 : static bool
505 35 : check_log_extension_options(char **newval, void **extra, GucSource source)
506 : {
507 : char *rawstring;
508 : auto_explain_extension_options *result;
509 : auto_explain_option *options;
510 35 : int maxoptions = 8;
511 : Size rawstring_len;
512 : Size allocsize;
513 : char *errmsg;
514 :
515 : /* NULL or empty string means no options. */
516 35 : if (*newval == NULL || (*newval)[0] == '\0')
517 : {
518 16 : *extra = NULL;
519 16 : return true;
520 : }
521 :
522 19 : rawstring_len = strlen(*newval) + 1;
523 :
524 20 : retry:
525 : /* Try to allocate an auto_explain_extension_options object. */
526 20 : allocsize = offsetof(auto_explain_extension_options, options) +
527 20 : sizeof(auto_explain_option) * maxoptions +
528 : rawstring_len;
529 20 : result = (auto_explain_extension_options *) guc_malloc(LOG, allocsize);
530 20 : if (result == NULL)
531 0 : return false;
532 :
533 : /* Copy the string after the options array. */
534 20 : rawstring = (char *) &result->options[maxoptions];
535 20 : memcpy(rawstring, *newval, rawstring_len);
536 :
537 : /* Parse. */
538 20 : options = result->options;
539 20 : result->noptions = auto_explain_split_options(rawstring, options,
540 : maxoptions, &errmsg);
541 20 : if (result->noptions < 0)
542 : {
543 8 : GUC_check_errdetail("%s", errmsg);
544 8 : guc_free(result);
545 8 : return false;
546 : }
547 :
548 : /*
549 : * Retry with a larger array if needed.
550 : *
551 : * It should be impossible for this to loop more than once, because
552 : * auto_explain_split_options tells us how many entries are needed.
553 : */
554 12 : if (result->noptions > maxoptions)
555 : {
556 1 : maxoptions = result->noptions;
557 1 : guc_free(result);
558 1 : goto retry;
559 : }
560 :
561 : /* Validate each option against its registered check handler. */
562 29 : for (int i = 0; i < result->noptions; i++)
563 : {
564 23 : if (!GUCCheckExplainExtensionOption(options[i].name, options[i].value,
565 23 : options[i].type))
566 : {
567 5 : guc_free(result);
568 5 : return false;
569 : }
570 : }
571 :
572 6 : *extra = result;
573 6 : return true;
574 : }
575 :
576 : /*
577 : * GUC assign hook for auto_explain.log_extension_options.
578 : */
579 : static void
580 22 : assign_log_extension_options(const char *newval, void *extra)
581 : {
582 22 : extension_options = (auto_explain_extension_options *) extra;
583 22 : }
584 :
585 : /*
586 : * Apply parsed extension options to an ExplainState.
587 : */
588 : static void
589 11 : apply_extension_options(ExplainState *es, auto_explain_extension_options *ext)
590 : {
591 11 : if (ext == NULL)
592 10 : return;
593 :
594 2 : for (int i = 0; i < ext->noptions; i++)
595 : {
596 1 : auto_explain_option *opt = &ext->options[i];
597 : DefElem *def;
598 : Node *arg;
599 :
600 1 : if (opt->value == NULL)
601 1 : arg = NULL;
602 0 : else if (opt->type == T_Integer)
603 0 : arg = (Node *) makeInteger(strtol(opt->value, NULL, 0));
604 0 : else if (opt->type == T_Float)
605 0 : arg = (Node *) makeFloat(opt->value);
606 : else
607 0 : arg = (Node *) makeString(opt->value);
608 :
609 1 : def = makeDefElem(opt->name, arg, -1);
610 1 : ApplyExtensionExplainOption(es, def, NULL);
611 : }
612 : }
613 :
614 : /*
615 : * auto_explain_scan_literal - In-place scanner for single-quoted string
616 : * literals.
617 : *
618 : * This is the single-quote analog of scan_quoted_identifier from varlena.c.
619 : */
620 : static char *
621 2 : auto_explain_scan_literal(char **endp, char **nextp)
622 : {
623 2 : char *token = *nextp + 1;
624 :
625 : for (;;)
626 : {
627 2 : *endp = strchr(*nextp + 1, '\'');
628 2 : if (*endp == NULL)
629 1 : return NULL; /* mismatched quotes */
630 1 : if ((*endp)[1] != '\'')
631 1 : break; /* found end of literal */
632 : /* Collapse adjacent quotes into one quote, and look again */
633 0 : memmove(*endp, *endp + 1, strlen(*endp));
634 0 : *nextp = *endp;
635 : }
636 : /* *endp now points at the terminating quote */
637 1 : *nextp = *endp + 1;
638 :
639 1 : return token;
640 : }
641 :
642 : /*
643 : * auto_explain_split_options - Parse an option string into an array of
644 : * auto_explain_option structs.
645 : *
646 : * Much of this logic is similar to SplitIdentifierString and friends, but our
647 : * needs are different enough that we roll our own parsing logic. The goal here
648 : * is to accept the same syntax that the main parser would accept inside of
649 : * an EXPLAIN option list. While we can't do that perfectly without adding a
650 : * lot more code, the goal of this implementation is to be close enough that
651 : * users don't really notice the differences.
652 : *
653 : * The input string is modified in place (null-terminated, downcased, quotes
654 : * collapsed). All name and value pointers in the output array refer into
655 : * this string, so the caller must ensure the string outlives the array.
656 : *
657 : * Returns the full number of options in the input string, but stores no
658 : * more than maxoptions into the caller-provided array. If a syntax error
659 : * occurs, returns -1 and sets *errmsg.
660 : */
661 : static int
662 20 : auto_explain_split_options(char *rawstring, auto_explain_option *options,
663 : int maxoptions, char **errmsg)
664 : {
665 20 : char *nextp = rawstring;
666 20 : int noptions = 0;
667 20 : bool done = false;
668 :
669 20 : *errmsg = NULL;
670 :
671 23 : while (scanner_isspace(*nextp))
672 3 : nextp++; /* skip leading whitespace */
673 :
674 20 : if (*nextp == '\0')
675 0 : return 0; /* empty string is fine */
676 :
677 53 : while (!done)
678 : {
679 : char *name;
680 : char *name_endp;
681 41 : char *value = NULL;
682 41 : char *value_endp = NULL;
683 41 : NodeTag type = T_Invalid;
684 :
685 : /* Parse the option name. */
686 41 : name = scan_identifier(&name_endp, &nextp, ',', true);
687 41 : if (name == NULL || name_endp == name)
688 : {
689 3 : *errmsg = "option name missing or empty";
690 8 : return -1;
691 : }
692 :
693 : /* Skip whitespace after the option name. */
694 53 : while (scanner_isspace(*nextp))
695 15 : nextp++;
696 :
697 : /*
698 : * Determine whether we have an option value. A comma or end of
699 : * string means no value; otherwise we have one.
700 : */
701 38 : if (*nextp != '\0' && *nextp != ',')
702 : {
703 15 : if (*nextp == '\'')
704 : {
705 : /* Single-quoted string literal. */
706 2 : type = T_String;
707 2 : value = auto_explain_scan_literal(&value_endp, &nextp);
708 2 : if (value == NULL)
709 : {
710 1 : *errmsg = "unterminated single-quoted string";
711 1 : return -1;
712 : }
713 : }
714 13 : else if (isdigit((unsigned char) *nextp) ||
715 9 : ((*nextp == '+' || *nextp == '-') &&
716 0 : isdigit((unsigned char) nextp[1])))
717 3 : {
718 : char *endptr;
719 : long intval;
720 : char saved;
721 :
722 : /* Remember the start of the next token, and find the end. */
723 4 : value = nextp;
724 23 : while (*nextp && *nextp != ',' && !scanner_isspace(*nextp))
725 19 : nextp++;
726 4 : value_endp = nextp;
727 :
728 : /* Temporarily '\0'-terminate so we can use strtol/strtod. */
729 4 : saved = *value_endp;
730 4 : *value_endp = '\0';
731 :
732 : /*
733 : * Integer, float, or neither?
734 : *
735 : * NB: Since we use strtol and strtod here rather than
736 : * pg_strtoint64_safe, some syntax that would be accepted by
737 : * the main parser is not accepted here, e.g. 100_000. On the
738 : * plus side, strtol and strtod won't allocate, and
739 : * pg_strtoint64_safe might. For now, it seems better to keep
740 : * things simple here.
741 : */
742 4 : errno = 0;
743 4 : intval = strtol(value, &endptr, 0);
744 4 : if (errno == 0 && *endptr == '\0' && endptr != value &&
745 2 : intval == (int) intval)
746 2 : type = T_Integer;
747 : else
748 : {
749 2 : type = T_Float;
750 2 : (void) strtod(value, &endptr);
751 2 : if (*endptr != '\0')
752 : {
753 1 : *value_endp = saved;
754 1 : *errmsg = "invalid numeric value";
755 1 : return -1;
756 : }
757 : }
758 :
759 : /* Remove temporary terminator. */
760 3 : *value_endp = saved;
761 : }
762 : else
763 : {
764 : /* Identifier, possibly double-quoted. */
765 9 : type = T_String;
766 9 : value = scan_identifier(&value_endp, &nextp, ',', true);
767 9 : if (value == NULL)
768 : {
769 : /*
770 : * scan_identifier will return NULL if it finds an
771 : * unterminated double-quoted identifier or it finds no
772 : * identifier at all because the next character is
773 : * whitespace or the separator character, here a comma.
774 : * But the latter case is impossible here because the code
775 : * above has skipped whitespace and checked for commas.
776 : */
777 1 : *errmsg = "unterminated double-quoted string";
778 1 : return -1;
779 : }
780 : }
781 : }
782 :
783 : /* Skip trailing whitespace. */
784 38 : while (scanner_isspace(*nextp))
785 3 : nextp++;
786 :
787 : /* Expect comma or end of string. */
788 35 : if (*nextp == ',')
789 : {
790 22 : nextp++;
791 43 : while (scanner_isspace(*nextp))
792 21 : nextp++;
793 22 : if (*nextp == '\0')
794 : {
795 1 : *errmsg = "trailing comma in option list";
796 1 : return -1;
797 : }
798 : }
799 13 : else if (*nextp == '\0')
800 12 : done = true;
801 : else
802 : {
803 1 : *errmsg = "expected comma or end of option list";
804 1 : return -1;
805 : }
806 :
807 : /*
808 : * Now safe to null-terminate the name and value. We couldn't do this
809 : * earlier because in the unquoted case, the null terminator position
810 : * may coincide with a character that the scanning logic above still
811 : * needed to read.
812 : */
813 33 : *name_endp = '\0';
814 33 : if (value_endp != NULL)
815 11 : *value_endp = '\0';
816 :
817 : /* Always count this option, and store the details if there is room. */
818 33 : if (noptions < maxoptions)
819 : {
820 31 : options[noptions].name = name;
821 31 : options[noptions].type = type;
822 31 : options[noptions].value = value;
823 : }
824 33 : noptions++;
825 : }
826 :
827 12 : return noptions;
828 : }
|