PostgreSQL Source Code git master
Loading...
Searching...
No Matches
pg_stash_advice.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * pg_stash_advice.c
4 * core infrastructure for pg_stash_advice contrib module
5 *
6 * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 *
8 * contrib/pg_stash_advice/pg_stash_advice.c
9 *
10 *-------------------------------------------------------------------------
11 */
12#include "postgres.h"
13
14#include "common/hashfn.h"
15#include "common/string.h"
16#include "nodes/queryjumble.h"
17#include "pg_plan_advice.h"
18#include "pg_stash_advice.h"
20#include "utils/guc.h"
21#include "utils/memutils.h"
22
24
25/* Shared memory hash table parameters */
34
36 sizeof(pgsa_entry_key),
37 sizeof(pgsa_entry),
41 LWTRANCHE_INVALID /* gets set at runtime */
42};
43
44/* GUC variable */
46
47/* Shared memory pointers */
52
53/* Other global variables */
55
56/* Function prototypes */
57static char *pgsa_advisor(PlannerGlobal *glob,
58 Query *parse,
59 const char *query_string,
60 int cursorOptions,
61 ExplainState *es);
62static bool pgsa_check_stash_name_guc(char **newval, void **extra,
64static void pgsa_init_shared_state(void *ptr, void *arg);
65static bool pgsa_is_identifier(char *str);
66
67/* Stash name -> stash ID hash table */
68#define SH_PREFIX pgsa_stash_name_table
69#define SH_ELEMENT_TYPE pgsa_stash_name
70#define SH_KEY_TYPE uint64
71#define SH_KEY pgsa_stash_id
72#define SH_HASH_KEY(tb, key) hash_bytes((const unsigned char *) &(key), sizeof(uint64))
73#define SH_EQUAL(tb, a, b) (a == b)
74#define SH_SCOPE extern
75#define SH_DEFINE
76#include "lib/simplehash.h"
77
78/*
79 * Initialize this module.
80 */
81void
83{
85
86 /* If compute_query_id = 'auto', we would like query IDs. */
88
89 /* Define our GUCs. */
90 DefineCustomStringVariable("pg_stash_advice.stash_name",
91 "Name of the advice stash to be used in this session.",
92 NULL,
94 "",
96 0,
98 NULL,
99 NULL);
100
101 MarkGUCPrefixReserved("pg_stash_advice");
102
103 /* Tell pg_plan_advice that we want to provide advice strings. */
105 load_external_function("pg_plan_advice", "pg_plan_advice_add_advisor",
106 true, NULL);
107 (*add_advisor_fn) (pgsa_advisor);
108}
109
110/*
111 * Get the advice string that has been configured for this query, if any,
112 * and return it. Otherwise, return NULL.
113 */
114static char *
116 const char *query_string, int cursorOptions,
117 ExplainState *es)
118{
119 pgsa_entry_key key;
120 pgsa_entry *entry;
121 char *advice_string;
123
124 /*
125 * Exit quickly if the stash name is empty or there's no query ID.
126 */
127 if (pg_stash_advice_stash_name[0] == '\0' || parse->queryId == 0)
128 return NULL;
129
130 /* Attach to dynamic shared memory if not already done. */
132 pgsa_attach();
133
134 /*
135 * Translate pg_stash_advice.stash_name to an integer ID.
136 *
137 * pgsa_check_stash_name_guc() has already validated the advice stash
138 * name, so we don't need to call pgsa_check_stash_name() here.
139 */
141 if (stash_id == 0)
142 return NULL;
143
144 /*
145 * Look up the advice string for the given stash ID + query ID.
146 *
147 * If we find an advice string, we copy it into the current memory
148 * context, presumably short-lived, so that we can release the lock on the
149 * dshash entry. pg_plan_advice only needs the value to remain allocated
150 * long enough for it to be parsed, so this should be good enough.
151 */
152 memset(&key, 0, sizeof(pgsa_entry_key));
153 key.pgsa_stash_id = stash_id;
154 key.queryId = parse->queryId;
155 entry = dshash_find(pgsa_entry_dshash, &key, false);
156 if (entry == NULL)
157 return NULL;
158 if (entry->advice_string == InvalidDsaPointer)
159 advice_string = NULL;
160 else
161 advice_string = pstrdup(dsa_get_address(pgsa_dsa_area,
162 entry->advice_string));
164
165 /* If we found an advice string, emit a debug message. */
166 if (advice_string != NULL)
167 elog(DEBUG2, "supplying automatic advice for stash \"%s\", query ID %" PRId64 ": %s",
168 pg_stash_advice_stash_name, key.queryId, advice_string);
169
170 return advice_string;
171}
172
173/*
174 * Attach to various structures in dynamic shared memory.
175 *
176 * This function is designed to be resilient against errors. That is, if it
177 * fails partway through, it should be possible to call it again, repeat no
178 * work already completed, and potentially succeed or at least get further if
179 * whatever caused the previous failure has been corrected.
180 */
181void
183{
184 bool found;
185 MemoryContext oldcontext;
186
187 /*
188 * Create a memory context to make sure that any control structures
189 * allocated in local memory are sufficiently persistent.
190 */
193 "pg_stash_advice",
196
197 /* Attach to the fixed-size state object if not already done. */
198 if (pgsa_state == NULL)
199 pgsa_state = GetNamedDSMSegment("pg_stash_advice",
200 sizeof(pgsa_shared_state),
202 &found, NULL);
203
204 /* Attach to the DSA area if not already done. */
205 if (pgsa_dsa_area == NULL)
206 {
208
212 {
217 }
218 else
219 {
222 }
224 }
225
226 /* Attach to the stash_name->stash_id hash table if not already done. */
227 if (pgsa_stash_dshash == NULL)
228 {
230
235 {
238 NULL);
242 }
243 else
244 {
249 }
250 }
251
252 /* Attach to the entry hash table if not already done. */
253 if (pgsa_entry_dshash == NULL)
254 {
256
261 {
264 NULL);
268 }
269 else
270 {
275 }
276 }
277
278 /* Restore previous memory context. */
279 MemoryContextSwitchTo(oldcontext);
280}
281
282/*
283 * Check whether an advice stash name is legal, and signal an error if not.
284 *
285 * Keep this in sync with pgsa_check_stash_name_guc, below.
286 */
287void
289{
290 /* Reject empty advice stash name. */
291 if (stash_name[0] == '\0')
294 errmsg("advice stash name may not be zero length"));
295
296 /* Reject overlong advice stash names. */
297 if (strlen(stash_name) + 1 > NAMEDATALEN)
300 errmsg("advice stash names may not be longer than %d bytes",
301 NAMEDATALEN - 1));
302
303 /*
304 * Reject non-ASCII advice stash names, since advice stashes are visible
305 * across all databases and the encodings of those databases might differ.
306 */
310 errmsg("advice stash name must not contain non-ASCII characters"));
311
312 /*
313 * Reject things that do not look like identifiers, since the ability to
314 * create an advice stash with non-printable characters or weird symbols
315 * in the name is not likely to be useful to anyone.
316 */
320 errmsg("advice stash name must begin with a letter or underscore and contain only letters, digits, and underscores"));
321}
322
323/*
324 * As above, but for the GUC check_hook. We allow the empty string here,
325 * though, as equivalent to disabling the feature.
326 */
327static bool
329{
330 char *stash_name = *newval;
331
332 /* Reject overlong advice stash names. */
333 if (strlen(stash_name) + 1 > NAMEDATALEN)
334 {
336 GUC_check_errdetail("advice stash names may not be longer than %d bytes",
337 NAMEDATALEN - 1);
338 return false;
339 }
340
341 /*
342 * Reject non-ASCII advice stash names, since advice stashes are visible
343 * across all databases and the encodings of those databases might differ.
344 */
346 {
348 GUC_check_errdetail("advice stash name must not contain non-ASCII characters");
349 return false;
350 }
351
352 /*
353 * Reject things that do not look like identifiers, since the ability to
354 * create an advice stash with non-printable characters or weird symbols
355 * in the name is not likely to be useful to anyone.
356 */
358 {
360 GUC_check_errdetail("advice stash name must begin with a letter or underscore and contain only letters, digits, and underscores");
361 return false;
362 }
363
364 return true;
365}
366
367/*
368 * Create an advice stash.
369 */
370void
372{
374 bool found;
375
377
378 /* Create a stash with this name, unless one already exists. */
380 if (found)
383 errmsg("advice stash \"%s\" already exists", stash_name));
384 stash->pgsa_stash_id = pgsa_state->next_stash_id++;
386}
387
388/*
389 * Remove any stored advice string for the given advice stash and query ID.
390 */
391void
393{
394 pgsa_entry *entry;
395 pgsa_entry_key key;
398
400
401 /* Translate the stash name to an integer ID. */
405 errmsg("advice stash \"%s\" does not exist", stash_name));
406
407 /*
408 * Look for an existing entry, and free it. But, be sure to save the
409 * pointer to the associated advice string, if any.
410 */
411 memset(&key, 0, sizeof(pgsa_entry_key));
412 key.pgsa_stash_id = stash_id;
413 key.queryId = queryId;
414 entry = dshash_find(pgsa_entry_dshash, &key, true);
415 if (entry == NULL)
417 else
418 {
419 old_dp = entry->advice_string;
421 }
422
423 /* Now we free the advice string as well, if there was one. */
426}
427
428/*
429 * Drop an advice stash.
430 */
431void
433{
434 pgsa_entry *entry;
438
440
441 /* Remove the entry for this advice stash. */
443 if (stash == NULL)
446 errmsg("advice stash \"%s\" does not exist", stash_name));
447 stash_id = stash->pgsa_stash_id;
449
450 /*
451 * Now remove all the entries. Since pgsa_state->lock must be held at
452 * least in shared mode to insert entries into pgsa_entry_dshash, it
453 * doesn't matter whether we do this before or after deleting the entry
454 * from pgsa_stash_dshash.
455 */
457 while ((entry = dshash_seq_next(&iterator)) != NULL)
458 {
459 if (stash_id == entry->key.pgsa_stash_id)
460 {
461 if (entry->advice_string != InvalidDsaPointer)
464 }
465 }
467}
468
469/*
470 * Initialize shared state when first created.
471 */
472static void
474{
476
477 LWLockInitialize(&state->lock,
478 LWLockNewTrancheId("pg_stash_advice_lock"));
479 state->dsa_tranche = LWLockNewTrancheId("pg_stash_advice_dsa");
480 state->stash_tranche = LWLockNewTrancheId("pg_stash_advice_stash");
481 state->entry_tranche = LWLockNewTrancheId("pg_stash_advice_entry");
482 state->next_stash_id = UINT64CONST(1);
484 state->stash_hash = DSHASH_HANDLE_INVALID;
485 state->entry_hash = DSHASH_HANDLE_INVALID;
486}
487
488/*
489 * Check whether a string looks like a valid identifier. It must contain only
490 * ASCII identifier characters, and must not begin with a digit.
491 */
492static bool
494{
495 if (*str >= '0' && *str <= '9')
496 return false;
497
498 while (*str != '\0')
499 {
500 char c = *str++;
501
502 if ((c < '0' || c > '9') && (c < 'a' || c > 'z') &&
503 (c < 'A' || c > 'Z') && c != '_')
504 return false;
505 }
506
507 return true;
508}
509
510/*
511 * Look up the integer ID that corresponds to the given stash name.
512 *
513 * Returns 0 if no such stash exists.
514 */
515uint64
517{
520
521 /* Search the shared hash table. */
523 if (stash == NULL)
524 return 0;
525 stash_id = stash->pgsa_stash_id;
527
528 return stash_id;
529}
530
531/*
532 * Store a new or updated advice string for the given advice stash and query ID.
533 */
534void
535pgsa_set_advice_string(char *stash_name, int64 queryId, char *advice_string)
536{
537 pgsa_entry *entry;
538 bool found;
539 pgsa_entry_key key;
543
544 /*
545 * The caller must hold our lock, at least in shared mode. This is
546 * important for two reasons.
547 *
548 * First, it holds off interrupts, so that we can't bail out of this code
549 * after allocating DSA memory for the advice string and before storing
550 * the resulting pointer somewhere that others can find it.
551 *
552 * Second, we need to avoid a race against pgsa_drop_stash(). That
553 * function removes a stash_name->stash_id mapping and all the entries for
554 * that stash_id. Without the lock, there's a race condition no matter
555 * which of those things it does first, because as soon as we've looked up
556 * the stash ID, that whole function can execute before we do the rest of
557 * our work, which would result in us adding an entry for a stash that no
558 * longer exists.
559 */
561
562 /* Look up the stash ID. */
566 errmsg("advice stash \"%s\" does not exist", stash_name));
567
568 /* Allocate space for the advice string. */
569 new_dp = dsa_allocate(pgsa_dsa_area, strlen(advice_string) + 1);
570 strcpy(dsa_get_address(pgsa_dsa_area, new_dp), advice_string);
571
572 /* Attempt to insert an entry into the hash table. */
573 memset(&key, 0, sizeof(pgsa_entry_key));
574 key.pgsa_stash_id = stash_id;
575 key.queryId = queryId;
578
579 /*
580 * If it didn't work, bail out, being careful to free the shared memory
581 * we've already allocated before, since error cleanup will not do so.
582 */
583 if (entry == NULL)
584 {
588 errmsg("out of memory"),
589 errdetail("could not insert advice string into shared hash table"));
590 }
591
592 /* Update the entry and release the lock. */
593 old_dp = found ? entry->advice_string : InvalidDsaPointer;
594 entry->advice_string = new_dp;
596
597 /*
598 * We're not safe from leaks yet!
599 *
600 * There's now a pointer to new_dp in the entry that we just updated, but
601 * that means that there's no longer anything pointing to old_dp.
602 */
605}
#define Assert(condition)
Definition c.h:943
int64_t int64
Definition c.h:621
uint64_t uint64
Definition c.h:625
#define unlikely(x)
Definition c.h:438
#define UINT64CONST(x)
Definition c.h:631
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
Definition dfmgr.c:95
dsa_area * dsa_attach(dsa_handle handle)
Definition dsa.c:510
void * dsa_get_address(dsa_area *area, dsa_pointer dp)
Definition dsa.c:957
void dsa_pin_mapping(dsa_area *area)
Definition dsa.c:650
dsa_handle dsa_get_handle(dsa_area *area)
Definition dsa.c:498
void dsa_free(dsa_area *area, dsa_pointer dp)
Definition dsa.c:841
void dsa_pin(dsa_area *area)
Definition dsa.c:990
uint64 dsa_pointer
Definition dsa.h:62
#define dsa_create(tranche_id)
Definition dsa.h:117
#define dsa_allocate(area, size)
Definition dsa.h:109
dsm_handle dsa_handle
Definition dsa.h:136
#define InvalidDsaPointer
Definition dsa.h:78
#define DSA_HANDLE_INVALID
Definition dsa.h:139
#define DsaPointerIsValid(x)
Definition dsa.h:106
void dshash_memcpy(void *dest, const void *src, size_t size, void *arg)
Definition dshash.c:611
void dshash_delete_entry(dshash_table *hash_table, void *entry)
Definition dshash.c:562
void dshash_strcpy(void *dest, const void *src, size_t size, void *arg)
Definition dshash.c:643
void dshash_release_lock(dshash_table *hash_table, void *entry)
Definition dshash.c:579
void dshash_seq_init(dshash_seq_status *status, dshash_table *hash_table, bool exclusive)
Definition dshash.c:659
void * dshash_find(dshash_table *hash_table, const void *key, bool exclusive)
Definition dshash.c:394
dshash_hash dshash_strhash(const void *v, size_t size, void *arg)
Definition dshash.c:632
dshash_table_handle dshash_get_hash_table_handle(dshash_table *hash_table)
Definition dshash.c:371
dshash_table * dshash_attach(dsa_area *area, const dshash_parameters *params, dshash_table_handle handle, void *arg)
Definition dshash.c:274
void dshash_seq_term(dshash_seq_status *status)
Definition dshash.c:768
int dshash_strcmp(const void *a, const void *b, size_t size, void *arg)
Definition dshash.c:620
dshash_hash dshash_memhash(const void *v, size_t size, void *arg)
Definition dshash.c:602
void * dshash_seq_next(dshash_seq_status *status)
Definition dshash.c:678
dshash_table * dshash_create(dsa_area *area, const dshash_parameters *params, void *arg)
Definition dshash.c:210
int dshash_memcmp(const void *a, const void *b, size_t size, void *arg)
Definition dshash.c:593
void * dshash_find_or_insert_extended(dshash_table *hash_table, const void *key, bool *found, int flags)
Definition dshash.c:442
void dshash_delete_current(dshash_seq_status *status)
Definition dshash.c:778
#define DSHASH_HANDLE_INVALID
Definition dshash.h:27
dsa_pointer dshash_table_handle
Definition dshash.h:24
#define DSHASH_INSERT_NO_OOM
Definition dshash.h:96
#define dshash_find_or_insert(hash_table, key, found)
Definition dshash.h:109
void * GetNamedDSMSegment(const char *name, size_t size, void(*init_callback)(void *ptr, void *arg), bool *found, void *arg)
Datum arg
Definition elog.c:1322
int errcode(int sqlerrcode)
Definition elog.c:874
int errdetail(const char *fmt,...) pg_attribute_printf(1
#define DEBUG2
Definition elog.h:29
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:227
#define ereport(elevel,...)
Definition elog.h:151
void GUC_check_errcode(int sqlerrcode)
Definition guc.c:6660
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:5123
#define newval
void MarkGUCPrefixReserved(const char *className)
Definition guc.c:5180
#define GUC_check_errdetail
Definition guc.h:507
GucSource
Definition guc.h:112
@ PGC_USERSET
Definition guc.h:79
const char * str
void parse(int)
Definition parse.c:49
bool LWLockHeldByMe(LWLock *lock)
Definition lwlock.c:1885
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition lwlock.c:1150
int LWLockNewTrancheId(const char *name)
Definition lwlock.c:562
bool LWLockHeldByMeInMode(LWLock *lock, LWLockMode mode)
Definition lwlock.c:1929
void LWLockRelease(LWLock *lock)
Definition lwlock.c:1767
void LWLockInitialize(LWLock *lock, int tranche_id)
Definition lwlock.c:670
@ LWTRANCHE_INVALID
Definition lwlock.h:164
@ LW_EXCLUSIVE
Definition lwlock.h:104
char * pstrdup(const char *in)
Definition mcxt.c:1781
MemoryContext TopMemoryContext
Definition mcxt.c:166
#define AllocSetContextCreate
Definition memutils.h:129
#define ALLOCSET_DEFAULT_SIZES
Definition memutils.h:160
static char * errmsg
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:124
#define NAMEDATALEN
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
void _PG_init(void)
static char * pgsa_advisor(PlannerGlobal *glob, Query *parse, const char *query_string, int cursorOptions, ExplainState *es)
dshash_table * pgsa_entry_dshash
dshash_table * pgsa_stash_dshash
static MemoryContext pg_stash_advice_mcxt
PG_MODULE_MAGIC
void pgsa_create_stash(char *stash_name)
void pgsa_drop_stash(char *stash_name)
void pgsa_set_advice_string(char *stash_name, int64 queryId, char *advice_string)
static bool pgsa_is_identifier(char *str)
dsa_area * pgsa_dsa_area
uint64 pgsa_lookup_stash_id(char *stash_name)
static dshash_parameters pgsa_stash_dshash_parameters
static char * pg_stash_advice_stash_name
static bool pgsa_check_stash_name_guc(char **newval, void **extra, GucSource source)
void pgsa_attach(void)
void pgsa_check_stash_name(char *stash_name)
pgsa_shared_state * pgsa_state
static dshash_parameters pgsa_entry_dshash_parameters
static void pgsa_init_shared_state(void *ptr, void *arg)
void pgsa_clear_advice_string(char *stash_name, int64 queryId)
char * c
static int fb(int x)
void EnableQueryId(void)
bool pg_is_ascii(const char *str)
Definition string.c:132
uint64 pgsa_stash_id
pgsa_entry_key key
dsa_pointer advice_string
dshash_table_handle entry_hash
dshash_table_handle stash_hash