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-2024, 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_collector.h"
17 : : #include "pgpa_identifier.h"
18 : : #include "pgpa_output.h"
19 : : #include "pgpa_planner.h"
20 : : #include "pgpa_trove.h"
21 : : #include "pgpa_walker.h"
22 : :
23 : : #include "commands/defrem.h"
24 : : #include "commands/explain.h"
25 : : #include "commands/explain_format.h"
26 : : #include "commands/explain_state.h"
27 : : #include "funcapi.h"
28 : : #include "optimizer/planner.h"
29 : : #include "storage/dsm_registry.h"
30 : : #include "utils/guc.h"
31 : :
32 : 795 : PG_MODULE_MAGIC;
33 : :
34 : : static pgpa_shared_state *pgpa_state = NULL;
35 : : static dsa_area *pgpa_dsa_area = NULL;
36 : :
37 : : /* GUC variables */
38 : : char *pg_plan_advice_advice = NULL;
39 : : bool pg_plan_advice_always_store_advice_details = false;
40 : : static bool pg_plan_advice_always_explain_supplied_advice = true;
41 : : bool pg_plan_advice_local_collector = false;
42 : : int pg_plan_advice_local_collection_limit = 0;
43 : : bool pg_plan_advice_shared_collector = false;
44 : : int pg_plan_advice_shared_collection_limit = 0;
45 : : bool pg_plan_advice_trace_mask = false;
46 : :
47 : : /* Saved hook value */
48 : : static explain_per_plan_hook_type prev_explain_per_plan = NULL;
49 : :
50 : : /* Other file-level globals */
51 : : static int es_extension_id;
52 : : static MemoryContext pgpa_memory_context = NULL;
53 : :
54 : : static void pgpa_init_shared_state(void *ptr, void *arg);
55 : : static void pg_plan_advice_explain_option_handler(ExplainState *es,
56 : : DefElem *opt,
57 : : ParseState *pstate);
58 : : static void pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
59 : : IntoClause *into,
60 : : ExplainState *es,
61 : : const char *queryString,
62 : : ParamListInfo params,
63 : : QueryEnvironment *queryEnv);
64 : : static bool pg_plan_advice_advice_check_hook(char **newval, void **extra,
65 : : GucSource source);
66 : : static DefElem *find_defelem_by_defname(List *deflist, char *defname);
67 : :
68 : : /*
69 : : * Initialize this module.
70 : : */
71 : : void
72 : 795 : _PG_init(void)
73 : : {
74 : 795 : DefineCustomStringVariable("pg_plan_advice.advice",
75 : : "advice to apply during query planning",
76 : : NULL,
77 : : &pg_plan_advice_advice,
78 : : NULL,
79 : : PGC_USERSET,
80 : : 0,
81 : : pg_plan_advice_advice_check_hook,
82 : : NULL,
83 : : NULL);
84 : :
85 : 795 : DefineCustomBoolVariable("pg_plan_advice.always_explain_supplied_advice",
86 : : "EXPLAIN output includes supplied advice even without EXPLAIN (PLAN_ADVICE)",
87 : : NULL,
88 : : &pg_plan_advice_always_explain_supplied_advice,
89 : : true,
90 : : PGC_USERSET,
91 : : 0,
92 : : NULL,
93 : : NULL,
94 : : NULL);
95 : :
96 : 795 : DefineCustomBoolVariable("pg_plan_advice.always_store_advice_details",
97 : : "Generate advice strings even when seemingly not required",
98 : : "Use this option to see generated advice for prepared queries.",
99 : : &pg_plan_advice_always_store_advice_details,
100 : : false,
101 : : PGC_USERSET,
102 : : 0,
103 : : NULL,
104 : : NULL,
105 : : NULL);
106 : :
107 : 795 : DefineCustomBoolVariable("pg_plan_advice.local_collector",
108 : : "Enable the local advice collector.",
109 : : NULL,
110 : : &pg_plan_advice_local_collector,
111 : : false,
112 : : PGC_USERSET,
113 : : 0,
114 : : NULL,
115 : : NULL,
116 : : NULL);
117 : :
118 : 795 : DefineCustomIntVariable("pg_plan_advice.local_collection_limit",
119 : : "# of advice entries to retain in per-backend memory",
120 : : NULL,
121 : : &pg_plan_advice_local_collection_limit,
122 : : 0,
123 : : 0, INT_MAX,
124 : : PGC_USERSET,
125 : : 0,
126 : : NULL,
127 : : NULL,
128 : : NULL);
129 : :
130 : 795 : DefineCustomBoolVariable("pg_plan_advice.shared_collector",
131 : : "Enable the shared advice collector.",
132 : : NULL,
133 : : &pg_plan_advice_shared_collector,
134 : : false,
135 : : PGC_SUSET,
136 : : 0,
137 : : NULL,
138 : : NULL,
139 : : NULL);
140 : :
141 : 795 : DefineCustomIntVariable("pg_plan_advice.shared_collection_limit",
142 : : "# of advice entries to retain in shared memory",
143 : : NULL,
144 : : &pg_plan_advice_shared_collection_limit,
145 : : 0,
146 : : 0, INT_MAX,
147 : : PGC_SUSET,
148 : : 0,
149 : : NULL,
150 : : NULL,
151 : : NULL);
152 : :
153 : 795 : DefineCustomBoolVariable("pg_plan_advice.trace_mask",
154 : : "Emit debugging messages showing the computed strategy mask for each relation",
155 : : NULL,
156 : : &pg_plan_advice_trace_mask,
157 : : false,
158 : : PGC_USERSET,
159 : : 0,
160 : : NULL,
161 : : NULL,
162 : : NULL);
163 : :
164 : 795 : MarkGUCPrefixReserved("pg_plan_advice");
165 : :
166 : : /* Get an ID that we can use to cache data in an ExplainState. */
167 : 795 : es_extension_id = GetExplainExtensionId("pg_plan_advice");
168 : :
169 : : /* Register the new EXPLAIN options implemented by this module. */
170 : 795 : RegisterExtensionExplainOption("plan_advice",
171 : : pg_plan_advice_explain_option_handler);
172 : :
173 : : /* Install hooks */
174 : 795 : pgpa_planner_install_hooks();
175 : 795 : prev_explain_per_plan = explain_per_plan_hook;
176 : 795 : explain_per_plan_hook = pg_plan_advice_explain_per_plan_hook;
177 : 795 : }
178 : :
179 : : /*
180 : : * Initialize shared state when first created.
181 : : */
182 : : static void
183 : 1 : pgpa_init_shared_state(void *ptr, void *arg)
184 : : {
185 : 1 : pgpa_shared_state *state = (pgpa_shared_state *) ptr;
186 : :
187 : 1 : LWLockInitialize(&state->lock, LWLockNewTrancheId("pg_plan_advice_lock"));
188 : 1 : state->dsa_tranche = LWLockNewTrancheId("pg_plan_advice_dsa");
189 : 1 : state->area = DSA_HANDLE_INVALID;
190 : 1 : state->shared_collector = InvalidDsaPointer;
191 : 1 : }
192 : :
193 : : /*
194 : : * Return a pointer to a memory context where long-lived data managed by this
195 : : * module can be stored.
196 : : */
197 : : MemoryContext
198 : 2256 : pg_plan_advice_get_mcxt(void)
199 : : {
200 [ + + ]: 2256 : if (pgpa_memory_context == NULL)
201 : 255 : pgpa_memory_context = AllocSetContextCreate(TopMemoryContext,
202 : : "pg_plan_advice",
203 : : ALLOCSET_DEFAULT_SIZES);
204 : :
205 : 2256 : return pgpa_memory_context;
206 : : }
207 : :
208 : : /*
209 : : * Get a pointer to our shared state.
210 : : *
211 : : * If no shared state exists, create and initialize it. If it does exist but
212 : : * this backend has not yet accessed it, attach to it. Otherwise, just return
213 : : * our cached pointer.
214 : : *
215 : : * Along the way, make sure the relevant LWLock tranches are registered.
216 : : */
217 : : pgpa_shared_state *
218 : 22584 : pg_plan_advice_attach(void)
219 : : {
220 [ + + ]: 22584 : if (pgpa_state == NULL)
221 : : {
222 : 254 : bool found;
223 : :
224 : 254 : pgpa_state =
225 : 254 : GetNamedDSMSegment("pg_plan_advice", sizeof(pgpa_shared_state),
226 : : pgpa_init_shared_state, &found, NULL);
227 : 254 : }
228 : :
229 : 22584 : return pgpa_state;
230 : : }
231 : :
232 : : /*
233 : : * Return a pointer to pg_plan_advice's DSA area, creating it if needed.
234 : : */
235 : : dsa_area *
236 : 44638 : pg_plan_advice_dsa_area(void)
237 : : {
238 [ + + ]: 44638 : if (pgpa_dsa_area == NULL)
239 : : {
240 : 254 : pgpa_shared_state *state = pg_plan_advice_attach();
241 : 254 : dsa_handle area_handle;
242 : 254 : MemoryContext oldcontext;
243 : :
244 : 254 : oldcontext = MemoryContextSwitchTo(pg_plan_advice_get_mcxt());
245 : :
246 : 254 : LWLockAcquire(&state->lock, LW_EXCLUSIVE);
247 : 254 : area_handle = state->area;
248 [ + + ]: 254 : if (area_handle == DSA_HANDLE_INVALID)
249 : : {
250 : 1 : pgpa_dsa_area = dsa_create(state->dsa_tranche);
251 : 1 : dsa_pin(pgpa_dsa_area);
252 : 1 : state->area = dsa_get_handle(pgpa_dsa_area);
253 : 1 : LWLockRelease(&state->lock);
254 : 1 : }
255 : : else
256 : : {
257 : 253 : LWLockRelease(&state->lock);
258 : 253 : pgpa_dsa_area = dsa_attach(area_handle);
259 : : }
260 : :
261 : 254 : dsa_pin_mapping(pgpa_dsa_area);
262 : :
263 : 254 : MemoryContextSwitchTo(oldcontext);
264 : 254 : }
265 : :
266 : 44638 : return pgpa_dsa_area;
267 : : }
268 : :
269 : : /*
270 : : * Was the PLAN_ADVICE option specified and not set to false?
271 : : */
272 : : bool
273 : 3784 : pg_plan_advice_should_explain(ExplainState *es)
274 : : {
275 : 3784 : bool *plan_advice = NULL;
276 : :
277 [ + + ]: 3784 : if (es != NULL)
278 : 3744 : plan_advice = GetExplainExtensionState(es, es_extension_id);
279 [ + + ]: 3784 : return plan_advice != NULL && *plan_advice;
280 : 3784 : }
281 : :
282 : : /*
283 : : * Handler for EXPLAIN (PLAN_ADVICE).
284 : : */
285 : : static void
286 : 122 : pg_plan_advice_explain_option_handler(ExplainState *es, DefElem *opt,
287 : : ParseState *pstate)
288 : : {
289 : 122 : bool *plan_advice;
290 : :
291 : 122 : plan_advice = GetExplainExtensionState(es, es_extension_id);
292 : :
293 [ - + ]: 122 : if (plan_advice == NULL)
294 : : {
295 : 122 : plan_advice = palloc0_object(bool);
296 : 122 : SetExplainExtensionState(es, es_extension_id, plan_advice);
297 : 122 : }
298 : :
299 : 122 : *plan_advice = defGetBoolean(opt);
300 : 122 : }
301 : :
302 : : /*
303 : : * Display a string that is likely to consist of multiple lines in EXPLAIN
304 : : * output.
305 : : */
306 : : static void
307 : 234 : pg_plan_advice_explain_text_multiline(ExplainState *es, char *qlabel,
308 : : char *value)
309 : : {
310 : 234 : char *s;
311 : :
312 : : /* For non-text formats, it's best not to add any special handling. */
313 [ + + ]: 234 : if (es->format != EXPLAIN_FORMAT_TEXT)
314 : : {
315 : 1 : ExplainPropertyText(qlabel, value, es);
316 : 1 : return;
317 : : }
318 : :
319 : : /* In text format, if there is no data, display nothing. */
320 [ + + ]: 233 : if (*value == '\0')
321 : 1 : return;
322 : :
323 : : /*
324 : : * It looks nicest to indent each line of the advice separately, beginning
325 : : * on the line below the label.
326 : : */
327 : 232 : ExplainIndentText(es);
328 : 232 : appendStringInfo(es->str, "%s:\n", qlabel);
329 : 232 : es->indent++;
330 [ + + ]: 723 : while ((s = strchr(value, '\n')) != NULL)
331 : : {
332 : 491 : ExplainIndentText(es);
333 : 491 : appendBinaryStringInfo(es->str, value, (s - value) + 1);
334 : 491 : value = s + 1;
335 : : }
336 : :
337 : : /* Don't interpret a terminal newline as a request for an empty line. */
338 [ + + ]: 232 : if (*value != '\0')
339 : : {
340 : 121 : ExplainIndentText(es);
341 : 121 : appendStringInfo(es->str, "%s\n", value);
342 : 121 : }
343 : :
344 : 232 : es->indent--;
345 [ - + ]: 234 : }
346 : :
347 : : /*
348 : : * Add advice feedback to the EXPLAIN output.
349 : : */
350 : : static void
351 : 113 : pg_plan_advice_explain_feedback(ExplainState *es, List *feedback)
352 : : {
353 : 113 : StringInfoData buf;
354 : :
355 : 113 : initStringInfo(&buf);
356 [ + + + + : 359 : foreach_node(DefElem, item, feedback)
+ + + + ]
357 : : {
358 : 133 : int flags = defGetInt32(item);
359 : :
360 : 133 : appendStringInfo(&buf, "%s /* ", item->defname);
361 [ + + ]: 133 : if ((flags & PGPA_TE_MATCH_FULL) != 0)
362 : : {
363 [ - + ]: 113 : Assert((flags & PGPA_TE_MATCH_PARTIAL) != 0);
364 : 113 : appendStringInfo(&buf, "matched");
365 : 113 : }
366 [ + + ]: 20 : else if ((flags & PGPA_TE_MATCH_PARTIAL) != 0)
367 : 4 : appendStringInfo(&buf, "partially matched");
368 : : else
369 : 16 : appendStringInfo(&buf, "not matched");
370 [ + + ]: 133 : if ((flags & PGPA_TE_INAPPLICABLE) != 0)
371 : 4 : appendStringInfo(&buf, ", inapplicable");
372 [ + + ]: 133 : if ((flags & PGPA_TE_CONFLICTING) != 0)
373 : 14 : appendStringInfo(&buf, ", conflicting");
374 [ + + ]: 133 : if ((flags & PGPA_TE_FAILED) != 0)
375 : 30 : appendStringInfo(&buf, ", failed");
376 : 133 : appendStringInfo(&buf, " */\n");
377 : 246 : }
378 : :
379 : 226 : pg_plan_advice_explain_text_multiline(es, "Supplied Plan Advice",
380 : 113 : buf.data);
381 : 113 : }
382 : :
383 : : /*
384 : : * Add relevant details, if any, to the EXPLAIN output for a single plan.
385 : : */
386 : : static void
387 : 3610 : pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
388 : : IntoClause *into,
389 : : ExplainState *es,
390 : : const char *queryString,
391 : : ParamListInfo params,
392 : : QueryEnvironment *queryEnv)
393 : : {
394 : 3610 : bool should_explain;
395 : 3610 : DefElem *pgpa_item;
396 : 3610 : List *pgpa_list;
397 : :
398 [ + - ]: 3610 : if (prev_explain_per_plan)
399 : 0 : prev_explain_per_plan(plannedstmt, into, es, queryString, params,
400 : 0 : queryEnv);
401 : :
402 : : /* Should an advice string be part of the EXPLAIN output? */
403 : 3610 : should_explain = pg_plan_advice_should_explain(es);
404 : :
405 : : /* Find any data pgpa_planner_shutdown stashed in the PlannedStmt. */
406 : 3610 : pgpa_item = find_defelem_by_defname(plannedstmt->extension_state,
407 : : "pg_plan_advice");
408 [ + + ]: 3610 : pgpa_list = pgpa_item == NULL ? NULL : (List *) pgpa_item->arg;
409 : :
410 : : /*
411 : : * By default, if there is a record of attempting to apply advice during
412 : : * query planning, we always output that information, but the user can set
413 : : * pg_plan_advice.always_explain_supplied_advice = false to suppress that
414 : : * behavior. If they do, we'll only display it when the PLAN_ADVICE option
415 : : * was specified and not set to false.
416 : : *
417 : : * NB: If we're explaining a query planned beforehand -- i.e. a prepared
418 : : * statement -- the application of query advice may not have been
419 : : * recorded, and therefore this won't be able to show anything. Use
420 : : * pg_plan_advice.always_store_advice_details = true to work around this.
421 : : */
422 [ + + + + : 3610 : if (pgpa_list != NULL && (pg_plan_advice_always_explain_supplied_advice ||
- + ]
423 : 3472 : should_explain))
424 : : {
425 : 133 : DefElem *feedback;
426 : :
427 : 133 : feedback = find_defelem_by_defname(pgpa_list, "feedback");
428 [ + + ]: 133 : if (feedback != NULL)
429 : 113 : pg_plan_advice_explain_feedback(es, (List *) feedback->arg);
430 : 133 : }
431 : :
432 : : /*
433 : : * If the PLAN_ADVICE option was specified -- and not sent to FALSE --
434 : : * show generated advice.
435 : : */
436 [ + + ]: 3610 : if (should_explain)
437 : : {
438 : 122 : DefElem *advice_string_item;
439 : 122 : char *advice_string = NULL;
440 : :
441 : 122 : advice_string_item =
442 : 122 : find_defelem_by_defname(pgpa_list, "advice_string");
443 [ + + ]: 122 : if (advice_string_item != NULL)
444 : : {
445 : 121 : advice_string = strVal(advice_string_item->arg);
446 : 242 : pg_plan_advice_explain_text_multiline(es, "Generated Plan Advice",
447 : 121 : advice_string);
448 : 121 : }
449 : 122 : }
450 : 3610 : }
451 : :
452 : : /*
453 : : * Check hook for pg_plan_advice.advice
454 : : */
455 : : static bool
456 : 2654 : pg_plan_advice_advice_check_hook(char **newval, void **extra, GucSource source)
457 : : {
458 : 2654 : MemoryContext oldcontext;
459 : 2654 : MemoryContext tmpcontext;
460 : 2654 : char *error;
461 : :
462 [ + + ]: 2654 : if (*newval == NULL)
463 : 1269 : return true;
464 : :
465 : 1385 : tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
466 : : "pg_plan_advice.advice",
467 : : ALLOCSET_DEFAULT_SIZES);
468 : 1385 : oldcontext = MemoryContextSwitchTo(tmpcontext);
469 : :
470 : : /*
471 : : * It would be nice to save the parse tree that we construct here for
472 : : * eventual use when planning with this advice, but *extra can only point
473 : : * to a single guc_malloc'd chunk, and our parse tree involves an
474 : : * arbitrary number of memory allocations.
475 : : */
476 : 1385 : (void) pgpa_parse(*newval, &error);
477 : :
478 [ + + ]: 1385 : if (error != NULL)
479 : : {
480 : 17 : GUC_check_errdetail("Could not parse advice: %s", error);
481 : 17 : return false;
482 : : }
483 : :
484 : 1368 : MemoryContextSwitchTo(oldcontext);
485 : 1368 : MemoryContextDelete(tmpcontext);
486 : :
487 : 1368 : return true;
488 : 2654 : }
489 : :
490 : : /*
491 : : * Search a list of DefElem objects for a given defname.
492 : : */
493 : : static DefElem *
494 : 3865 : find_defelem_by_defname(List *deflist, char *defname)
495 : : {
496 [ + + + + : 7851 : foreach_node(DefElem, item, deflist)
+ + + + +
+ - + + ]
497 : : {
498 [ + + ]: 3960 : if (strcmp(item->defname, defname) == 0)
499 : 3839 : return item;
500 : 147 : }
501 : :
502 : 26 : return NULL;
503 : 3865 : }
|