v2-0001-Allow-complex-data-for-GUC-extra.patch
text/plain
Filename: v2-0001-Allow-complex-data-for-GUC-extra.patch
Type: text/plain
Part: 0
From efa009566623e214b9859efeea768efdf9b00e85 Mon Sep 17 00:00:00 2001
From: Bryan Green <dbryan.green@gmail.com>
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 <dbryan.green@gmail.com>
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