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
33/* GUC variables */
39
40/* Saved hook value */
42
43/* Other file-level globals */
44static int es_extension_id;
47
49 DefElem *opt,
50 ParseState *pstate);
52 IntoClause *into,
53 ExplainState *es,
54 const char *queryString,
55 ParamListInfo params,
56 QueryEnvironment *queryEnv);
57static bool pg_plan_advice_advice_check_hook(char **newval, void **extra,
59static DefElem *find_defelem_by_defname(List *deflist, char *defname);
60
61/*
62 * Initialize this module.
63 */
64void
66{
67 DefineCustomStringVariable("pg_plan_advice.advice",
68 "advice to apply during query planning",
69 NULL,
71 NULL,
73 0,
75 NULL,
76 NULL);
77
78 DefineCustomBoolVariable("pg_plan_advice.always_explain_supplied_advice",
79 "EXPLAIN output includes supplied advice even without EXPLAIN (PLAN_ADVICE)",
80 NULL,
82 true,
84 0,
85 NULL,
86 NULL,
87 NULL);
88
89 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.",
93 false,
95 0,
96 NULL,
97 NULL,
98 NULL);
99
100 DefineCustomBoolVariable("pg_plan_advice.feedback_warnings",
101 "Warn when supplied advice does not apply cleanly",
102 NULL,
104 false,
106 0,
107 NULL,
108 NULL,
109 NULL);
110
111 DefineCustomBoolVariable("pg_plan_advice.trace_mask",
112 "Emit debugging messages showing the computed strategy mask for each relation",
113 NULL,
115 false,
117 0,
118 NULL,
119 NULL,
120 NULL);
121
122 MarkGUCPrefixReserved("pg_plan_advice");
123
124 /* Get an ID that we can use to cache data in an ExplainState. */
125 es_extension_id = GetExplainExtensionId("pg_plan_advice");
126
127 /* Register the new EXPLAIN options implemented by this module. */
128 RegisterExtensionExplainOption("plan_advice",
130
131 /* Install hooks */
135}
136
137/*
138 * Return a pointer to a memory context where long-lived data managed by this
139 * module can be stored.
140 */
151
152/*
153 * Was the PLAN_ADVICE option specified and not set to false?
154 */
155bool
164
165/*
166 * Get the advice that should be used while planning a particular query.
167 */
168char *
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 foreach(lc, advisor_hook_list)
182 {
184 char *advice_string;
185
186 advice_string = (*hook) (glob, parse, query_string, cursorOptions, es);
187 if (advice_string != NULL)
188 return advice_string;
189 }
190
191 /* Otherwise, just use the value of the GUC. */
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 */
203void
212
213/*
214 * Remove an advisor.
215 */
216void
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 */
240void
251
252/*
253 * Handler for EXPLAIN (PLAN_ADVICE).
254 */
255static void
271
272/*
273 * Display a string that is likely to consist of multiple lines in EXPLAIN
274 * output.
275 */
276static void
278 char *value)
279{
280 char *s;
281
282 /* For non-text formats, it's best not to add any special handling. */
283 if (es->format != EXPLAIN_FORMAT_TEXT)
284 {
286 return;
287 }
288
289 /* In text format, if there is no data, display nothing. */
290 if (*value == '\0')
291 return;
292
293 /*
294 * It looks nicest to indent each line of the advice separately, beginning
295 * on the line below the label.
296 */
298 appendStringInfo(es->str, "%s:\n", qlabel);
299 es->indent++;
300 while ((s = strchr(value, '\n')) != NULL)
301 {
303 appendBinaryStringInfo(es->str, value, (s - value) + 1);
304 value = s + 1;
305 }
306
307 /* Don't interpret a terminal newline as a request for an empty line. */
308 if (*value != '\0')
309 {
311 appendStringInfo(es->str, "%s\n", value);
312 }
313
314 es->indent--;
315}
316
317/*
318 * Add advice feedback to the EXPLAIN output.
319 */
320static void
322{
324
327 {
328 int flags = defGetInt32(item);
329
330 appendStringInfo(&buf, "%s /* ", item->defname);
332 appendStringInfo(&buf, " */\n");
333 }
334
335 pg_plan_advice_explain_text_multiline(es, "Supplied Plan Advice",
336 buf.data);
337}
338
339/*
340 * Add relevant details, if any, to the EXPLAIN output for a single plan.
341 */
342static void
344 IntoClause *into,
345 ExplainState *es,
346 const char *queryString,
347 ParamListInfo params,
348 QueryEnvironment *queryEnv)
349{
350 bool should_explain;
353
355 prev_explain_per_plan(plannedstmt, into, es, queryString, params,
356 queryEnv);
357
358 /* Should an advice string be part of the EXPLAIN output? */
360
361 /* Find any data pgpa_planner_shutdown stashed in the PlannedStmt. */
363 "pg_plan_advice");
364 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 */
380 {
382
384 if (feedback != NULL)
386 }
387
388 /*
389 * If the PLAN_ADVICE option was specified -- and not set to FALSE -- show
390 * generated advice.
391 */
392 if (should_explain)
393 {
395 char *advice_string = NULL;
396
398 find_defelem_by_defname(pgpa_list, "advice_string");
400 {
402 pg_plan_advice_explain_text_multiline(es, "Generated Plan Advice",
404 }
405 }
406}
407
408/*
409 * Check hook for pg_plan_advice.advice
410 */
411static bool
413{
414 MemoryContext oldcontext;
415 MemoryContext tmpcontext;
416 char *error;
417
418 if (*newval == NULL)
419 return true;
420
422 "pg_plan_advice.advice",
424 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 */
433
434 if (error != NULL)
435 GUC_check_errdetail("Could not parse advice: %s", error);
436
437 MemoryContextSwitchTo(oldcontext);
438 MemoryContextDelete(tmpcontext);
439
440 return (error == NULL);
441}
442
443/*
444 * Search a list of DefElem objects for a given defname.
445 */
446static DefElem *
448{
450 {
451 if (strcmp(item->defname, defname) == 0)
452 return item;
453 }
454
455 return NULL;
456}
#define Assert(condition)
Definition c.h:927
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:56
void(* explain_per_plan_hook_type)(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv)
Definition explain.h:32
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)
@ EXPLAIN_FORMAT_TEXT
#define palloc0_object(type)
Definition fe_memutils.h:75
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:5091
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:5011
#define newval
void MarkGUCPrefixReserved(const char *className)
Definition guc.c:5148
#define GUC_check_errdetail
Definition guc.h:506
GucSource
Definition guc.h:112
@ PGC_USERSET
Definition guc.h:79
void parse(int)
Definition parse.c:49
static struct @174 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:166
MemoryContext CurrentMemoryContext
Definition mcxt.c:160
void MemoryContextDelete(MemoryContext context)
Definition mcxt.c:472
#define AllocSetContextCreate
Definition memutils.h:129
#define ALLOCSET_DEFAULT_SIZES
Definition memutils.h:160
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:124
#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:496
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)
PG_MODULE_MAGIC
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:343
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 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