LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pg_plan_advice.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 90.1 % 111 100
Test Date: 2026-07-25 22:15:46 Functions: 85.7 % 14 12
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 88.7 % 62 55

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * pg_plan_advice.c
       4                 :             :  *    main entrypoints for generating and applying planner advice
       5                 :             :  *
       6                 :             :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       7                 :             :  *
       8                 :             :  *    contrib/pg_plan_advice/pg_plan_advice.c
       9                 :             :  *
      10                 :             :  *-------------------------------------------------------------------------
      11                 :             :  */
      12                 :             : #include "postgres.h"
      13                 :             : 
      14                 :             : #include "pg_plan_advice.h"
      15                 :             : #include "pgpa_ast.h"
      16                 :             : #include "pgpa_identifier.h"
      17                 :             : #include "pgpa_output.h"
      18                 :             : #include "pgpa_planner.h"
      19                 :             : #include "pgpa_trove.h"
      20                 :             : #include "pgpa_walker.h"
      21                 :             : 
      22                 :             : #include "commands/defrem.h"
      23                 :             : #include "commands/explain.h"
      24                 :             : #include "commands/explain_format.h"
      25                 :             : #include "commands/explain_state.h"
      26                 :             : #include "funcapi.h"
      27                 :             : #include "optimizer/planner.h"
      28                 :             : #include "storage/dsm_registry.h"
      29                 :             : #include "utils/guc.h"
      30                 :             : 
      31                 :          22 : PG_MODULE_MAGIC_EXT(
      32                 :             :                     .name = "pg_plan_advice",
      33                 :             :                     .version = PG_VERSION
      34                 :             : );
      35                 :             : 
      36                 :             : /* GUC variables */
      37                 :             : char       *pg_plan_advice_advice = NULL;
      38                 :             : bool        pg_plan_advice_always_store_advice_details = false;
      39                 :             : static bool pg_plan_advice_always_explain_supplied_advice = true;
      40                 :             : bool        pg_plan_advice_feedback_warnings = false;
      41                 :             : bool        pg_plan_advice_trace_mask = false;
      42                 :             : 
      43                 :             : /* Saved hook value */
      44                 :             : static explain_per_plan_hook_type prev_explain_per_plan = NULL;
      45                 :             : 
      46                 :             : /* Other file-level globals */
      47                 :             : static int  es_extension_id;
      48                 :             : static MemoryContext pgpa_memory_context = NULL;
      49                 :             : static List *advisor_hook_list = NIL;
      50                 :             : 
      51                 :             : static void pg_plan_advice_explain_option_handler(ExplainState *es,
      52                 :             :                                                   DefElem *opt,
      53                 :             :                                                   ParseState *pstate);
      54                 :             : static void pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
      55                 :             :                                                  IntoClause *into,
      56                 :             :                                                  ExplainState *es,
      57                 :             :                                                  const char *queryString,
      58                 :             :                                                  ParamListInfo params,
      59                 :             :                                                  QueryEnvironment *queryEnv);
      60                 :             : static bool pg_plan_advice_advice_check_hook(char **newval, void **extra,
      61                 :             :                                              GucSource source);
      62                 :             : static DefElem *find_defelem_by_defname(List *deflist, char *defname);
      63                 :             : 
      64                 :             : /*
      65                 :             :  * Initialize this module.
      66                 :             :  */
      67                 :             : void
      68                 :          22 : _PG_init(void)
      69                 :             : {
      70                 :          22 :     DefineCustomStringVariable("pg_plan_advice.advice",
      71                 :             :                                "advice to apply during query planning",
      72                 :             :                                NULL,
      73                 :             :                                &pg_plan_advice_advice,
      74                 :             :                                NULL,
      75                 :             :                                PGC_USERSET,
      76                 :             :                                0,
      77                 :             :                                pg_plan_advice_advice_check_hook,
      78                 :             :                                NULL,
      79                 :             :                                NULL);
      80                 :             : 
      81                 :          22 :     DefineCustomBoolVariable("pg_plan_advice.always_explain_supplied_advice",
      82                 :             :                              "EXPLAIN output includes supplied advice even without EXPLAIN (PLAN_ADVICE)",
      83                 :             :                              NULL,
      84                 :             :                              &pg_plan_advice_always_explain_supplied_advice,
      85                 :             :                              true,
      86                 :             :                              PGC_USERSET,
      87                 :             :                              0,
      88                 :             :                              NULL,
      89                 :             :                              NULL,
      90                 :             :                              NULL);
      91                 :             : 
      92                 :          22 :     DefineCustomBoolVariable("pg_plan_advice.always_store_advice_details",
      93                 :             :                              "Generate advice strings even when seemingly not required",
      94                 :             :                              "Use this option to see generated advice for prepared queries.",
      95                 :             :                              &pg_plan_advice_always_store_advice_details,
      96                 :             :                              false,
      97                 :             :                              PGC_USERSET,
      98                 :             :                              0,
      99                 :             :                              NULL,
     100                 :             :                              NULL,
     101                 :             :                              NULL);
     102                 :             : 
     103                 :          22 :     DefineCustomBoolVariable("pg_plan_advice.feedback_warnings",
     104                 :             :                              "Warn when supplied advice does not apply cleanly",
     105                 :             :                              NULL,
     106                 :             :                              &pg_plan_advice_feedback_warnings,
     107                 :             :                              false,
     108                 :             :                              PGC_USERSET,
     109                 :             :                              0,
     110                 :             :                              NULL,
     111                 :             :                              NULL,
     112                 :             :                              NULL);
     113                 :             : 
     114                 :          22 :     DefineCustomBoolVariable("pg_plan_advice.trace_mask",
     115                 :             :                              "Emit debugging messages showing the computed strategy mask for each relation",
     116                 :             :                              NULL,
     117                 :             :                              &pg_plan_advice_trace_mask,
     118                 :             :                              false,
     119                 :             :                              PGC_USERSET,
     120                 :             :                              0,
     121                 :             :                              NULL,
     122                 :             :                              NULL,
     123                 :             :                              NULL);
     124                 :             : 
     125                 :          22 :     MarkGUCPrefixReserved("pg_plan_advice");
     126                 :             : 
     127                 :             :     /* Get an ID that we can use to cache data in an ExplainState. */
     128                 :          22 :     es_extension_id = GetExplainExtensionId("pg_plan_advice");
     129                 :             : 
     130                 :             :     /* Register the new EXPLAIN options implemented by this module. */
     131                 :          22 :     RegisterExtensionExplainOption("plan_advice",
     132                 :             :                                    pg_plan_advice_explain_option_handler,
     133                 :             :                                    GUCCheckBooleanExplainOption);
     134                 :             : 
     135                 :             :     /* Install hooks */
     136                 :          22 :     pgpa_planner_install_hooks();
     137                 :          22 :     prev_explain_per_plan = explain_per_plan_hook;
     138                 :          22 :     explain_per_plan_hook = pg_plan_advice_explain_per_plan_hook;
     139                 :          22 : }
     140                 :             : 
     141                 :             : /*
     142                 :             :  * Return a pointer to a memory context where long-lived data managed by this
     143                 :             :  * module can be stored.
     144                 :             :  */
     145                 :             : MemoryContext
     146                 :           7 : pg_plan_advice_get_mcxt(void)
     147                 :             : {
     148         [ +  - ]:           7 :     if (pgpa_memory_context == NULL)
     149                 :           7 :         pgpa_memory_context = AllocSetContextCreate(TopMemoryContext,
     150                 :             :                                                     "pg_plan_advice",
     151                 :             :                                                     ALLOCSET_DEFAULT_SIZES);
     152                 :             : 
     153                 :           7 :     return pgpa_memory_context;
     154                 :             : }
     155                 :             : 
     156                 :             : /*
     157                 :             :  * Was the PLAN_ADVICE option specified and not set to false?
     158                 :             :  */
     159                 :             : bool
     160                 :       48443 : pg_plan_advice_should_explain(ExplainState *es)
     161                 :             : {
     162                 :       48443 :     bool       *plan_advice = NULL;
     163                 :             : 
     164         [ +  + ]:       48443 :     if (es != NULL)
     165                 :        7527 :         plan_advice = GetExplainExtensionState(es, es_extension_id);
     166   [ +  +  +  - ]:       48443 :     return plan_advice != NULL && *plan_advice;
     167                 :             : }
     168                 :             : 
     169                 :             : /*
     170                 :             :  * Get the advice that should be used while planning a particular query.
     171                 :             :  */
     172                 :             : char *
     173                 :       89151 : pg_plan_advice_get_supplied_query_advice(PlannerGlobal *glob,
     174                 :             :                                          Query *parse,
     175                 :             :                                          const char *query_string,
     176                 :             :                                          int cursorOptions,
     177                 :             :                                          ExplainState *es)
     178                 :             : {
     179                 :             :     ListCell   *lc;
     180                 :             : 
     181                 :             :     /*
     182                 :             :      * If any advisors are loaded, consult them. The first one that produces a
     183                 :             :      * non-NULL string wins.
     184                 :             :      */
     185   [ +  +  +  +  :      133695 :     foreach(lc, advisor_hook_list)
                   +  + ]
     186                 :             :     {
     187                 :       88977 :         pg_plan_advice_advisor_hook hook = lfirst(lc);
     188                 :             :         char       *advice_string;
     189                 :             : 
     190                 :       88977 :         advice_string = (*hook) (glob, parse, query_string, cursorOptions, es);
     191         [ +  + ]:       88192 :         if (advice_string != NULL)
     192                 :       43648 :             return advice_string;
     193                 :             :     }
     194                 :             : 
     195                 :             :     /* Otherwise, just use the value of the GUC. */
     196                 :       44718 :     return pg_plan_advice_advice;
     197                 :             : }
     198                 :             : 
     199                 :             : /*
     200                 :             :  * Add an advisor, which can supply advice strings to be used during future
     201                 :             :  * query planning operations.
     202                 :             :  *
     203                 :             :  * The advisor should return NULL if it has no advice string to offer for a
     204                 :             :  * given query. If multiple advisors are added, they will be consulted in the
     205                 :             :  * order added until one of them returns a non-NULL value.
     206                 :             :  */
     207                 :             : void
     208                 :           7 : pg_plan_advice_add_advisor(pg_plan_advice_advisor_hook hook)
     209                 :             : {
     210                 :             :     MemoryContext oldcontext;
     211                 :             : 
     212                 :           7 :     oldcontext = MemoryContextSwitchTo(pg_plan_advice_get_mcxt());
     213                 :           7 :     advisor_hook_list = lappend(advisor_hook_list, hook);
     214                 :           7 :     MemoryContextSwitchTo(oldcontext);
     215                 :           7 : }
     216                 :             : 
     217                 :             : /*
     218                 :             :  * Remove an advisor.
     219                 :             :  */
     220                 :             : void
     221                 :           0 : pg_plan_advice_remove_advisor(pg_plan_advice_advisor_hook hook)
     222                 :             : {
     223                 :             :     MemoryContext oldcontext;
     224                 :             : 
     225                 :           0 :     oldcontext = MemoryContextSwitchTo(pg_plan_advice_get_mcxt());
     226                 :           0 :     advisor_hook_list = list_delete_ptr(advisor_hook_list, hook);
     227                 :           0 :     MemoryContextSwitchTo(oldcontext);
     228                 :           0 : }
     229                 :             : 
     230                 :             : /*
     231                 :             :  * Other loadable modules can use this function to trigger advice generation.
     232                 :             :  *
     233                 :             :  * Calling this function with activate = true requests that any queries
     234                 :             :  * planned afterwards should generate plan advice, which will be stored in the
     235                 :             :  * PlannedStmt. Calling this function with activate = false revokes that
     236                 :             :  * request. Multiple loadable modules could be using this simultaneously, so
     237                 :             :  * make sure to only revoke your own requests.
     238                 :             :  *
     239                 :             :  * Note that you can't use this function to *suppress* advice generation,
     240                 :             :  * which can occur for other reasons, such as the use of EXPLAIN (PLAN_ADVICE),
     241                 :             :  * regardless. It's a way of turning advice generation on, not a way of turning
     242                 :             :  * it off.
     243                 :             :  */
     244                 :             : void
     245                 :           0 : pg_plan_advice_request_advice_generation(bool activate)
     246                 :             : {
     247         [ #  # ]:           0 :     if (activate)
     248                 :           0 :         pgpa_planner_generate_advice++;
     249                 :             :     else
     250                 :             :     {
     251                 :             :         Assert(pgpa_planner_generate_advice > 0);
     252                 :           0 :         pgpa_planner_generate_advice--;
     253                 :             :     }
     254                 :           0 : }
     255                 :             : 
     256                 :             : /*
     257                 :             :  * Handler for EXPLAIN (PLAN_ADVICE).
     258                 :             :  */
     259                 :             : static void
     260                 :         136 : pg_plan_advice_explain_option_handler(ExplainState *es, DefElem *opt,
     261                 :             :                                       ParseState *pstate)
     262                 :             : {
     263                 :             :     bool       *plan_advice;
     264                 :             : 
     265                 :         136 :     plan_advice = GetExplainExtensionState(es, es_extension_id);
     266                 :             : 
     267         [ +  - ]:         136 :     if (plan_advice == NULL)
     268                 :             :     {
     269                 :         136 :         plan_advice = palloc0_object(bool);
     270                 :         136 :         SetExplainExtensionState(es, es_extension_id, plan_advice);
     271                 :             :     }
     272                 :             : 
     273                 :         136 :     *plan_advice = defGetBoolean(opt);
     274                 :         136 : }
     275                 :             : 
     276                 :             : /*
     277                 :             :  * Display a string that is likely to consist of multiple lines in EXPLAIN
     278                 :             :  * output.
     279                 :             :  */
     280                 :             : static void
     281                 :         264 : pg_plan_advice_explain_text_multiline(ExplainState *es, char *qlabel,
     282                 :             :                                       char *value)
     283                 :             : {
     284                 :             :     char       *s;
     285                 :             : 
     286                 :             :     /* For non-text formats, it's best not to add any special handling. */
     287         [ +  + ]:         264 :     if (es->format != EXPLAIN_FORMAT_TEXT)
     288                 :             :     {
     289                 :           1 :         ExplainPropertyText(qlabel, value, es);
     290                 :           1 :         return;
     291                 :             :     }
     292                 :             : 
     293                 :             :     /* In text format, if there is no data, display nothing. */
     294         [ +  + ]:         263 :     if (*value == '\0')
     295                 :           1 :         return;
     296                 :             : 
     297                 :             :     /*
     298                 :             :      * It looks nicest to indent each line of the advice separately, beginning
     299                 :             :      * on the line below the label.
     300                 :             :      */
     301                 :         262 :     ExplainIndentText(es);
     302                 :         262 :     appendStringInfo(es->str, "%s:\n", qlabel);
     303                 :         262 :     es->indent++;
     304         [ +  + ]:         800 :     while ((s = strchr(value, '\n')) != NULL)
     305                 :             :     {
     306                 :         538 :         ExplainIndentText(es);
     307                 :         538 :         appendBinaryStringInfo(es->str, value, (s - value) + 1);
     308                 :         538 :         value = s + 1;
     309                 :             :     }
     310                 :             : 
     311                 :             :     /* Don't interpret a terminal newline as a request for an empty line. */
     312         [ +  + ]:         262 :     if (*value != '\0')
     313                 :             :     {
     314                 :         135 :         ExplainIndentText(es);
     315                 :         135 :         appendStringInfo(es->str, "%s\n", value);
     316                 :             :     }
     317                 :             : 
     318                 :         262 :     es->indent--;
     319                 :             : }
     320                 :             : 
     321                 :             : /*
     322                 :             :  * Add advice feedback to the EXPLAIN output.
     323                 :             :  */
     324                 :             : static void
     325                 :         129 : pg_plan_advice_explain_feedback(ExplainState *es, List *feedback)
     326                 :             : {
     327                 :             :     StringInfoData buf;
     328                 :             : 
     329                 :         129 :     initStringInfo(&buf);
     330   [ +  +  +  +  :         408 :     foreach_node(DefElem, item, feedback)
                   +  + ]
     331                 :             :     {
     332                 :         150 :         int         flags = defGetInt32(item);
     333                 :             : 
     334                 :         150 :         appendStringInfo(&buf, "%s /* ", item->defname);
     335                 :         150 :         pgpa_trove_append_flags(&buf, flags);
     336                 :         150 :         appendStringInfoString(&buf, " */\n");
     337                 :             :     }
     338                 :             : 
     339                 :         129 :     pg_plan_advice_explain_text_multiline(es, "Supplied Plan Advice",
     340                 :             :                                           buf.data);
     341                 :         129 : }
     342                 :             : 
     343                 :             : /*
     344                 :             :  * Add relevant details, if any, to the EXPLAIN output for a single plan.
     345                 :             :  */
     346                 :             : static void
     347                 :        3789 : pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
     348                 :             :                                      IntoClause *into,
     349                 :             :                                      ExplainState *es,
     350                 :             :                                      const char *queryString,
     351                 :             :                                      ParamListInfo params,
     352                 :             :                                      QueryEnvironment *queryEnv)
     353                 :             : {
     354                 :             :     bool        should_explain;
     355                 :             :     DefElem    *pgpa_item;
     356                 :             :     List       *pgpa_list;
     357                 :             : 
     358         [ -  + ]:        3789 :     if (prev_explain_per_plan)
     359                 :           0 :         prev_explain_per_plan(plannedstmt, into, es, queryString, params,
     360                 :             :                               queryEnv);
     361                 :             : 
     362                 :             :     /* Should an advice string be part of the EXPLAIN output? */
     363                 :        3789 :     should_explain = pg_plan_advice_should_explain(es);
     364                 :             : 
     365                 :             :     /* Find any data pgpa_planner_shutdown stashed in the PlannedStmt. */
     366                 :        3789 :     pgpa_item = find_defelem_by_defname(plannedstmt->extension_state,
     367                 :             :                                         "pg_plan_advice");
     368         [ +  + ]:        3789 :     pgpa_list = pgpa_item == NULL ? NULL : (List *) pgpa_item->arg;
     369                 :             : 
     370                 :             :     /*
     371                 :             :      * By default, if there is a record of attempting to apply advice during
     372                 :             :      * query planning, we always output that information, but the user can set
     373                 :             :      * pg_plan_advice.always_explain_supplied_advice = false to suppress that
     374                 :             :      * behavior. If they do, we'll only display it when the PLAN_ADVICE option
     375                 :             :      * was specified and not set to false.
     376                 :             :      *
     377                 :             :      * NB: If we're explaining a query planned beforehand -- i.e. a prepared
     378                 :             :      * statement -- the application of query advice may not have been
     379                 :             :      * recorded, and therefore this won't be able to show anything. Use
     380                 :             :      * pg_plan_advice.always_store_advice_details = true to work around this.
     381                 :             :      */
     382   [ +  +  +  +  :        3789 :     if (pgpa_list != NULL && (pg_plan_advice_always_explain_supplied_advice ||
                   -  + ]
     383                 :             :                               should_explain))
     384                 :             :     {
     385                 :             :         DefElem    *feedback;
     386                 :             : 
     387                 :         156 :         feedback = find_defelem_by_defname(pgpa_list, "feedback");
     388         [ +  + ]:         156 :         if (feedback != NULL)
     389                 :         129 :             pg_plan_advice_explain_feedback(es, (List *) feedback->arg);
     390                 :             :     }
     391                 :             : 
     392                 :             :     /*
     393                 :             :      * If the PLAN_ADVICE option was specified -- and not set to FALSE -- show
     394                 :             :      * generated advice.
     395                 :             :      */
     396         [ +  + ]:        3789 :     if (should_explain)
     397                 :             :     {
     398                 :             :         DefElem    *advice_string_item;
     399                 :         136 :         char       *advice_string = NULL;
     400                 :             : 
     401                 :             :         advice_string_item =
     402                 :         136 :             find_defelem_by_defname(pgpa_list, "advice_string");
     403         [ +  + ]:         136 :         if (advice_string_item != NULL)
     404                 :             :         {
     405                 :         135 :             advice_string = strVal(advice_string_item->arg);
     406                 :         135 :             pg_plan_advice_explain_text_multiline(es, "Generated Plan Advice",
     407                 :             :                                                   advice_string);
     408                 :             :         }
     409                 :             :     }
     410                 :        3789 : }
     411                 :             : 
     412                 :             : /*
     413                 :             :  * Check hook for pg_plan_advice.advice
     414                 :             :  */
     415                 :             : static bool
     416                 :         168 : pg_plan_advice_advice_check_hook(char **newval, void **extra, GucSource source)
     417                 :             : {
     418                 :             :     MemoryContext oldcontext;
     419                 :             :     MemoryContext tmpcontext;
     420                 :             :     char       *error;
     421                 :             : 
     422         [ +  + ]:         168 :     if (*newval == NULL)
     423                 :          22 :         return true;
     424                 :             : 
     425                 :         146 :     tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
     426                 :             :                                        "pg_plan_advice.advice",
     427                 :             :                                        ALLOCSET_DEFAULT_SIZES);
     428                 :         146 :     oldcontext = MemoryContextSwitchTo(tmpcontext);
     429                 :             : 
     430                 :             :     /*
     431                 :             :      * It would be nice to save the parse tree that we construct here for
     432                 :             :      * eventual use when planning with this advice, but *extra can only point
     433                 :             :      * to a single guc_malloc'd chunk, and our parse tree involves an
     434                 :             :      * arbitrary number of memory allocations.
     435                 :             :      */
     436                 :         146 :     (void) pgpa_parse(*newval, &error);
     437                 :             : 
     438         [ +  + ]:         146 :     if (error != NULL)
     439                 :          20 :         GUC_check_errdetail("Could not parse advice: %s", error);
     440                 :             : 
     441                 :         146 :     MemoryContextSwitchTo(oldcontext);
     442                 :         146 :     MemoryContextDelete(tmpcontext);
     443                 :             : 
     444                 :         146 :     return (error == NULL);
     445                 :             : }
     446                 :             : 
     447                 :             : /*
     448                 :             :  * Search a list of DefElem objects for a given defname.
     449                 :             :  */
     450                 :             : static DefElem *
     451                 :        4081 : find_defelem_by_defname(List *deflist, char *defname)
     452                 :             : {
     453   [ +  +  +  +  :        4257 :     foreach_node(DefElem, item, deflist)
                   +  + ]
     454                 :             :     {
     455         [ +  + ]:        4175 :         if (strcmp(item->defname, defname) == 0)
     456                 :        4040 :             return item;
     457                 :             :     }
     458                 :             : 
     459                 :          41 :     return NULL;
     460                 :             : }
        

Generated by: LCOV version 2.0-1