Following up on Tom Lane's suggestion to use MemoryContexts for GUC
extra data [1], I've implemented a working solution that addresses the
design issues Robert Haas identified with my initial approach.

The original problem: GUC check hooks can only return a single chunk for
extra data, making it awkward to use complex structures like Lists or
hash tables. Tom suggested allowing the extra field to point to a
MemoryContext instead, which would enable arbitrary nested structures
with automatic cleanup via MemoryContextDelete().

My first implementation stored the context pointer directly as the extra
data. Robert pointed out the fatal flaw: during transaction rollback,
the assign hook needs to receive a data pointer (to update its global
variable), but if extra contains a context pointer, there's no way to
retrieve the actual data. A global mapping table would work but seemed
unnecessarily complex.

The solution uses a wrapper struct (GucContextExtra) containing both the
MemoryContext and data pointers. Check hooks:
  1. Create a context under CurrentMemoryContext (for error safety)
  2. Allocate their data structures within it
  3. Allocate the wrapper itself within the same context
  4. On success, re-parent the context to TopMemoryContext
  5. Return the wrapper as extra

The GUC machinery manages wrapper pointers in its stack. On rollback,
the assign hook receives the old wrapper and extracts the correct old
data pointer, maintaining proper transaction semantics. When freeing
extra data, we simply delete the context - since the wrapper lives
inside it, everything is freed with one call.

Error handling is automatic: if the check hook errors during parsing,
the context is still under CurrentMemoryContext and gets cleaned up
normally, preventing leaks.

The attached patch adds:
  - GUC_EXTRA_IS_CONTEXT flag
  - GucContextExtra struct definition
  - Modified free_extra_value() to handle both paths
  - Test module (src/test/modules/test_guc) with simple counter
(traditional path, no context) and server pool (context-based path with
List)

Regression tests validate that Lists survive transaction rollback and
savepoint operations correctly.

Thoughts?

Patch attached.

[1] https://discord.com/channels/1258108670710124574/1402360503036285130

-- 
Bryan Green
EDB: https://www.enterprisedb.com
From 1aa72827c12ac3618e9af412f01a1f8273b24c23 Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
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

Reply via email to