From 1aa72827c12ac3618e9af412f01a1f8273b24c23 Mon Sep 17 00:00:00 2001 From: Bryan Green Date: Sun, 16 Nov 2025 17:49:11 -0600 Subject: [PATCH v1] Allow complex data for GUC extra. Add flag GUC_EXTRA_IS_CONTEXT to indicate that the extra pointer is a GucContextExtra wrapper containing a MemoryContext and a data pointer. When this flag is set, check hooks create a current context, allocate their data structures within it, creates a GucContextExtra wrapper to hold both the context and data pointers, reparents the context to TopMemoryContext and then returns the wrapper. When freeing the extra data, we just delete the context. The wrapper approach is necessary to maintain the GUC contract during rollback: the GUC stack holds wrapper pointers, but assign hooks must receive data pointers. Storing the context pointer in the wrapper ensures we can find and delete the right context while still passing the correct data pointer to assign hooks. --- src/backend/utils/misc/guc.c | 44 ++- src/include/utils/guc.h | 14 +- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + src/test/modules/test_guc/Makefile | 23 ++ .../modules/test_guc/expected/test_guc.out | 202 +++++++++++ src/test/modules/test_guc/meson.build | 33 ++ src/test/modules/test_guc/sql/test_guc.sql | 83 +++++ src/test/modules/test_guc/test_guc--1.0.sql | 24 ++ src/test/modules/test_guc/test_guc.c | 323 ++++++++++++++++++ src/test/modules/test_guc/test_guc.control | 5 + 11 files changed, 739 insertions(+), 14 deletions(-) create mode 100644 src/test/modules/test_guc/Makefile create mode 100644 src/test/modules/test_guc/expected/test_guc.out create mode 100644 src/test/modules/test_guc/meson.build create mode 100644 src/test/modules/test_guc/sql/test_guc.sql create mode 100644 src/test/modules/test_guc/test_guc--1.0.sql create mode 100644 src/test/modules/test_guc/test_guc.c create mode 100644 src/test/modules/test_guc/test_guc.control diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 7e2b17cc04..fbe25d9f0f 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -756,6 +756,24 @@ extra_field_used(struct config_generic *gconf, void *extra) return false; } +/* + * Free a GUC extra value, handling both regular extra data and context-based extra data. + */ +static void +free_extra_value(struct config_generic *gconf, void *extra) +{ + if (extra == NULL) + return; + + if (gconf->flags & GUC_EXTRA_IS_CONTEXT) + { + GucContextExtra *wrapper = (GucContextExtra *) extra; + MemoryContextDelete(wrapper->context); + } + else + guc_free(extra); +} + /* * Support for assigning to an "extra" field of a GUC item. Free the prior * value if it's not referenced anywhere else in the item (including stacked @@ -3598,7 +3616,7 @@ set_config_with_handle(const char *name, config_handle *handle, { /* Release newextra, unless it's reset_extra */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); if (*conf->variable != newval) { @@ -3655,7 +3673,7 @@ set_config_with_handle(const char *name, config_handle *handle, /* Perhaps we didn't install newextra anywhere */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); break; #undef newval @@ -3694,7 +3712,7 @@ set_config_with_handle(const char *name, config_handle *handle, { /* Release newextra, unless it's reset_extra */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); if (*conf->variable != newval) { @@ -3751,7 +3769,7 @@ set_config_with_handle(const char *name, config_handle *handle, /* Perhaps we didn't install newextra anywhere */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); break; #undef newval @@ -3790,7 +3808,7 @@ set_config_with_handle(const char *name, config_handle *handle, { /* Release newextra, unless it's reset_extra */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); if (*conf->variable != newval) { @@ -3847,7 +3865,7 @@ set_config_with_handle(const char *name, config_handle *handle, /* Perhaps we didn't install newextra anywhere */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); break; #undef newval @@ -3915,7 +3933,7 @@ set_config_with_handle(const char *name, config_handle *handle, guc_free(newval); /* Release newextra, unless it's reset_extra */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); if (newval_different) { @@ -4015,7 +4033,7 @@ set_config_with_handle(const char *name, config_handle *handle, guc_free(newval); /* Perhaps we didn't install newextra anywhere */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); break; #undef newval @@ -4054,7 +4072,7 @@ set_config_with_handle(const char *name, config_handle *handle, { /* Release newextra, unless it's reset_extra */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); if (*conf->variable != newval) { @@ -4111,7 +4129,7 @@ set_config_with_handle(const char *name, config_handle *handle, /* Perhaps we didn't install newextra anywhere */ if (newextra && !extra_field_used(record, newextra)) - guc_free(newextra); + free_extra_value(record, newextra); break; #undef newval @@ -4563,7 +4581,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) if (record->vartype == PGC_STRING && newval.stringval != NULL) guc_free(newval.stringval); - guc_free(newextra); + free_extra_value(record, newextra); } } else @@ -6103,7 +6121,7 @@ RestoreGUCState(void *gucstate) * in. */ Assert(gconf->stack == NULL); - guc_free(gconf->extra); + free_extra_value(gconf, gconf->extra); guc_free(gconf->last_reported); guc_free(gconf->sourcefile); switch (gconf->vartype) @@ -6125,7 +6143,7 @@ RestoreGUCState(void *gucstate) } } if (gconf->reset_extra && gconf->reset_extra != gconf->extra) - guc_free(gconf->reset_extra); + free_extra_value(gconf, gconf->reset_extra); /* Remove it from any lists it's in. */ RemoveGUCFromLists(gconf); /* Now we can reset the struct to PGS_S_DEFAULT state. */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index f21ec37da8..eb9dfb894b 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -228,7 +228,7 @@ typedef enum 0x002000 /* can't set in PG_AUTOCONF_FILENAME */ #define GUC_RUNTIME_COMPUTED 0x004000 /* delay processing in 'postgres -C' */ #define GUC_ALLOW_IN_PARALLEL 0x008000 /* allow setting in parallel mode */ - +#define GUC_EXTRA_IS_CONTEXT 0x010000 /* 'extra' is GucContextExtra */ #define GUC_UNIT_KB 0x01000000 /* value is in kilobytes */ #define GUC_UNIT_BLOCKS 0x02000000 /* value is in blocks */ #define GUC_UNIT_XBLOCKS 0x03000000 /* value is in xlog blocks */ @@ -243,6 +243,18 @@ typedef enum #define GUC_UNIT (GUC_UNIT_MEMORY | GUC_UNIT_TIME) +/* + * GUC_EXTRA_IS_CONTEXT indicates that the 'extra' field should be treated as + * a GucContextExtra wrapper containing a MemoryContext rather than plain + * allocated memory. This allows check hooks to allocate complex data structures + * (Lists, hash tables, etc.) using palloc within the context. The GUC machinery + * will call MemoryContextDelete() on the wrapped context. + */ +typedef struct GucContextExtra +{ + MemoryContext context; + void *data; +} GucContextExtra; /* GUC vars that are actually defined in guc_tables.c, rather than elsewhere */ extern PGDLLIMPORT bool Debug_print_plan; diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 902a795410..4cf5ce7d7b 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -26,6 +26,7 @@ SUBDIRS = \ test_escape \ test_extensions \ test_ginpostinglist \ + test_guc \ test_int128 \ test_integerset \ test_json_parser \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 14fc761c4c..37bb9f254b 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -25,6 +25,7 @@ subdir('test_dsm_registry') subdir('test_escape') subdir('test_extensions') subdir('test_ginpostinglist') +subdir('test_guc') subdir('test_int128') subdir('test_integerset') subdir('test_json_parser') diff --git a/src/test/modules/test_guc/Makefile b/src/test/modules/test_guc/Makefile new file mode 100644 index 0000000000..dbecbcd1bb --- /dev/null +++ b/src/test/modules/test_guc/Makefile @@ -0,0 +1,23 @@ +# src/test/modules/test_guc/Makefile + +MODULE_big = test_guc +OBJS = \ + $(WIN32RES) \ + test_guc.o + +EXTENSION = test_guc +DATA = test_guc--1.0.sql +PGFILEDESC = "test_guc - test module for GUC_EXTRA_IS_CONTEXT" + +REGRESS = test_guc + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_guc +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_guc/expected/test_guc.out b/src/test/modules/test_guc/expected/test_guc.out new file mode 100644 index 0000000000..168a794926 --- /dev/null +++ b/src/test/modules/test_guc/expected/test_guc.out @@ -0,0 +1,202 @@ +-- test_guc.sql +-- Test GUC_EXTRA_IS_CONTEXT feature with simple and complex data +CREATE EXTENSION test_guc; +-- Basic set and get +SET test_guc.counter = '42'; +SELECT get_counter_value(); + get_counter_value +------------------- + 42 +(1 row) + +SELECT get_counter_description(); + get_counter_description +------------------------- + Count is 42 +(1 row) + +-- Empty value +SET test_guc.counter = ''; +SELECT get_counter_value(); + get_counter_value +------------------- + 0 +(1 row) + +-- Transaction rollback +SET test_guc.counter = '100'; +BEGIN; +SET LOCAL test_guc.counter = '200'; +SELECT get_counter_value(); + get_counter_value +------------------- + 200 +(1 row) + +ROLLBACK; +SELECT get_counter_value(); + get_counter_value +------------------- + 100 +(1 row) + +-- Savepoint rollback +BEGIN; +SET LOCAL test_guc.counter = '10'; +SAVEPOINT sp1; +SET LOCAL test_guc.counter = '20'; +SELECT get_counter_value(); + get_counter_value +------------------- + 20 +(1 row) + +ROLLBACK TO sp1; +SELECT get_counter_value(); + get_counter_value +------------------- + 10 +(1 row) + +ROLLBACK; +-- Basic set with List +SET test_guc.pool = 'prod:db1.example.com,db2.example.com,db3.example.com;max_connections=100;timeout=60'; +SELECT count_servers(); + count_servers +--------------- + 3 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + prod +(1 row) + +SELECT get_pool_setting('max_connections'); + get_pool_setting +------------------ + 100 +(1 row) + +SELECT get_pool_setting('timeout'); + get_pool_setting +------------------ + 60 +(1 row) + +-- Show full pool +SELECT show_server_pool(); + show_server_pool +---------------------- + Pool: prod + + Max connections: 100+ + Timeout: 60 seconds + + Servers (3 total): + + - db1.example.com + + - db2.example.com + + - db3.example.com + + +(1 row) + +-- Different pool configuration +SET test_guc.pool = 'dev:localhost,192.168.1.10;max_connections=5;timeout=30'; +SELECT count_servers(); + count_servers +--------------- + 2 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + dev +(1 row) + +-- Empty value clears complex data +SET test_guc.pool = ''; +SELECT count_servers(); + count_servers +--------------- + 0 +(1 row) + +-- Transaction rollback with complex data +SET test_guc.pool = 'pool1:s1,s2;max_connections=10;timeout=20'; +BEGIN; +SET LOCAL test_guc.pool = 'pool2:s3,s4,s5;max_connections=50;timeout=90'; +SELECT count_servers(); + count_servers +--------------- + 3 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + pool2 +(1 row) + +ROLLBACK; +SELECT count_servers(); + count_servers +--------------- + 2 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + pool1 +(1 row) + +-- Savepoint rollback with complex data +BEGIN; +SET LOCAL test_guc.pool = 'outer:host1,host2;max_connections=10;timeout=30'; +SAVEPOINT sp1; +SET LOCAL test_guc.pool = 'inner:host3,host4,host5;max_connections=20;timeout=40'; +SELECT count_servers(); + count_servers +--------------- + 3 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + inner +(1 row) + +ROLLBACK TO sp1; +SELECT count_servers(); + count_servers +--------------- + 2 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + outer +(1 row) + +ROLLBACK; +-- Multiple SET LOCAL at same level (last wins) +BEGIN; +SET LOCAL test_guc.pool = 'first:s1;max_connections=10;timeout=20'; +SET LOCAL test_guc.pool = 'second:s1,s2;max_connections=20;timeout=30'; +SELECT count_servers(); + count_servers +--------------- + 2 +(1 row) + +SELECT get_pool_setting('pool_name'); + get_pool_setting +------------------ + second +(1 row) + +ROLLBACK; +-- Clean up +DROP EXTENSION test_guc; diff --git a/src/test/modules/test_guc/meson.build b/src/test/modules/test_guc/meson.build new file mode 100644 index 0000000000..a3728dd6c6 --- /dev/null +++ b/src/test/modules/test_guc/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group + +test_guc_sources = files( + 'test_guc.c', +) + +if host_system == 'windows' + test_guc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_guc', + '--FILEDESC', 'test_guc - test module for GUC_EXTRA_IS_CONTEXT',]) +endif + +test_guc = shared_module('test_guc', + test_guc_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_guc + +test_install_data += files( + 'test_guc.control', + 'test_guc--1.0.sql', +) + +tests += { + 'name': 'test_guc', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_guc', + ], + }, +} diff --git a/src/test/modules/test_guc/sql/test_guc.sql b/src/test/modules/test_guc/sql/test_guc.sql new file mode 100644 index 0000000000..1ceb353bfb --- /dev/null +++ b/src/test/modules/test_guc/sql/test_guc.sql @@ -0,0 +1,83 @@ +-- test_guc.sql +-- Test GUC_EXTRA_IS_CONTEXT feature with simple and complex data + +CREATE EXTENSION test_guc; + +-- Basic set and get +SET test_guc.counter = '42'; +SELECT get_counter_value(); +SELECT get_counter_description(); + +-- Empty value +SET test_guc.counter = ''; +SELECT get_counter_value(); + +-- Transaction rollback +SET test_guc.counter = '100'; +BEGIN; +SET LOCAL test_guc.counter = '200'; +SELECT get_counter_value(); +ROLLBACK; +SELECT get_counter_value(); + +-- Savepoint rollback +BEGIN; +SET LOCAL test_guc.counter = '10'; +SAVEPOINT sp1; +SET LOCAL test_guc.counter = '20'; +SELECT get_counter_value(); +ROLLBACK TO sp1; +SELECT get_counter_value(); +ROLLBACK; + +-- Basic set with List +SET test_guc.pool = 'prod:db1.example.com,db2.example.com,db3.example.com;max_connections=100;timeout=60'; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); +SELECT get_pool_setting('max_connections'); +SELECT get_pool_setting('timeout'); + +-- Show full pool +SELECT show_server_pool(); + +-- Different pool configuration +SET test_guc.pool = 'dev:localhost,192.168.1.10;max_connections=5;timeout=30'; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); + +-- Empty value clears complex data +SET test_guc.pool = ''; +SELECT count_servers(); + +-- Transaction rollback with complex data +SET test_guc.pool = 'pool1:s1,s2;max_connections=10;timeout=20'; +BEGIN; +SET LOCAL test_guc.pool = 'pool2:s3,s4,s5;max_connections=50;timeout=90'; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); +ROLLBACK; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); + +-- Savepoint rollback with complex data +BEGIN; +SET LOCAL test_guc.pool = 'outer:host1,host2;max_connections=10;timeout=30'; +SAVEPOINT sp1; +SET LOCAL test_guc.pool = 'inner:host3,host4,host5;max_connections=20;timeout=40'; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); +ROLLBACK TO sp1; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); +ROLLBACK; + +-- Multiple SET LOCAL at same level (last wins) +BEGIN; +SET LOCAL test_guc.pool = 'first:s1;max_connections=10;timeout=20'; +SET LOCAL test_guc.pool = 'second:s1,s2;max_connections=20;timeout=30'; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); +ROLLBACK; + +-- Clean up +DROP EXTENSION test_guc; diff --git a/src/test/modules/test_guc/test_guc--1.0.sql b/src/test/modules/test_guc/test_guc--1.0.sql new file mode 100644 index 0000000000..76638b7ed6 --- /dev/null +++ b/src/test/modules/test_guc/test_guc--1.0.sql @@ -0,0 +1,24 @@ +CREATE FUNCTION get_counter_value() +RETURNS int +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION get_counter_description() +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION show_server_pool() +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION count_servers() +RETURNS int +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION get_pool_setting(text) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; diff --git a/src/test/modules/test_guc/test_guc.c b/src/test/modules/test_guc/test_guc.c new file mode 100644 index 0000000000..2ee496a394 --- /dev/null +++ b/src/test/modules/test_guc/test_guc.c @@ -0,0 +1,323 @@ +#include "postgres.h" +#include "funcapi.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" + +PG_MODULE_MAGIC; + +typedef struct SimpleCounterExtra +{ + int count; + char description[64]; +} SimpleCounterExtra; + +static SimpleCounterExtra * counter_extra = NULL; +static char *counter_string = NULL; + +static bool check_counter_guc(char **newval, void **extra, GucSource source); +static void assign_counter_guc(const char *newval, void *extra); + +typedef struct ServerPool +{ + char *pool_name; + List *servers; + int max_connections; + int timeout_seconds; + char *description; +} ServerPool; + +static ServerPool * pool_extra = NULL; +static char *pool_string = NULL; + +static bool check_pool_guc(char **newval, void **extra, GucSource source); +static void assign_pool_guc(const char *newval, void *extra); + +PG_FUNCTION_INFO_V1(get_counter_value); +PG_FUNCTION_INFO_V1(get_counter_description); +PG_FUNCTION_INFO_V1(show_server_pool); +PG_FUNCTION_INFO_V1(count_servers); +PG_FUNCTION_INFO_V1(get_pool_setting); + +void +_PG_init(void) +{ + DefineCustomStringVariable("test_guc.counter", + "Simple GUC without context (backward compatibility)", + "Integer counter value", + &counter_string, + NULL, + PGC_USERSET, + 0, /* No GUC_EXTRA_IS_CONTEXT flag */ + check_counter_guc, + assign_counter_guc, + NULL); + + DefineCustomStringVariable("test_guc.pool", + "Server pool configuration with context", + "Format: name:server1,server2;max_connections=N;timeout=N", + &pool_string, + NULL, + PGC_USERSET, + GUC_EXTRA_IS_CONTEXT, /* Uses context wrapper */ + check_pool_guc, + assign_pool_guc, + NULL); +} + +static bool +check_counter_guc(char **newval, void **extra, GucSource source) +{ + SimpleCounterExtra *data; + int count; + + if (*newval == NULL || **newval == '\0') + { + *extra = NULL; + return true; + } + + count = atoi(*newval); + + data = (SimpleCounterExtra *) guc_malloc(LOG, sizeof(SimpleCounterExtra)); + if (data == NULL) + return false; + + data->count = count; + snprintf(data->description, sizeof(data->description), "Count is %d", count); + + *extra = data; + + elog(DEBUG1, "counter GUC: parsed count=%d, data=%p", count, data); + + return true; +} + +static void +assign_counter_guc(const char *newval, void *extra) +{ + if (extra == NULL) + { + counter_extra = NULL; + elog(DEBUG1, "Counter GUC: cleared"); + return; + } + + counter_extra = (SimpleCounterExtra *) extra; + + elog(DEBUG1, "counter GUC: active with count=%d, data=%p", + counter_extra->count, counter_extra); +} + +Datum +get_counter_value(PG_FUNCTION_ARGS) +{ + if (counter_extra == NULL) + PG_RETURN_INT32(0); + + PG_RETURN_INT32(counter_extra->count); +} + +Datum +get_counter_description(PG_FUNCTION_ARGS) +{ + if (counter_extra == NULL) + PG_RETURN_TEXT_P(cstring_to_text("No counter configured")); + + PG_RETURN_TEXT_P(cstring_to_text(counter_extra->description)); +} + +static bool +check_pool_guc(char **newval, void **extra, GucSource source) +{ + MemoryContext extra_cxt; + MemoryContext oldcontext; + ServerPool *pool; + GucContextExtra *wrapper; + char *work_str; + char *pool_name; + char *servers_part; + char *settings_part; + char *server_token; + int max_conn = 10; + int timeout = 30; + int server_count = 0; + + if (*newval == NULL || **newval == '\0') + { + *extra = NULL; + return true; + } + + work_str = pstrdup(*newval); + + pool_name = work_str; + servers_part = strchr(work_str, ':'); + if (servers_part == NULL) + { + pfree(work_str); + GUC_check_errdetail("Format should be 'name:server1,server2;setting=val'"); + return false; + } + *servers_part++ = '\0'; + + settings_part = strchr(servers_part, ';'); + if (settings_part != NULL) + *settings_part++ = '\0'; + + if (settings_part != NULL) + { + char *setting = strtok(settings_part, ";"); + + while (setting != NULL) + { + char *eq = strchr(setting, '='); + + if (eq != NULL) + { + *eq++ = '\0'; + if (strcmp(setting, "max_connections") == 0) + max_conn = atoi(eq); + else if (strcmp(setting, "timeout") == 0) + timeout = atoi(eq); + } + setting = strtok(NULL, ";"); + } + } + + /* Create context under CurrentMemoryContext - if we ERROR, it gets cleaned up */ + extra_cxt = AllocSetContextCreate(CurrentMemoryContext, + "server_pool_context", + ALLOCSET_SMALL_SIZES); + + oldcontext = MemoryContextSwitchTo(extra_cxt); + + pool = (ServerPool *) palloc(sizeof(ServerPool)); + pool->pool_name = pstrdup(pool_name); + pool->servers = NIL; + pool->max_connections = max_conn; + pool->timeout_seconds = timeout; + + /* Parse server list */ + server_token = strtok(servers_part, ","); + while (server_token != NULL) + { + while (*server_token == ' ' || *server_token == '\t') + server_token++; + char *end = server_token + strlen(server_token) - 1; + + while (end > server_token && (*end == ' ' || *end == '\t')) + *end-- = '\0'; + + if (*server_token != '\0') + { + pool->servers = lappend(pool->servers, pstrdup(server_token)); + server_count++; + } + + server_token = strtok(NULL, ","); + } + + pool->description = psprintf("Pool '%s': %d servers, max_conn=%d, timeout=%d", + pool->pool_name, + server_count, + pool->max_connections, + pool->timeout_seconds); + + /* Create wrapper in the same context */ + wrapper = (GucContextExtra *) palloc(sizeof(GucContextExtra)); + wrapper->context = extra_cxt; + wrapper->data = pool; + + MemoryContextSwitchTo(oldcontext); + + MemoryContextSetParent(extra_cxt, TopMemoryContext); + + *extra = wrapper; + + pfree(work_str); + + elog(DEBUG1, "pool GUC: parsed pool '%s' with %d servers, context=%p, data=%p", + pool->pool_name, server_count, extra_cxt, pool); + + return true; +} + +static void +assign_pool_guc(const char *newval, void *extra) +{ + if (extra == NULL) + { + pool_extra = NULL; + elog(DEBUG1, "Server pool GUC: cleared"); + return; + } + + GucContextExtra *wrapper = (GucContextExtra *) extra; + + pool_extra = (ServerPool *) wrapper->data; + + elog(DEBUG1, "pool GUC: active pool '%s' with %d servers, context=%p, data=%p", + pool_extra->pool_name, + list_length(pool_extra->servers), + wrapper->context, + pool_extra); +} + +Datum +show_server_pool(PG_FUNCTION_ARGS) +{ + StringInfoData buf; + ListCell *lc; + + if (pool_extra == NULL) + PG_RETURN_TEXT_P(cstring_to_text("No server pool configured")); + + initStringInfo(&buf); + appendStringInfo(&buf, "Pool: %s\n", pool_extra->pool_name); + appendStringInfo(&buf, "Max connections: %d\n", pool_extra->max_connections); + appendStringInfo(&buf, "Timeout: %d seconds\n", pool_extra->timeout_seconds); + appendStringInfo(&buf, "Servers (%d total):\n", list_length(pool_extra->servers)); + + foreach(lc, pool_extra->servers) + { + char *server = (char *) lfirst(lc); + + appendStringInfo(&buf, " - %s\n", server); + } + + PG_RETURN_TEXT_P(cstring_to_text(buf.data)); +} + +Datum +count_servers(PG_FUNCTION_ARGS) +{ + if (pool_extra == NULL) + PG_RETURN_INT32(0); + + PG_RETURN_INT32(list_length(pool_extra->servers)); +} + +Datum +get_pool_setting(PG_FUNCTION_ARGS) +{ + text *setting_name = PG_GETARG_TEXT_PP(0); + char *name = text_to_cstring(setting_name); + char result[256]; + + if (pool_extra == NULL) + PG_RETURN_TEXT_P(cstring_to_text("No pool configured")); + + if (strcmp(name, "pool_name") == 0) + snprintf(result, sizeof(result), "%s", pool_extra->pool_name); + else if (strcmp(name, "max_connections") == 0) + snprintf(result, sizeof(result), "%d", pool_extra->max_connections); + else if (strcmp(name, "timeout") == 0) + snprintf(result, sizeof(result), "%d", pool_extra->timeout_seconds); + else if (strcmp(name, "description") == 0) + snprintf(result, sizeof(result), "%s", pool_extra->description); + else + snprintf(result, sizeof(result), "Unknown setting: %s", name); + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} diff --git a/src/test/modules/test_guc/test_guc.control b/src/test/modules/test_guc/test_guc.control new file mode 100644 index 0000000000..ed0bd6b941 --- /dev/null +++ b/src/test/modules/test_guc/test_guc.control @@ -0,0 +1,5 @@ +# test_guc extension +comment = 'Test module for GUC_EXTRA_IS_CONTEXT feature' +default_version = '1.0' +module_pathname = '$libdir/test_guc' +relocatable = true -- 2.46.0.windows.1