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