LCOV - code coverage report
Current view: top level - contrib/auto_explain - auto_explain.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 90.9 % 241 219
Test Date: 2026-07-25 22:15:46 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 65.2 % 178 116

             Branch data     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