LCOV - code coverage report
Current view: top level - contrib/auto_explain - auto_explain.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 90.9 % 241 219
Test Date: 2026-05-09 14:16:36 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_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              : }
        

Generated by: LCOV version 2.0-1