LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pg_plan_advice.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 78.4 % 111 87
Test Date: 2026-03-16 00:15:06 Functions: 71.4 % 14 10
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1