Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * stashfuncs.c
4 : : * SQL interface to pg_stash_advice
5 : : *
6 : : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 : : *
8 : : * contrib/pg_stash_advice/stashfuncs.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : : #include "postgres.h"
13 : :
14 : : #include "common/hashfn.h"
15 : : #include "fmgr.h"
16 : : #include "funcapi.h"
17 : : #include "miscadmin.h"
18 : : #include "pg_stash_advice.h"
19 : : #include "utils/builtins.h"
20 : : #include "utils/tuplestore.h"
21 : :
29 rhaas@postgresql.org 22 :GNC 6 : PG_FUNCTION_INFO_V1(pg_create_advice_stash);
23 : 4 : PG_FUNCTION_INFO_V1(pg_drop_advice_stash);
24 : 4 : PG_FUNCTION_INFO_V1(pg_get_advice_stash_contents);
25 : 7 : PG_FUNCTION_INFO_V1(pg_get_advice_stashes);
26 : 4 : PG_FUNCTION_INFO_V1(pg_set_stashed_advice);
28 27 : 2 : PG_FUNCTION_INFO_V1(pg_start_stash_advice_worker);
28 : :
29 : : typedef struct pgsa_stash_count
30 : : {
31 : : uint32 status;
32 : : uint64 pgsa_stash_id;
33 : : int64 num_entries;
34 : : } pgsa_stash_count;
35 : :
36 : : #define SH_PREFIX pgsa_stash_count_table
37 : : #define SH_ELEMENT_TYPE pgsa_stash_count
38 : : #define SH_KEY_TYPE uint64
39 : : #define SH_KEY pgsa_stash_id
40 : : #define SH_HASH_KEY(tb, key) hash_bytes((const unsigned char *) &(key), sizeof(uint64))
41 : : #define SH_EQUAL(tb, a, b) (a == b)
42 : : #define SH_SCOPE static inline
43 : : #define SH_DEFINE
44 : : #define SH_DECLARE
45 : : #include "lib/simplehash.h"
46 : :
47 : : /*
48 : : * SQL-callable function to create an advice stash
49 : : */
50 : : Datum
29 51 : 10 : pg_create_advice_stash(PG_FUNCTION_ARGS)
52 : : {
53 : 10 : char *stash_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
54 : :
55 : 10 : pgsa_check_stash_name(stash_name);
56 [ + + ]: 6 : if (unlikely(pgsa_entry_dshash == NULL))
57 : 3 : pgsa_attach();
28 58 : 6 : pgsa_check_lockout();
29 59 : 6 : LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
60 : 6 : pgsa_create_stash(stash_name);
61 : 5 : LWLockRelease(&pgsa_state->lock);
62 : 5 : PG_RETURN_VOID();
63 : : }
64 : :
65 : : /*
66 : : * SQL-callable function to drop an advice stash
67 : : */
68 : : Datum
69 : 6 : pg_drop_advice_stash(PG_FUNCTION_ARGS)
70 : : {
71 : 6 : char *stash_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
72 : :
73 : 6 : pgsa_check_stash_name(stash_name);
74 [ + + ]: 6 : if (unlikely(pgsa_entry_dshash == NULL))
75 : 1 : pgsa_attach();
28 76 : 6 : pgsa_check_lockout();
29 77 : 6 : LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
78 : 6 : pgsa_drop_stash(stash_name);
79 : 5 : LWLockRelease(&pgsa_state->lock);
80 : 5 : PG_RETURN_VOID();
81 : : }
82 : :
83 : : /*
84 : : * SQL-callable function to provide a list of advice stashes
85 : : */
86 : : Datum
87 : 6 : pg_get_advice_stashes(PG_FUNCTION_ARGS)
88 : : {
89 : 6 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
90 : : dshash_seq_status iterator;
91 : : pgsa_entry *entry;
92 : : pgsa_stash *stash;
93 : : pgsa_stash_count_table_hash *chash;
94 : :
95 : 6 : InitMaterializedSRF(fcinfo, 0);
96 : :
97 : : /* Attach to dynamic shared memory if not already done. */
98 [ + + ]: 6 : if (unlikely(pgsa_entry_dshash == NULL))
99 : 4 : pgsa_attach();
100 : :
101 : : /* If stash data is still being restored from disk, ignore. */
28 102 [ - + ]: 6 : if (pg_atomic_unlocked_test_flag(&pgsa_state->stashes_ready))
28 rhaas@postgresql.org 103 :UNC 0 : return (Datum) 0;
104 : :
105 : : /* Tally up the number of entries per stash. */
29 rhaas@postgresql.org 106 :GNC 6 : chash = pgsa_stash_count_table_create(CurrentMemoryContext, 64, NULL);
107 : 6 : dshash_seq_init(&iterator, pgsa_entry_dshash, true);
108 [ + + ]: 18 : while ((entry = dshash_seq_next(&iterator)) != NULL)
109 : : {
110 : : pgsa_stash_count *c;
111 : : bool found;
112 : :
113 : 12 : c = pgsa_stash_count_table_insert(chash,
114 : : entry->key.pgsa_stash_id,
115 : : &found);
116 [ + + ]: 12 : if (!found)
117 : 8 : c->num_entries = 1;
118 : : else
119 : 4 : c->num_entries++;
120 : : }
121 : 6 : dshash_seq_term(&iterator);
122 : :
123 : : /* Emit results. */
124 : 6 : dshash_seq_init(&iterator, pgsa_stash_dshash, true);
125 [ + + ]: 17 : while ((stash = dshash_seq_next(&iterator)) != NULL)
126 : : {
127 : : Datum values[2];
128 : : bool nulls[2];
129 : : pgsa_stash_count *c;
130 : :
131 : 11 : values[0] = CStringGetTextDatum(stash->name);
132 : 11 : nulls[0] = false;
133 : :
134 : 11 : c = pgsa_stash_count_table_lookup(chash, stash->pgsa_stash_id);
135 [ + + ]: 11 : values[1] = Int64GetDatum(c == NULL ? 0 : c->num_entries);
136 : 11 : nulls[1] = false;
137 : :
138 : 11 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values,
139 : : nulls);
140 : : }
141 : 6 : dshash_seq_term(&iterator);
142 : :
143 : 6 : return (Datum) 0;
144 : : }
145 : :
146 : : /*
147 : : * SQL-callable function to provide advice stash contents
148 : : */
149 : : Datum
150 : 6 : pg_get_advice_stash_contents(PG_FUNCTION_ARGS)
151 : : {
152 : 6 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
153 : : dshash_seq_status iterator;
154 : 6 : char *stash_name = NULL;
155 : 6 : pgsa_stash_name_table_hash *nhash = NULL;
156 : 6 : uint64 stash_id = 0;
157 : : pgsa_entry *entry;
158 : :
159 : 6 : InitMaterializedSRF(fcinfo, 0);
160 : :
161 : : /* Attach to dynamic shared memory if not already done. */
162 [ + + ]: 6 : if (unlikely(pgsa_entry_dshash == NULL))
163 : 1 : pgsa_attach();
164 : :
165 : : /* If stash data is still being restored from disk, ignore. */
28 166 [ - + ]: 6 : if (pg_atomic_unlocked_test_flag(&pgsa_state->stashes_ready))
28 rhaas@postgresql.org 167 :UNC 0 : return (Datum) 0;
168 : :
169 : : /* User can pass NULL for all stashes, or the name of a specific stash. */
29 rhaas@postgresql.org 170 [ + + ]:GNC 6 : if (!PG_ARGISNULL(0))
171 : : {
172 : 4 : stash_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
173 : 4 : pgsa_check_stash_name(stash_name);
174 : 4 : stash_id = pgsa_lookup_stash_id(stash_name);
175 : :
176 : : /* If the user specified a stash name, it should exist. */
177 [ + + ]: 4 : if (stash_id == 0)
178 [ + - ]: 1 : ereport(ERROR,
179 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
180 : : errmsg("advice stash \"%s\" does not exist", stash_name));
181 : : }
182 : : else
183 : : {
184 : : pgsa_stash *stash;
185 : :
186 : : /*
187 : : * If we're dumping data about all stashes, we need an ID->name lookup
188 : : * table.
189 : : */
190 : 2 : nhash = pgsa_stash_name_table_create(CurrentMemoryContext, 64, NULL);
191 : 2 : dshash_seq_init(&iterator, pgsa_stash_dshash, true);
192 [ + + ]: 6 : while ((stash = dshash_seq_next(&iterator)) != NULL)
193 : : {
194 : : pgsa_stash_name *n;
195 : : bool found;
196 : :
197 : 4 : n = pgsa_stash_name_table_insert(nhash,
198 : : stash->pgsa_stash_id,
199 : : &found);
200 [ - + ]: 4 : Assert(!found);
201 : 4 : n->name = pstrdup(stash->name);
202 : : }
203 : 2 : dshash_seq_term(&iterator);
204 : : }
205 : :
206 : : /* Now iterate over all the entries. */
207 : 5 : dshash_seq_init(&iterator, pgsa_entry_dshash, false);
208 [ + + ]: 15 : while ((entry = dshash_seq_next(&iterator)) != NULL)
209 : : {
210 : : Datum values[3];
211 : : bool nulls[3];
212 : : char *this_stash_name;
213 : : char *advice_string;
214 : :
215 : : /* Skip incomplete entries where the advice string was never set. */
216 [ - + ]: 10 : if (entry->advice_string == InvalidDsaPointer)
217 : 2 : continue;
218 : :
219 [ + + ]: 10 : if (stash_id != 0)
220 : : {
221 : : /*
222 : : * We're only dumping data for one particular stash, so skip
223 : : * entries for any other stash and use the stash name specified by
224 : : * the user.
225 : : */
226 [ + + ]: 5 : if (stash_id != entry->key.pgsa_stash_id)
227 : 2 : continue;
228 : 3 : this_stash_name = stash_name;
229 : : }
230 : : else
231 : : {
232 : : pgsa_stash_name *n;
233 : :
234 : : /*
235 : : * We're dumping data for all stashes, so look up the correct name
236 : : * to use in the hash table. If nothing is found, which is
237 : : * possible due to race conditions, make up a string to use.
238 : : */
239 : 5 : n = pgsa_stash_name_table_lookup(nhash, entry->key.pgsa_stash_id);
240 [ + - ]: 5 : if (n != NULL)
241 : 5 : this_stash_name = n->name;
242 : : else
29 rhaas@postgresql.org 243 :UNC 0 : this_stash_name = psprintf("<stash %" PRIu64 ">",
244 : : entry->key.pgsa_stash_id);
245 : : }
246 : :
247 : : /* Work out tuple values. */
29 rhaas@postgresql.org 248 :GNC 8 : values[0] = CStringGetTextDatum(this_stash_name);
249 : 8 : nulls[0] = false;
250 : 8 : values[1] = Int64GetDatum(entry->key.queryId);
251 : 8 : nulls[1] = false;
252 : 8 : advice_string = dsa_get_address(pgsa_dsa_area, entry->advice_string);
253 : 8 : values[2] = CStringGetTextDatum(advice_string);
254 : 8 : nulls[2] = false;
255 : :
256 : : /* Emit the tuple. */
257 : 8 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values,
258 : : nulls);
259 : : }
260 : 5 : dshash_seq_term(&iterator);
261 : :
262 : 5 : return (Datum) 0;
263 : : }
264 : :
265 : : /*
266 : : * SQL-callable function to update an advice stash entry for a particular
267 : : * query ID
268 : : *
269 : : * If the second argument is NULL, we delete any existing advice stash
270 : : * entry; otherwise, we either create an entry or update it with the new
271 : : * advice string.
272 : : */
273 : : Datum
274 : 11 : pg_set_stashed_advice(PG_FUNCTION_ARGS)
275 : : {
276 : : char *stash_name;
277 : : int64 queryId;
278 : :
279 [ + - - + ]: 11 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
29 rhaas@postgresql.org 280 :UNC 0 : PG_RETURN_NULL();
281 : :
282 : : /* Get and check advice stash name. */
29 rhaas@postgresql.org 283 :GNC 11 : stash_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
284 : 11 : pgsa_check_stash_name(stash_name);
285 : :
286 : : /*
287 : : * Get and check query ID.
288 : : *
289 : : * Query ID 0 means no query ID was computed, so reject that.
290 : : */
291 : 11 : queryId = PG_GETARG_INT64(1);
292 [ + + ]: 11 : if (queryId == 0)
293 [ + - ]: 1 : ereport(ERROR,
294 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
295 : : errmsg("cannot set advice string for query ID 0"));
296 : :
297 : : /* Attach to dynamic shared memory if not already done. */
298 [ - + ]: 10 : if (unlikely(pgsa_entry_dshash == NULL))
29 rhaas@postgresql.org 299 :UNC 0 : pgsa_attach();
300 : :
301 : : /* Don't allow writes if stash data is still being restored from disk. */
28 rhaas@postgresql.org 302 :GNC 10 : pgsa_check_lockout();
303 : :
304 : : /* Now call the appropriate function to do the real work. */
29 305 [ + + ]: 10 : if (PG_ARGISNULL(2))
306 : : {
307 : 2 : LWLockAcquire(&pgsa_state->lock, LW_SHARED);
308 : 2 : pgsa_clear_advice_string(stash_name, queryId);
309 : 1 : LWLockRelease(&pgsa_state->lock);
310 : : }
311 : : else
312 : : {
313 : 8 : char *advice_string = text_to_cstring(PG_GETARG_TEXT_PP(2));
314 : :
315 : 8 : LWLockAcquire(&pgsa_state->lock, LW_SHARED);
316 : 8 : pgsa_set_advice_string(stash_name, queryId, advice_string);
317 : 7 : LWLockRelease(&pgsa_state->lock);
318 : : }
319 : :
320 : 8 : PG_RETURN_VOID();
321 : : }
322 : :
323 : : /*
324 : : * SQL-callable function to start the persistence background worker.
325 : : */
326 : : Datum
28 rhaas@postgresql.org 327 :UNC 0 : pg_start_stash_advice_worker(PG_FUNCTION_ARGS)
328 : : {
329 : : pid_t pid;
330 : :
331 [ # # ]: 0 : if (unlikely(pgsa_entry_dshash == NULL))
332 : 0 : pgsa_attach();
333 : :
334 : 0 : LWLockAcquire(&pgsa_state->lock, LW_SHARED);
335 : 0 : pid = pgsa_state->bgworker_pid;
336 : 0 : LWLockRelease(&pgsa_state->lock);
337 : :
338 [ # # ]: 0 : if (pid != InvalidPid)
339 [ # # ]: 0 : ereport(ERROR,
340 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
341 : : errmsg("pg_stash_advice worker is already running under PID %d",
342 : : (int) pid)));
343 : :
344 : 0 : pgsa_start_worker();
345 : :
346 : 0 : PG_RETURN_VOID();
347 : : }
|