From efa009566623e214b9859efeea768efdf9b00e85 Mon Sep 17 00:00:00 2001 From: Bryan Green Date: Fri, 5 Dec 2025 23:56:33 -0600 Subject: [PATCH v2] Allow complex data for GUC extra. Add support for GUC_EXTRA_IS_CONTEXT flag that allows check hooks to allocate their extra data in a dedicated memory context. This eliminates the need for manual memory management in assign and free hooks. When GUC_EXTRA_IS_CONTEXT is set, the check hook functions (call_bool_check_hook, call_int_check_hook, call_real_check_hook, call_string_check_hook, call_enum_check_hook) create a temporary AllocSetContext before calling the check hook. If the hook succeeds, the context is reparented to GUCMemoryContext. If it fails, the context is deleted. The cleanup in set_extra_field() uses GetMemoryChunkContext() to find and delete the context when freeing old values. Author: Bryan Green Suggested-by: Robert Haas --- src/backend/utils/misc/guc.c | 174 +++++++++- src/include/utils/guc.h | 1 + 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 | 188 +++++++++++ src/test/modules/test_guc/meson.build | 33 ++ src/test/modules/test_guc/sql/test_guc.sql | 68 ++++ src/test/modules/test_guc/test_guc--1.0.sql | 24 ++ src/test/modules/test_guc/test_guc.c | 310 ++++++++++++++++++ src/test/modules/test_guc/test_guc.control | 5 + 11 files changed, 822 insertions(+), 6 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 c6484aea08..e743269a0d 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -764,6 +764,7 @@ extra_field_used(struct config_generic *gconf, void *extra) static void set_extra_field(struct config_generic *gconf, void **field, void *newval) { + MemoryContext ctx = NULL; void *oldval = *field; /* Do the assignment */ @@ -771,7 +772,15 @@ set_extra_field(struct config_generic *gconf, void **field, void *newval) /* Free old value if it's not NULL and isn't referenced anymore */ if (oldval && !extra_field_used(gconf, oldval)) - guc_free(oldval); + { + if(gconf->flags & GUC_EXTRA_IS_CONTEXT) + { + ctx = GetMemoryChunkContext(oldval); + MemoryContextDelete(ctx); + } + else + guc_free(oldval); + } } /* @@ -6641,17 +6650,47 @@ static bool call_bool_check_hook(const struct config_generic *conf, bool *newval, void **extra, GucSource source, int elevel) { + MemoryContext extra_cxt = NULL; + MemoryContext old_cxt = NULL; + bool result; + /* Quick success if no hook */ if (!conf->_bool.check_hook) return true; + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + extra_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GUC check_hook extra context", + ALLOCSET_DEFAULT_SIZES); + old_cxt = MemoryContextSwitchTo(extra_cxt); + } + /* Reset variables that might be set by hook */ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; GUC_check_errmsg_string = NULL; GUC_check_errdetail_string = NULL; GUC_check_errhint_string = NULL; - if (!conf->_bool.check_hook(newval, extra, source)) + result = conf->_bool.check_hook(newval, extra, source); + + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + MemoryContextSwitchTo(old_cxt); + if (result) + { + if (*extra != NULL) + MemoryContextSetParent(extra_cxt, GUCMemoryContext); + else + MemoryContextDelete(extra_cxt); + } + else + { + MemoryContextDelete(extra_cxt); + } + } + + if (!result) { ereport(elevel, (errcode(GUC_check_errcode_value), @@ -6675,17 +6714,47 @@ static bool call_int_check_hook(const struct config_generic *conf, int *newval, void **extra, GucSource source, int elevel) { + MemoryContext extra_cxt = NULL; + MemoryContext old_cxt = NULL; + bool result; + /* Quick success if no hook */ if (!conf->_int.check_hook) return true; + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + extra_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GUC check_hook extra context", + ALLOCSET_DEFAULT_SIZES); + old_cxt = MemoryContextSwitchTo(extra_cxt); + } + /* Reset variables that might be set by hook */ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; GUC_check_errmsg_string = NULL; GUC_check_errdetail_string = NULL; GUC_check_errhint_string = NULL; - if (!conf->_int.check_hook(newval, extra, source)) + result = conf->_int.check_hook(newval, extra, source); + + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + MemoryContextSwitchTo(old_cxt); + if (result) + { + if (*extra != NULL) + MemoryContextSetParent(extra_cxt, GUCMemoryContext); + else + MemoryContextDelete(extra_cxt); + } + else + { + MemoryContextDelete(extra_cxt); + } + } + + if (!result) { ereport(elevel, (errcode(GUC_check_errcode_value), @@ -6709,17 +6778,47 @@ static bool call_real_check_hook(const struct config_generic *conf, double *newval, void **extra, GucSource source, int elevel) { + MemoryContext extra_cxt = NULL; + MemoryContext old_cxt = NULL; + bool result; + /* Quick success if no hook */ if (!conf->_real.check_hook) return true; + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + extra_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GUC check_hook extra context", + ALLOCSET_DEFAULT_SIZES); + old_cxt = MemoryContextSwitchTo(extra_cxt); + } + /* Reset variables that might be set by hook */ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; GUC_check_errmsg_string = NULL; GUC_check_errdetail_string = NULL; GUC_check_errhint_string = NULL; - if (!conf->_real.check_hook(newval, extra, source)) + result = conf->_real.check_hook(newval, extra, source); + + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + MemoryContextSwitchTo(old_cxt); + if (result) + { + if (*extra != NULL) + MemoryContextSetParent(extra_cxt, GUCMemoryContext); + else + MemoryContextDelete(extra_cxt); + } + else + { + MemoryContextDelete(extra_cxt); + } + } + + if (!result) { ereport(elevel, (errcode(GUC_check_errcode_value), @@ -6743,12 +6842,22 @@ static bool call_string_check_hook(const struct config_generic *conf, char **newval, void **extra, GucSource source, int elevel) { + MemoryContext extra_cxt = NULL; + MemoryContext old_cxt = NULL; volatile bool result = true; /* Quick success if no hook */ if (!conf->_string.check_hook) return true; + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + extra_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GUC check_hook extra context", + ALLOCSET_DEFAULT_SIZES); + old_cxt = MemoryContextSwitchTo(extra_cxt); + } + /* * If elevel is ERROR, or if the check_hook itself throws an elog * (undesirable, but not always avoidable), make sure we don't leak the @@ -6762,7 +6871,9 @@ call_string_check_hook(const struct config_generic *conf, char **newval, void ** GUC_check_errdetail_string = NULL; GUC_check_errhint_string = NULL; - if (!conf->_string.check_hook(newval, extra, source)) + result = conf->_string.check_hook(newval, extra, source); + + if (!result) { ereport(elevel, (errcode(GUC_check_errcode_value), @@ -6781,11 +6892,32 @@ call_string_check_hook(const struct config_generic *conf, char **newval, void ** } PG_CATCH(); { + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + MemoryContextSwitchTo(old_cxt); + MemoryContextDelete(extra_cxt); + } guc_free(*newval); PG_RE_THROW(); } PG_END_TRY(); + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + MemoryContextSwitchTo(old_cxt); + if (result) + { + if (*extra != NULL) + MemoryContextSetParent(extra_cxt, GUCMemoryContext); + else + MemoryContextDelete(extra_cxt); + } + else + { + MemoryContextDelete(extra_cxt); + } + } + return result; } @@ -6793,17 +6925,47 @@ static bool call_enum_check_hook(const struct config_generic *conf, int *newval, void **extra, GucSource source, int elevel) { + MemoryContext extra_cxt = NULL; + MemoryContext old_cxt = NULL; + bool result; + /* Quick success if no hook */ if (!conf->_enum.check_hook) return true; + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + extra_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GUC check_hook extra context", + ALLOCSET_DEFAULT_SIZES); + old_cxt = MemoryContextSwitchTo(extra_cxt); + } + /* Reset variables that might be set by hook */ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; GUC_check_errmsg_string = NULL; GUC_check_errdetail_string = NULL; GUC_check_errhint_string = NULL; - if (!conf->_enum.check_hook(newval, extra, source)) + result = conf->_enum.check_hook(newval, extra, source); + + if (conf->flags & GUC_EXTRA_IS_CONTEXT) + { + MemoryContextSwitchTo(old_cxt); + if (result) + { + if (*extra != NULL) + MemoryContextSetParent(extra_cxt, GUCMemoryContext); + else + MemoryContextDelete(extra_cxt); + } + else + { + MemoryContextDelete(extra_cxt); + } + } + + if (!result) { ereport(elevel, (errcode(GUC_check_errcode_value), diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index f21ec37da8..1783301bce 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -228,6 +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 field is context pointer */ #define GUC_UNIT_KB 0x01000000 /* value is in kilobytes */ #define GUC_UNIT_BLOCKS 0x02000000 /* value is in blocks */ diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index d079b91b1a..725b2bfe59 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -27,6 +27,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 cc57461e59..f00c8245f2 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -27,6 +27,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..32d48874ab --- /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 \ No newline at end of file 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..a2ed8a4292 --- /dev/null +++ b/src/test/modules/test_guc/expected/test_guc.out @@ -0,0 +1,188 @@ +CREATE EXTENSION test_guc; +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) + +SET test_guc.counter = ''; +SELECT get_counter_value(); + get_counter_value +------------------- + 0 +(1 row) + +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) + +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; +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) + +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) + +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) + +SET test_guc.pool = ''; +SELECT count_servers(); + count_servers +--------------- + 0 +(1 row) + +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) + +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; +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; +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..76035d79b1 --- /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', + ], + }, +} \ No newline at end of file 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..4bbe2742d3 --- /dev/null +++ b/src/test/modules/test_guc/sql/test_guc.sql @@ -0,0 +1,68 @@ +CREATE EXTENSION test_guc; + +SET test_guc.counter = '42'; +SELECT get_counter_value(); +SELECT get_counter_description(); + +SET test_guc.counter = ''; +SELECT get_counter_value(); + +SET test_guc.counter = '100'; +BEGIN; +SET LOCAL test_guc.counter = '200'; +SELECT get_counter_value(); +ROLLBACK; +SELECT get_counter_value(); + +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; + +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'); + +SELECT show_server_pool(); + +SET test_guc.pool = 'dev:localhost,192.168.1.10;max_connections=5;timeout=30'; +SELECT count_servers(); +SELECT get_pool_setting('pool_name'); + +SET test_guc.pool = ''; +SELECT count_servers(); + +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'); + +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; + +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; + +DROP EXTENSION test_guc; \ No newline at end of file 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..06c2cc22c2 --- /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; \ No newline at end of file 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..5a4a90641c --- /dev/null +++ b/src/test/modules/test_guc/test_guc.c @@ -0,0 +1,310 @@ +/* + * test_guc.c + */ + +#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, /* GUC machinery manages + * context */ + 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)); +} + +/* + * Parse server pool configuration + * Format: "pool_name:server1,server2,server3;max_connections=10;timeout=30" + */ +static bool +check_pool_guc(char **newval, void **extra, GucSource source) +{ + ServerPool *pool; + 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, ";"); + } + } + + 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); + + *extra = pool; + + pfree(work_str); + + elog(DEBUG1, "pool GUC: parsed pool '%s' with %d servers, data=%p", + pool->pool_name, server_count, 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; + } + + pool_extra = (ServerPool *) extra; + + elog(DEBUG1, "pool GUC: active pool '%s' with %d servers, data=%p", + pool_extra->pool_name, + list_length(pool_extra->servers), + 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..c648e51ef3 --- /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 \ No newline at end of file -- 2.52.0.windows.1