Line data Source code
1 : /*------------------------------------------------------------------------------------
2 : *
3 : * test_custom_var_stats.c
4 : * Test module for variable-sized custom pgstats
5 : *
6 : * Copyright (c) 2025-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/test/modules/test_custom_var_stats/test_custom_var_stats.c
10 : *
11 : * ------------------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "access/htup_details.h"
16 : #include "common/hashfn.h"
17 : #include "funcapi.h"
18 : #include "storage/dsm_registry.h"
19 : #include "storage/fd.h"
20 : #include "utils/builtins.h"
21 : #include "utils/pgstat_internal.h"
22 :
23 3 : PG_MODULE_MAGIC_EXT(
24 : .name = "test_custom_var_stats",
25 : .version = PG_VERSION
26 : );
27 :
28 : #define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
29 :
30 : /*--------------------------------------------------------------------------
31 : * Macros and constants
32 : *--------------------------------------------------------------------------
33 : */
34 :
35 : /*
36 : * Kind ID for test_custom_var_stats statistics.
37 : */
38 : #define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
39 :
40 : /* File paths for auxiliary data serialization */
41 : #define TEST_CUSTOM_AUX_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats"
42 :
43 : /*
44 : * Hash statistic name to generate entry index for pgstat lookup.
45 : */
46 : #define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
47 :
48 : /*--------------------------------------------------------------------------
49 : * Type definitions
50 : *--------------------------------------------------------------------------
51 : */
52 :
53 : /* Backend-local pending statistics before flush to shared memory */
54 : typedef struct PgStat_StatCustomVarEntry
55 : {
56 : PgStat_Counter numcalls; /* times statistic was incremented */
57 : } PgStat_StatCustomVarEntry;
58 :
59 : /* Shared memory statistics entry visible to all backends */
60 : typedef struct PgStatShared_CustomVarEntry
61 : {
62 : PgStatShared_Common header; /* standard pgstat entry header */
63 : PgStat_StatCustomVarEntry stats; /* custom statistics data */
64 : dsa_pointer description; /* pointer to description string in DSA */
65 : } PgStatShared_CustomVarEntry;
66 :
67 : /*--------------------------------------------------------------------------
68 : * Global Variables
69 : *--------------------------------------------------------------------------
70 : */
71 :
72 : /* File handle for auxiliary data serialization */
73 : static FILE *fd_description = NULL;
74 :
75 : /* Current write offset in fd_description file */
76 : static pgoff_t fd_description_offset = 0;
77 :
78 : /* DSA area for storing variable-length description strings */
79 : static dsa_area *custom_stats_description_dsa = NULL;
80 :
81 : /*--------------------------------------------------------------------------
82 : * Function prototypes
83 : *--------------------------------------------------------------------------
84 : */
85 :
86 : /* Flush callback: merge pending stats into shared memory */
87 : static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref,
88 : bool nowait);
89 :
90 : /* Serialization callback: write auxiliary entry data */
91 : static void test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
92 : const PgStatShared_Common *header,
93 : FILE *statfile);
94 :
95 : /* Deserialization callback: read auxiliary entry data */
96 : static bool test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
97 : PgStatShared_Common *header,
98 : FILE *statfile);
99 :
100 : /* Finish callback: end of statistics file operations */
101 : static void test_custom_stats_var_finish(PgStat_StatsFileOp status);
102 :
103 : /*--------------------------------------------------------------------------
104 : * Custom kind configuration
105 : *--------------------------------------------------------------------------
106 : */
107 :
108 : static const PgStat_KindInfo custom_stats = {
109 : .name = "test_custom_var_stats",
110 : .fixed_amount = false, /* variable number of entries */
111 : .write_to_file = true, /* persist across restarts */
112 : .track_entry_count = true, /* count active entries */
113 : .accessed_across_databases = true, /* global statistics */
114 : .shared_size = sizeof(PgStatShared_CustomVarEntry),
115 : .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
116 : .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
117 : .pending_size = sizeof(PgStat_StatCustomVarEntry),
118 : .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
119 : .to_serialized_data = test_custom_stats_var_to_serialized_data,
120 : .from_serialized_data = test_custom_stats_var_from_serialized_data,
121 : .finish = test_custom_stats_var_finish,
122 : };
123 :
124 : /*--------------------------------------------------------------------------
125 : * Module initialization
126 : *--------------------------------------------------------------------------
127 : */
128 :
129 : void
130 3 : _PG_init(void)
131 : {
132 : /* Must be loaded via shared_preload_libraries */
133 3 : if (!process_shared_preload_libraries_in_progress)
134 0 : return;
135 :
136 : /* Register custom statistics kind */
137 3 : pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, &custom_stats);
138 : }
139 :
140 : /*--------------------------------------------------------------------------
141 : * Statistics callback functions
142 : *--------------------------------------------------------------------------
143 : */
144 :
145 : /*
146 : * test_custom_stats_var_flush_pending_cb
147 : * Merge pending backend statistics into shared memory
148 : *
149 : * Called by pgstat collector to flush accumulated local statistics
150 : * to shared memory where other backends can read them.
151 : *
152 : * Returns false only if nowait=true and lock acquisition fails.
153 : */
154 : static bool
155 10 : test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
156 : {
157 : PgStat_StatCustomVarEntry *pending_entry;
158 : PgStatShared_CustomVarEntry *shared_entry;
159 :
160 10 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
161 10 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
162 :
163 10 : if (!pgstat_lock_entry(entry_ref, nowait))
164 0 : return false;
165 :
166 : /* Add pending counts to shared totals */
167 10 : shared_entry->stats.numcalls += pending_entry->numcalls;
168 :
169 10 : pgstat_unlock_entry(entry_ref);
170 :
171 10 : return true;
172 : }
173 :
174 : /*
175 : * test_custom_stats_var_to_serialized_data() -
176 : *
177 : * Serialize auxiliary data (descriptions) for custom statistics entries
178 : * to a secondary statistics file. This is called while writing the statistics
179 : * to disk.
180 : *
181 : * This callback writes a mix of data within the main pgstats file and a
182 : * secondary statistics file. The following data is written to the main file for
183 : * each entry:
184 : * - An arbitrary magic number.
185 : * - An offset. This is used to know the location we need to look at
186 : * to retrieve the information from the second file.
187 : *
188 : * The following data is written to the secondary statistics file:
189 : * - The entry key, cross-checked with the data from the main file
190 : * when reloaded.
191 : * - The length of the description.
192 : * - The description data itself.
193 : */
194 : static void
195 2 : test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
196 : const PgStatShared_Common *header,
197 : FILE *statfile)
198 : {
199 : char *description;
200 : size_t len;
201 2 : const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
202 : bool found;
203 2 : uint32 magic_number = TEST_CUSTOM_VAR_MAGIC_NUMBER;
204 :
205 : /*
206 : * First mark the main file with a magic number, keeping a trace that some
207 : * auxiliary data will exist in the secondary statistics file.
208 : */
209 2 : pgstat_write_chunk_s(statfile, &magic_number);
210 :
211 : /* Open statistics file for writing. */
212 2 : if (!fd_description)
213 : {
214 1 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_W);
215 1 : if (fd_description == NULL)
216 : {
217 0 : ereport(LOG,
218 : (errcode_for_file_access(),
219 : errmsg("could not open statistics file \"%s\" for writing: %m",
220 : TEST_CUSTOM_AUX_DATA_DESC)));
221 0 : return;
222 : }
223 :
224 : /* Initialize offset for secondary statistics file. */
225 1 : fd_description_offset = 0;
226 : }
227 :
228 : /* Write offset to the main data file */
229 2 : pgstat_write_chunk_s(statfile, &fd_description_offset);
230 :
231 : /*
232 : * First write the entry key to the secondary statistics file. This will
233 : * be cross-checked with the key read from main stats file at loading
234 : * time.
235 : */
236 2 : pgstat_write_chunk_s(fd_description, (PgStat_HashKey *) key);
237 2 : fd_description_offset += sizeof(PgStat_HashKey);
238 :
239 2 : if (!custom_stats_description_dsa)
240 1 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
241 :
242 : /* Handle entries without descriptions */
243 2 : if (!DsaPointerIsValid(entry->description) || !custom_stats_description_dsa)
244 : {
245 : /* length to description file */
246 0 : len = 0;
247 0 : pgstat_write_chunk_s(fd_description, &len);
248 0 : fd_description_offset += sizeof(size_t);
249 0 : return;
250 : }
251 :
252 : /*
253 : * Retrieve description from DSA, then write the length followed by the
254 : * description.
255 : */
256 2 : description = dsa_get_address(custom_stats_description_dsa,
257 2 : entry->description);
258 2 : len = strlen(description) + 1;
259 2 : pgstat_write_chunk_s(fd_description, &len);
260 2 : pgstat_write_chunk(fd_description, description, len);
261 :
262 : /*
263 : * Update offset for next entry, counting for the length (size_t) of the
264 : * description and the description contents.
265 : */
266 2 : fd_description_offset += len + sizeof(size_t);
267 : }
268 :
269 : /*
270 : * test_custom_stats_var_from_serialized_data() -
271 : *
272 : * Read auxiliary data (descriptions) for custom statistics entries from
273 : * the secondary statistics file. This is called while loading the statistics
274 : * at startup.
275 : *
276 : * See the top of test_custom_stats_var_to_serialized_data() for a
277 : * detailed description of the data layout read here.
278 : */
279 : static bool
280 2 : test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
281 : PgStatShared_Common *header,
282 : FILE *statfile)
283 : {
284 : PgStatShared_CustomVarEntry *entry;
285 : dsa_pointer dp;
286 : size_t len;
287 : pgoff_t offset;
288 : char *buffer;
289 : bool found;
290 2 : uint32 magic_number = 0;
291 : PgStat_HashKey file_key;
292 :
293 : /* Check the magic number first, in the main file. */
294 2 : if (!pgstat_read_chunk_s(statfile, &magic_number))
295 : {
296 0 : elog(WARNING, "failed to read magic number from statistics file");
297 0 : return false;
298 : }
299 :
300 2 : if (magic_number != TEST_CUSTOM_VAR_MAGIC_NUMBER)
301 : {
302 0 : elog(WARNING, "found magic number %u from statistics file, should be %u",
303 : magic_number, TEST_CUSTOM_VAR_MAGIC_NUMBER);
304 0 : return false;
305 : }
306 :
307 : /*
308 : * Read the offset from the main stats file, to be able to read the
309 : * auxiliary data from the secondary statistics file.
310 : */
311 2 : if (!pgstat_read_chunk_s(statfile, &offset))
312 : {
313 0 : elog(WARNING, "failed to read metadata offset from statistics file");
314 0 : return false;
315 : }
316 :
317 : /* Open statistics file for reading if not already open */
318 2 : if (!fd_description)
319 : {
320 1 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_R);
321 1 : if (fd_description == NULL)
322 : {
323 0 : if (errno != ENOENT)
324 0 : ereport(LOG,
325 : (errcode_for_file_access(),
326 : errmsg("could not open statistics file \"%s\" for reading: %m",
327 : TEST_CUSTOM_AUX_DATA_DESC)));
328 0 : pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS);
329 0 : return false;
330 : }
331 : }
332 :
333 : /* Read data from the secondary statistics file, at the specified offset */
334 2 : if (fseeko(fd_description, offset, SEEK_SET) != 0)
335 : {
336 0 : elog(WARNING, "could not seek in file \"%s\": %m",
337 : TEST_CUSTOM_AUX_DATA_DESC);
338 0 : return false;
339 : }
340 :
341 : /* Read the hash key from the secondary statistics file */
342 2 : if (!pgstat_read_chunk_s(fd_description, &file_key))
343 : {
344 0 : elog(WARNING, "failed to read hash key from file");
345 0 : return false;
346 : }
347 :
348 : /* Check key consistency */
349 2 : if (file_key.kind != key->kind ||
350 2 : file_key.dboid != key->dboid ||
351 2 : file_key.objid != key->objid)
352 : {
353 0 : elog(WARNING, "found entry key %u/%u/%" PRIu64 " not matching with %u/%u/%" PRIu64,
354 : file_key.kind, file_key.dboid, file_key.objid,
355 : key->kind, key->dboid, key->objid);
356 0 : return false;
357 : }
358 :
359 2 : entry = (PgStatShared_CustomVarEntry *) header;
360 :
361 : /* Read the description length and its data */
362 2 : if (!pgstat_read_chunk_s(fd_description, &len))
363 : {
364 0 : elog(WARNING, "failed to read metadata length from statistics file");
365 0 : return false;
366 : }
367 :
368 : /* Handle empty descriptions */
369 2 : if (len == 0)
370 : {
371 0 : entry->description = InvalidDsaPointer;
372 0 : return true;
373 : }
374 :
375 : /* Initialize DSA if needed */
376 2 : if (!custom_stats_description_dsa)
377 1 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
378 :
379 2 : if (!custom_stats_description_dsa)
380 : {
381 0 : elog(WARNING, "could not access DSA for custom statistics descriptions");
382 0 : return false;
383 : }
384 :
385 2 : buffer = palloc(len);
386 2 : if (!pgstat_read_chunk(fd_description, buffer, len))
387 : {
388 0 : pfree(buffer);
389 0 : elog(WARNING, "failed to read description from file");
390 0 : return false;
391 : }
392 :
393 : /* Allocate space in DSA and copy the description */
394 2 : dp = dsa_allocate(custom_stats_description_dsa, len);
395 2 : memcpy(dsa_get_address(custom_stats_description_dsa, dp), buffer, len);
396 2 : entry->description = dp;
397 2 : pfree(buffer);
398 :
399 2 : return true;
400 : }
401 :
402 : /*
403 : * test_custom_stats_var_finish() -
404 : *
405 : * Cleanup function called at the end of statistics file operations.
406 : * Handles closing files and cleanup based on the operation type.
407 : */
408 : static void
409 4 : test_custom_stats_var_finish(PgStat_StatsFileOp status)
410 : {
411 4 : switch (status)
412 : {
413 1 : case STATS_WRITE:
414 1 : if (!fd_description)
415 0 : return;
416 :
417 1 : fd_description_offset = 0;
418 :
419 : /* Check for write errors and cleanup if necessary */
420 1 : if (ferror(fd_description))
421 : {
422 0 : ereport(LOG,
423 : (errcode_for_file_access(),
424 : errmsg("could not write to file \"%s\": %m",
425 : TEST_CUSTOM_AUX_DATA_DESC)));
426 0 : FreeFile(fd_description);
427 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
428 : }
429 1 : else if (FreeFile(fd_description) < 0)
430 : {
431 0 : ereport(LOG,
432 : (errcode_for_file_access(),
433 : errmsg("could not close file \"%s\": %m",
434 : TEST_CUSTOM_AUX_DATA_DESC)));
435 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
436 : }
437 1 : break;
438 :
439 2 : case STATS_READ:
440 2 : if (fd_description)
441 1 : FreeFile(fd_description);
442 :
443 : /* Remove the file after reading */
444 2 : elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
445 2 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
446 2 : break;
447 :
448 1 : case STATS_DISCARD:
449 : {
450 : int ret;
451 :
452 : /* Attempt to remove the file */
453 1 : ret = unlink(TEST_CUSTOM_AUX_DATA_DESC);
454 1 : if (ret != 0)
455 : {
456 1 : if (errno == ENOENT)
457 1 : elog(LOG,
458 : "didn't need to unlink file \"%s\" - didn't exist",
459 : TEST_CUSTOM_AUX_DATA_DESC);
460 : else
461 0 : ereport(LOG,
462 : (errcode_for_file_access(),
463 : errmsg("could not unlink file \"%s\": %m",
464 : TEST_CUSTOM_AUX_DATA_DESC)));
465 : }
466 : else
467 : {
468 0 : ereport(LOG,
469 : (errmsg_internal("unlinked file \"%s\"",
470 : TEST_CUSTOM_AUX_DATA_DESC)));
471 : }
472 : }
473 1 : break;
474 : }
475 :
476 4 : fd_description = NULL;
477 : }
478 :
479 : /*--------------------------------------------------------------------------
480 : * Helper functions
481 : *--------------------------------------------------------------------------
482 : */
483 :
484 : /*
485 : * test_custom_stats_var_fetch_entry
486 : * Look up custom statistic by name
487 : *
488 : * Returns statistics entry from shared memory, or NULL if not found.
489 : */
490 : static PgStat_StatCustomVarEntry *
491 9 : test_custom_stats_var_fetch_entry(const char *stat_name)
492 : {
493 : /* Fetch entry by hashed name */
494 9 : return (PgStat_StatCustomVarEntry *)
495 9 : pgstat_fetch_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
496 : InvalidOid,
497 9 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name));
498 : }
499 :
500 : /*--------------------------------------------------------------------------
501 : * SQL-callable functions
502 : *--------------------------------------------------------------------------
503 : */
504 :
505 : /*
506 : * test_custom_stats_var_create
507 : * Create new custom statistic entry
508 : *
509 : * Initializes a statistics entry with the given name and description.
510 : */
511 5 : PG_FUNCTION_INFO_V1(test_custom_stats_var_create);
512 : Datum
513 4 : test_custom_stats_var_create(PG_FUNCTION_ARGS)
514 : {
515 : PgStat_EntryRef *entry_ref;
516 : PgStatShared_CustomVarEntry *shared_entry;
517 4 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
518 4 : char *description = text_to_cstring(PG_GETARG_TEXT_PP(1));
519 4 : dsa_pointer dp = InvalidDsaPointer;
520 : bool found;
521 :
522 : /* Validate name length first */
523 4 : if (strlen(stat_name) >= NAMEDATALEN)
524 0 : ereport(ERROR,
525 : (errcode(ERRCODE_NAME_TOO_LONG),
526 : errmsg("custom statistic name \"%s\" is too long", stat_name),
527 : errdetail("Name must be less than %d characters.", NAMEDATALEN)));
528 :
529 : /* Initialize DSA and description provided */
530 4 : if (!custom_stats_description_dsa)
531 4 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
532 :
533 4 : if (!custom_stats_description_dsa)
534 0 : ereport(ERROR,
535 : (errmsg("could not access DSA for custom statistics descriptions")));
536 :
537 : /* Allocate space in DSA and copy description */
538 4 : dp = dsa_allocate(custom_stats_description_dsa, strlen(description) + 1);
539 8 : memcpy(dsa_get_address(custom_stats_description_dsa, dp),
540 : description,
541 4 : strlen(description) + 1);
542 :
543 : /* Create or get existing entry */
544 4 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
545 4 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true);
546 :
547 4 : if (!entry_ref)
548 0 : PG_RETURN_VOID();
549 :
550 4 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
551 :
552 : /* Zero-initialize statistics */
553 4 : memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
554 :
555 : /* Store description pointer */
556 4 : shared_entry->description = dp;
557 :
558 4 : pgstat_unlock_entry(entry_ref);
559 :
560 4 : PG_RETURN_VOID();
561 : }
562 :
563 : /*
564 : * test_custom_stats_var_update
565 : * Increment custom statistic counter
566 : *
567 : * Increments call count in backend-local memory. Changes are flushed
568 : * to shared memory by the statistics collector.
569 : */
570 11 : PG_FUNCTION_INFO_V1(test_custom_stats_var_update);
571 : Datum
572 10 : test_custom_stats_var_update(PG_FUNCTION_ARGS)
573 : {
574 10 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
575 : PgStat_EntryRef *entry_ref;
576 : PgStat_StatCustomVarEntry *pending_entry;
577 :
578 : /* Get pending entry in local memory */
579 10 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
580 10 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL);
581 :
582 10 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
583 10 : pending_entry->numcalls++;
584 :
585 10 : PG_RETURN_VOID();
586 : }
587 :
588 : /*
589 : * test_custom_stats_var_drop
590 : * Remove custom statistic entry
591 : *
592 : * Drops the named statistic from shared memory.
593 : */
594 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop);
595 : Datum
596 2 : test_custom_stats_var_drop(PG_FUNCTION_ARGS)
597 : {
598 2 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
599 :
600 : /* Drop entry and request GC if the entry could not be freed */
601 2 : if (!pgstat_drop_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
602 2 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name)))
603 0 : pgstat_request_entry_refs_gc();
604 :
605 2 : PG_RETURN_VOID();
606 : }
607 :
608 : /*
609 : * test_custom_stats_var_report
610 : * Retrieve custom statistic values
611 : *
612 : * Returns single row with statistic name, call count, and description if the
613 : * statistic exists, otherwise returns no rows.
614 : */
615 10 : PG_FUNCTION_INFO_V1(test_custom_stats_var_report);
616 : Datum
617 14 : test_custom_stats_var_report(PG_FUNCTION_ARGS)
618 : {
619 : FuncCallContext *funcctx;
620 : char *stat_name;
621 : PgStat_StatCustomVarEntry *stat_entry;
622 :
623 14 : if (SRF_IS_FIRSTCALL())
624 : {
625 : TupleDesc tupdesc;
626 : MemoryContext oldcontext;
627 :
628 : /* Initialize SRF context */
629 9 : funcctx = SRF_FIRSTCALL_INIT();
630 9 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
631 :
632 : /* Get composite return type */
633 9 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
634 0 : elog(ERROR, "test_custom_stats_var_report: return type is not composite");
635 :
636 9 : funcctx->tuple_desc = BlessTupleDesc(tupdesc);
637 9 : funcctx->max_calls = 1; /* single row result */
638 :
639 9 : MemoryContextSwitchTo(oldcontext);
640 : }
641 :
642 14 : funcctx = SRF_PERCALL_SETUP();
643 :
644 14 : if (funcctx->call_cntr < funcctx->max_calls)
645 : {
646 : Datum values[3];
647 9 : bool nulls[3] = {false, false, false};
648 : HeapTuple tuple;
649 : PgStat_EntryRef *entry_ref;
650 : PgStatShared_CustomVarEntry *shared_entry;
651 9 : char *description = NULL;
652 : bool found;
653 :
654 9 : stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
655 9 : stat_entry = test_custom_stats_var_fetch_entry(stat_name);
656 :
657 : /* Return row only if entry exists */
658 9 : if (stat_entry)
659 : {
660 : /* Get entry ref to access shared entry */
661 5 : entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
662 5 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false, NULL);
663 :
664 5 : if (entry_ref)
665 : {
666 5 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
667 :
668 : /* Get description from DSA if available */
669 5 : if (DsaPointerIsValid(shared_entry->description))
670 : {
671 5 : if (!custom_stats_description_dsa)
672 5 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
673 :
674 5 : if (custom_stats_description_dsa)
675 5 : description = dsa_get_address(custom_stats_description_dsa, shared_entry->description);
676 : }
677 : }
678 :
679 5 : values[0] = PointerGetDatum(cstring_to_text(stat_name));
680 5 : values[1] = Int64GetDatum(stat_entry->numcalls);
681 :
682 5 : if (description)
683 5 : values[2] = PointerGetDatum(cstring_to_text(description));
684 : else
685 0 : nulls[2] = true;
686 :
687 5 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
688 5 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
689 : }
690 : }
691 :
692 9 : SRF_RETURN_DONE(funcctx);
693 : }
|