PostgreSQL Source Code git master
Loading...
Searching...
No Matches
pg_plan_advice.c
Go to the documentation of this file.
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"
26#include "funcapi.h"
27#include "optimizer/planner.h"
29#include "utils/guc.h"
30
32 .name = "pg_plan_advice",
33 .version = PG_VERSION
34);
35
36/* GUC variables */
42
43/* Saved hook value */
45
46/* Other file-level globals */
47static int es_extension_id;
50
52 DefElem *opt,
53 ParseState *pstate);
55 IntoClause *into,
56 ExplainState *es,
57 const char *queryString,
58 ParamListInfo params,
59 QueryEnvironment *queryEnv);
60static bool pg_plan_advice_advice_check_hook(char **newval, void **extra,
62static DefElem *find_defelem_by_defname(List *deflist, char *defname);
63
64/*
65 * Initialize this module.
66 */
67void
69{
70 DefineCustomStringVariable("pg_plan_advice.advice",
71 "advice to apply during query planning",
72 NULL,
74 NULL,
76 0,
78 NULL,
79 NULL);
80
81 DefineCustomBoolVariable("pg_plan_advice.always_explain_supplied_advice",
82 "EXPLAIN output includes supplied advice even without EXPLAIN (PLAN_ADVICE)",
83 NULL,
85 true,
87 0,
88 NULL,
89 NULL,
90 NULL);
91
92 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.",
96 false,
98 0,
99 NULL,
100 NULL,
101 NULL);
102
103 DefineCustomBoolVariable("pg_plan_advice.feedback_warnings",
104 "Warn when supplied advice does not apply cleanly",
105 NULL,
107 false,
109 0,
110 NULL,
111 NULL,
112 NULL);
113
114 DefineCustomBoolVariable("pg_plan_advice.trace_mask",
115 "Emit debugging messages showing the computed strategy mask for each relation",
116 NULL,
118 false,
120 0,
121 NULL,
122 NULL,
123 NULL);
124
125 MarkGUCPrefixReserved("pg_plan_advice");
126
127 /* Get an ID that we can use to cache data in an ExplainState. */
128 es_extension_id = GetExplainExtensionId("pg_plan_advice");
129
130 /* Register the new EXPLAIN options implemented by this module. */
131 RegisterExtensionExplainOption("plan_advice",
134
135 /* Install hooks */
139}
140
141/*
142 * Return a pointer to a memory context where long-lived data managed by this
143 * module can be stored.
144 */
155
156/*
157 * Was the PLAN_ADVICE option specified and not set to false?
158 */
159bool
168
169/*
170 * Get the advice that should be used while planning a particular query.
171 */
172char *
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 foreach(lc, advisor_hook_list)
186 {
188 char *advice_string;
189
190 advice_string = (*hook) (glob, parse, query_string, cursorOptions, es);
191 if (advice_string != NULL)
192 return advice_string;
193 }
194
195 /* Otherwise, just use the value of the GUC. */
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 */
207void
216
217/*
218 * Remove an advisor.
219 */
220void
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 */
244void
255
256/*
257 * Handler for EXPLAIN (PLAN_ADVICE).
258 */
259static void
275
276/*
277 * Display a string that is likely to consist of multiple lines in EXPLAIN
278 * output.
279 */
280static void
282 char *value)
283{
284 char *s;
285
286 /* For non-text formats, it's best not to add any special handling. */
287 if (es->format != EXPLAIN_FORMAT_TEXT)
288 {
290 return;
291 }
292
293 /* In text format, if there is no data, display nothing. */
294 if (*value == '\0')
295 return;
296
297 /*
298 * It looks nicest to indent each line of the advice separately, beginning
299 * on the line below the label.
300 */
302 appendStringInfo(es->str, "%s:\n", qlabel);
303 es->indent++;
304 while ((s = strchr(value, '\n')) != NULL)
305 {
307 appendBinaryStringInfo(es->str, value, (s - value) + 1);
308 value = s + 1;
309 }
310
311 /* Don't interpret a terminal newline as a request for an empty line. */
312 if (*value != '\0')
313 {
315 appendStringInfo(es->str, "%s\n", value);
316 }
317
318 es->indent--;
319}
320
321/*
322 * Add advice feedback to the EXPLAIN output.
323 */
324static void
326{
328
331 {
332 int flags = defGetInt32(item);
333
334 appendStringInfo(&buf, "%s /* ", item->defname);
336 appendStringInfoString(&buf, " */\n");
337 }
338
339 pg_plan_advice_explain_text_multiline(es, "Supplied Plan Advice",
340 buf.data);
341}
342
343/*
344 * Add relevant details, if any, to the EXPLAIN output for a single plan.
345 */
346static void
348 IntoClause *into,
349 ExplainState *es,
350 const char *queryString,
351 ParamListInfo params,
352 QueryEnvironment *queryEnv)
353{
354 bool should_explain;
357
359 prev_explain_per_plan(plannedstmt, into, es, queryString, params,
360 queryEnv);
361
362 /* Should an advice string be part of the EXPLAIN output? */
364
365 /* Find any data pgpa_planner_shutdown stashed in the PlannedStmt. */
367 "pg_plan_advice");
368 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 */
384 {
386
388 if (feedback != NULL)
390 }
391
392 /*
393 * If the PLAN_ADVICE option was specified -- and not set to FALSE -- show
394 * generated advice.
395 */
396 if (should_explain)
397 {
399 char *advice_string = NULL;
400
402 find_defelem_by_defname(pgpa_list, "advice_string");
404 {
405 advice_string = strVal(advice_string_item->arg);
406 pg_plan_advice_explain_text_multiline(es, "Generated Plan Advice",
407 advice_string);
408 }
409 }
410}
411
412/*
413 * Check hook for pg_plan_advice.advice
414 */
415static bool
417{
418 MemoryContext oldcontext;
419 MemoryContext tmpcontext;
420 char *error;
421
422 if (*newval == NULL)
423 return true;
424
426 "pg_plan_advice.advice",
428 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 */
437
438 if (error != NULL)
439 GUC_check_errdetail("Could not parse advice: %s", error);
440
441 MemoryContextSwitchTo(oldcontext);
442 MemoryContextDelete(tmpcontext);
443
444 return (error == NULL);
445}
446
447/*
448 * Search a list of DefElem objects for a given defname.
449 */
450static DefElem *
452{
454 {
455 if (strcmp(item->defname, defname) == 0)
456 return item;
457 }
458
459 return NULL;
460}
#define Assert(condition)
Definition c.h:1002
int32 defGetInt32(DefElem *def)
Definition define.c:148
bool defGetBoolean(DefElem *def)
Definition define.c:93
explain_per_plan_hook_type explain_per_plan_hook
Definition explain.c:58
void(* explain_per_plan_hook_type)(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv)
Definition explain.h:33
void ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
void ExplainIndentText(ExplainState *es)
int GetExplainExtensionId(const char *extension_name)
void * GetExplainExtensionState(ExplainState *es, int extension_id)
void SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
void RegisterExtensionExplainOption(const char *option_name, ExplainOptionHandler handler, ExplainOptionGUCCheckHandler guc_check_handler)
bool GUCCheckBooleanExplainOption(const char *option_name, const char *option_value, NodeTag option_type)
@ EXPLAIN_FORMAT_TEXT
#define palloc0_object(type)
Definition fe_memutils.h:90
#define PG_MODULE_MAGIC_EXT(...)
Definition fmgr.h:540
void DefineCustomStringVariable(const char *name, const char *short_desc, const char *long_desc, char **valueAddr, const char *bootValue, GucContext context, int flags, GucStringCheckHook check_hook, GucStringAssignHook assign_hook, GucShowHook show_hook)
Definition guc.c:5129
void DefineCustomBoolVariable(const char *name, const char *short_desc, const char *long_desc, bool *valueAddr, bool bootValue, GucContext context, int flags, GucBoolCheckHook check_hook, GucBoolAssignHook assign_hook, GucShowHook show_hook)
Definition guc.c:5049
#define newval
void MarkGUCPrefixReserved(const char *className)
Definition guc.c:5186
#define GUC_check_errdetail
Definition guc.h:508
GucSource
Definition guc.h:112
@ PGC_USERSET
Definition guc.h:79
void parse(int)
Definition parse.c:49
static struct @175 value
List * list_delete_ptr(List *list, void *datum)
Definition list.c:872
List * lappend(List *list, void *datum)
Definition list.c:339
MemoryContext TopMemoryContext
Definition mcxt.c:167
MemoryContext CurrentMemoryContext
Definition mcxt.c:161
void MemoryContextDelete(MemoryContext context)
Definition mcxt.c:475
#define AllocSetContextCreate
Definition memutils.h:129
#define ALLOCSET_DEFAULT_SIZES
Definition memutils.h:160
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:138
#define lfirst(lc)
Definition pg_list.h:172
#define NIL
Definition pg_list.h:68
#define foreach_node(type, var, lst)
Definition pg_list.h:528
static MemoryContext pgpa_memory_context
char * pg_plan_advice_advice
void _PG_init(void)
MemoryContext pg_plan_advice_get_mcxt(void)
static DefElem * find_defelem_by_defname(List *deflist, char *defname)
bool pg_plan_advice_trace_mask
static explain_per_plan_hook_type prev_explain_per_plan
bool pg_plan_advice_always_store_advice_details
static bool pg_plan_advice_advice_check_hook(char **newval, void **extra, GucSource source)
bool pg_plan_advice_should_explain(ExplainState *es)
static void pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv)
void pg_plan_advice_remove_advisor(pg_plan_advice_advisor_hook hook)
static void pg_plan_advice_explain_option_handler(ExplainState *es, DefElem *opt, ParseState *pstate)
char * pg_plan_advice_get_supplied_query_advice(PlannerGlobal *glob, Query *parse, const char *query_string, int cursorOptions, ExplainState *es)
void pg_plan_advice_request_advice_generation(bool activate)
void pg_plan_advice_add_advisor(pg_plan_advice_advisor_hook hook)
static void pg_plan_advice_explain_text_multiline(ExplainState *es, char *qlabel, char *value)
bool pg_plan_advice_feedback_warnings
static List * advisor_hook_list
static void pg_plan_advice_explain_feedback(ExplainState *es, List *feedback)
static int es_extension_id
static bool pg_plan_advice_always_explain_supplied_advice
char *(* pg_plan_advice_advisor_hook)(PlannerGlobal *glob, Query *parse, const char *query_string, int cursorOptions, ExplainState *es)
static rewind_source * source
Definition pg_rewind.c:89
static char buf[DEFAULT_XLOG_SEG_SIZE]
List * pgpa_parse(const char *advice_string, char **error_p)
void pgpa_planner_install_hooks(void)
int pgpa_planner_generate_advice
void pgpa_trove_append_flags(StringInfo buf, int flags)
Definition pgpa_trove.c:345
static int fb(int x)
static void error(void)
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145
void appendBinaryStringInfo(StringInfo str, const void *data, int datalen)
Definition stringinfo.c:281
void appendStringInfoString(StringInfo str, const char *s)
Definition stringinfo.c:230
void initStringInfo(StringInfo str)
Definition stringinfo.c:97
StringInfo str
ExplainFormat format
Definition pg_list.h:54
List * extension_state
Definition plannodes.h:165
#define strVal(v)
Definition value.h:82
const char * name