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