LCOV - code coverage report
Current view: top level - contrib/auto_explain - auto_explain.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 91.3 % 241 220
Test Date: 2026-04-07 14:16:30 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1