From 545719924797a9068b67e9be5be7feac412de878 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 26 Feb 2026 15:24:03 -0500
Subject: [PATCH v2 4/5] MemCTX: Add minimal proxy context type that just
 defers to malloc

Co-authored-by: Matthias van de Meent <boekewurm+postgres@gmail.com>
Co-authored-by: Andres Freund <andres@anarazel.de>
---
 src/backend/nodes/gen_node_support.pl |   2 +-
 src/backend/utils/adt/mcxtfuncs.c     |   3 +
 src/backend/utils/mmgr/Makefile       |   1 +
 src/backend/utils/mmgr/README         |   4 +
 src/backend/utils/mmgr/mcxt.c         |  14 +-
 src/backend/utils/mmgr/meson.build    |   1 +
 src/backend/utils/mmgr/proxy.c        | 511 ++++++++++++++++++++++++++
 src/include/nodes/memnodes.h          |   3 +-
 src/include/utils/memutils.h          |   4 +
 src/include/utils/memutils_internal.h |  18 +-
 src/tools/pgindent/typedefs.list      |   2 +
 11 files changed, 559 insertions(+), 4 deletions(-)
 create mode 100644 src/backend/utils/mmgr/proxy.c

diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 0b766272018..d3bb9877a62 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -136,7 +136,7 @@ my @abstract_types = qw(Node);
 # they otherwise don't participate in node support.
 my @extra_tags = qw(
   IntList OidList XidList
-  AllocSetContext GenerationContext SlabContext BumpContext
+  AllocSetContext GenerationContext SlabContext BumpContext ProxyContext
   TIDBitmap
   WindowObjectData
 );
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 1a4dbbeb8db..85a93de331e 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -160,6 +160,9 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
 		case T_BumpContext:
 			type = "Bump";
 			break;
+		case T_ProxyContext:
+			type = "Proxy";
+			break;
 		default:
 			type = "???";
 			break;
diff --git a/src/backend/utils/mmgr/Makefile b/src/backend/utils/mmgr/Makefile
index 01a1fb85270..64d70bfcdf0 100644
--- a/src/backend/utils/mmgr/Makefile
+++ b/src/backend/utils/mmgr/Makefile
@@ -22,6 +22,7 @@ OBJS = \
 	mcxt.o \
 	memdebug.o \
 	portalmem.o \
+	proxy.o \
 	slab.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README
index 695088bb66d..6105514f586 100644
--- a/src/backend/utils/mmgr/README
+++ b/src/backend/utils/mmgr/README
@@ -495,6 +495,10 @@ allocator types also exist which are special-purpose:
   allocations.  Blocks are only free'd back to the operating system when the
   context is reset or deleted.
 
+* proxy.c (ProxyContext) is best suited for those use cases that require
+  long-lived allocations, with minimal allocation traffic, and generally
+  very few allocations; such as relcache's 'index info' contexts.
+
 For further details, please read the header comment in the corresponding .c
 file.
 
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 594c7a93bba..1cc6e603c9b 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -132,6 +132,19 @@ static const MemoryContextMethods mcxt_methods[] = {
 	[MCTX_BUMP_ID].check = BumpCheck,
 #endif
 
+	/* proxy.c */
+	[MCTX_PROXY_ID].alloc = ProxyAlloc,
+	[MCTX_PROXY_ID].free_p = ProxyFree,
+	[MCTX_PROXY_ID].realloc = ProxyRealloc,
+	[MCTX_PROXY_ID].reset = ProxyReset,
+	[MCTX_PROXY_ID].delete_context = ProxyDelete,
+	[MCTX_PROXY_ID].get_chunk_context = ProxyGetChunkContext,
+	[MCTX_PROXY_ID].get_chunk_space = ProxyGetChunkSpace,
+	[MCTX_PROXY_ID].is_empty = ProxyIsEmpty,
+	[MCTX_PROXY_ID].stats = ProxyStats,
+#ifdef MEMORY_CONTEXT_CHECKING
+	[MCTX_PROXY_ID].check = ProxyCheck,
+#endif
 
 	/*
 	 * Reserved and unused IDs should have dummy entries here.  This allows us
@@ -141,7 +154,6 @@ static const MemoryContextMethods mcxt_methods[] = {
 	 */
 	BOGUS_MCTX(MCTX_1_RESERVED_GLIBC_ID),
 	BOGUS_MCTX(MCTX_2_RESERVED_GLIBC_ID),
-	BOGUS_MCTX(MCTX_8_UNUSED_ID),
 	BOGUS_MCTX(MCTX_9_UNUSED_ID),
 	BOGUS_MCTX(MCTX_10_UNUSED_ID),
 	BOGUS_MCTX(MCTX_11_UNUSED_ID),
diff --git a/src/backend/utils/mmgr/meson.build b/src/backend/utils/mmgr/meson.build
index 61837e9f91b..6eb5e068864 100644
--- a/src/backend/utils/mmgr/meson.build
+++ b/src/backend/utils/mmgr/meson.build
@@ -10,5 +10,6 @@ backend_sources += files(
   'mcxt.c',
   'memdebug.c',
   'portalmem.c',
+  'proxy.c',
   'slab.c',
 )
diff --git a/src/backend/utils/mmgr/proxy.c b/src/backend/utils/mmgr/proxy.c
new file mode 100644
index 00000000000..f07b25244b8
--- /dev/null
+++ b/src/backend/utils/mmgr/proxy.c
@@ -0,0 +1,511 @@
+/*-------------------------------------------------------------------------
+*
+ * proxy.c
+ *	  Proxy allocator definitions.
+ *
+ * Proxy is a MemoryContext implementation designed for memory usages which
+ * require their own memory context, but which generally have few allocations
+ * that generally have a very long lifetime.  Compared to ASet, every
+ * allocation of a Proxy memory context gets an External chunk, i.e. every
+ * palloc results in a malloc.
+ *
+ * Portions Copyright (c) 2024-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/mmgr/proxy.c
+ *
+ * NOTE:
+ *	Proxy is best suited to cases where a memory context with long lifetimes
+ *	is required, but is expected to hold only a small number of small
+ *	allocations, with a total memory usage smaller than the minimum aset block
+ *	size.
+ *
+ *	Allocations are MAXALIGNed.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/ilist.h"
+#include "utils/memdebug.h"
+#include "utils/memutils.h"
+#include "utils/memutils_memorychunk.h"
+#include "utils/memutils_internal.h"
+
+typedef struct ProxyContext
+{
+	MemoryContextData header;	/* Standard memory-context fields */
+
+	dlist_head	allocations;	/* list of all allocations of this memctx */
+} ProxyContext;
+
+typedef struct ProxyBlock
+{
+	dlist_node	node;
+	size_t		sz;
+	ProxyContext *context;
+} ProxyBlock;
+
+#define PROXY_CONTEXTSIZE	(MAXALIGN(sizeof(ProxyContext)))
+#define PROXY_BLOCKHDRSZ	(MAXALIGN(sizeof(ProxyBlock)))
+#define PROXY_CHUNKHDRSZ	(sizeof(MemoryChunk))
+
+/*
+ * ProxyIsValid
+ *		True iff ctx is valid allocation ctx.
+ */
+#define ProxyIsValid(ctx) \
+	((ctx) && IsA(ctx, ProxyContext))
+
+#define ExternalChunkGetBlock(chunk) \
+	((ProxyBlock *) (((char *) chunk) - PROXY_BLOCKHDRSZ))
+
+
+/*
+ * ProxyContextCreate
+ *		Create a Proxy memory context
+ */
+MemoryContext
+ProxyContextCreate(MemoryContext parent, const char *name)
+{
+	size_t		allocSize = PROXY_CONTEXTSIZE;
+	ProxyContext *ctx;
+
+	/*
+	 * Allocate the context struct.  Unlike other memory contexts, this
+	 * context doesn't have an attached chunk of palloc'able memory.
+	 */
+	ctx = (ProxyContext *) malloc(allocSize);
+	if (ctx == NULL)
+	{
+		MemoryContextStats(TopMemoryContext);
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed while creating memory context \"%s\".",
+						   name)));
+	}
+
+	VALGRIND_CREATE_MEMPOOL(ctx, 0, false);
+	VALGRIND_MEMPOOL_ALLOC(ctx, ctx, allocSize);
+
+	dlist_init(&ctx->allocations);
+
+	/* Finally, do the type-independent part of context creation */
+	MemoryContextCreate((MemoryContext) ctx, T_ProxyContext, MCTX_PROXY_ID,
+						parent, name);
+
+	((MemoryContext) ctx)->mem_allocated = allocSize;
+
+	return (MemoryContext) ctx;
+}
+
+/*
+ * ProxyAlloc
+ *		Alloc memory into the Proxy memory context.
+ */
+void *
+ProxyAlloc(MemoryContext context, size_t size, int flags)
+{
+	ProxyContext *ctx = (ProxyContext *) context;
+	size_t		blksize;
+	ProxyBlock *block;
+	MemoryChunk *chunk;
+
+	Assert(ProxyIsValid(ctx));
+
+	/* validate 'size' is within the limits for the given 'flags' */
+	MemoryContextCheckSize(context, size, flags);
+
+	/* adjust size for sentinel byte */
+#ifdef MEMORY_CONTEXT_CHECKING
+	blksize = MAXALIGN(size + 1);
+#else
+	blksize = MAXALIGN(size);
+#endif
+
+	blksize += PROXY_BLOCKHDRSZ + PROXY_CHUNKHDRSZ;
+
+	block = (ProxyBlock *) malloc(blksize);
+	if (block == NULL)
+		return MemoryContextAllocationFailure(context, size, flags);
+
+	VALGRIND_MEMPOOL_ALLOC(ctx, block, PROXY_BLOCKHDRSZ);
+
+	context->mem_allocated += blksize;
+
+	dlist_node_init(&block->node);
+
+	block->context = ctx;
+	block->sz = blksize;
+
+	dlist_push_tail(&ctx->allocations, &block->node);
+
+	chunk = (MemoryChunk *) (((char *) block) + PROXY_BLOCKHDRSZ);
+
+	MemoryChunkSetHdrMaskExternal(chunk, MCTX_PROXY_ID);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	chunk->requested_size = size;
+	/* set mark to catch clobber of "unused" space */
+	set_sentinel(MemoryChunkGetPointer(chunk), size);
+#endif
+
+#ifdef RANDOMIZE_ALLOCATED_MEMORY
+	/* fill the allocated space with junk */
+	randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
+#endif
+
+	VALGRIND_MAKE_MEM_NOACCESS(chunk, PROXY_CHUNKHDRSZ);
+
+	return MemoryChunkGetPointer(chunk);
+}
+
+/*
+ * ProxyAlloc
+ *		Free memory from the Proxy memory context.
+ */
+void
+ProxyFree(void *pointer)
+{
+	ProxyContext *ctx;
+	MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
+	ProxyBlock *block;
+
+	VALGRIND_MAKE_MEM_DEFINED(chunk, PROXY_CHUNKHDRSZ);
+
+	Assert(MemoryChunkIsExternal(chunk));
+
+	block = ExternalChunkGetBlock(chunk);
+
+	ctx = block->context;
+
+	Assert(ProxyIsValid(ctx));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Test for someone scribbling on unused space in chunk */
+	if (!sentinel_ok(pointer, chunk->requested_size))
+		elog(WARNING, "detected write past chunk end in %s %p",
+			 ctx->header.name, chunk);
+#endif
+
+	/* ok, remove block from the list, and free it */
+	dlist_delete_from(&ctx->allocations, &block->node);
+
+	ctx->header.mem_allocated -= block->sz;
+
+#ifdef CLOBBER_FREED_MEMORY
+	wipe_mem(block, block->sz);
+#endif
+
+	VALGRIND_MEMPOOL_FREE(ctx, block);
+
+	free(block);
+}
+
+/*
+ * ProxyAlloc
+ *		Realloc memory in the Proxy memory context.
+ */
+void *
+ProxyRealloc(void *pointer, size_t size, int flags)
+{
+	ProxyBlock *block,
+			   *new_block;
+	ProxyContext *ctx;
+	MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
+	size_t		blksize;
+
+	VALGRIND_MAKE_MEM_DEFINED(chunk, PROXY_CHUNKHDRSZ);
+
+	Assert(MemoryChunkIsExternal(chunk));
+
+	block = ExternalChunkGetBlock(chunk);
+
+	ctx = block->context;
+
+	Assert(ProxyIsValid(ctx));
+
+	/* only check size in paths where the limits could be hit */
+	MemoryContextCheckSize((MemoryContext) ctx, size, flags);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Test for someone scribbling on unused space in chunk */
+	if (!sentinel_ok(pointer, chunk->requested_size))
+		elog(WARNING, "detected write past chunk end in %s %p",
+			 ctx->header.name, chunk);
+#endif
+
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* adjust for sentinel byte, and align */
+	blksize = MAXALIGN(size + 1);
+#else
+	blksize = MAXALIGN(size);
+#endif
+	blksize += PROXY_BLOCKHDRSZ + PROXY_CHUNKHDRSZ;
+
+	/*
+	 * Temporarily unlink the allocation, because the address of the
+	 * allocation may change.
+	 */
+	dlist_delete_from_thoroughly(&ctx->allocations, &block->node);
+
+	new_block = realloc(block, blksize);
+	if (new_block == NULL)
+	{
+		VALGRIND_MAKE_MEM_NOACCESS(chunk, PROXY_CHUNKHDRSZ);
+		return MemoryContextAllocationFailure((MemoryContext) ctx, size, flags);
+	}
+
+	/* Move the block-header vchunk */
+	VALGRIND_MEMPOOL_CHANGE(ctx, block, new_block, PROXY_BLOCKHDRSZ);
+	block = new_block;
+
+	dlist_push_tail(&ctx->allocations, &block->node);
+
+	chunk = (MemoryChunk *) (((char *) block) + PROXY_BLOCKHDRSZ);
+
+	/* randomize the newly allocated memory */
+#ifdef RANDOMIZE_ALLOCATED_MEMORY
+	if (block->sz < blksize)
+		randomize_mem(((char *) block) + block->sz, blksize - block->sz);
+#endif
+
+	/* update chunk's size data */
+#ifdef MEMORY_CONTEXT_CHECKING
+	chunk->requested_size = size;
+	set_sentinel(MemoryChunkGetPointer(chunk), size);
+#endif
+
+	/* and adjust the size indicator of the block header */
+	block->sz = blksize;
+
+	/* Disallow access to the chunk header. */
+	VALGRIND_MAKE_MEM_NOACCESS(chunk, PROXY_CHUNKHDRSZ);
+
+	return MemoryChunkGetPointer(chunk);
+}
+
+/*
+ * ProxyReset
+ *		Reset the Proxy memory context.
+ */
+void
+ProxyReset(MemoryContext context)
+{
+	ProxyContext *ctx = (ProxyContext *) context;
+
+	Assert(ProxyIsValid(ctx));
+
+	while (!dlist_is_empty(&ctx->allocations))
+	{
+		ProxyBlock *block =
+			dlist_container(ProxyBlock, node,
+							dlist_pop_head_node(&ctx->allocations));
+		size_t		blksize = block->sz;
+
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(block, blksize);
+#endif
+
+		ctx->header.mem_allocated -= blksize;
+
+		/*
+		 * We need to free the block header's vchunk explicitly, although
+		 * the user-data vchunks within will go away in the TRIM below.
+		 * Otherwise Valgrind complains about leaked allocations.
+		 */
+		VALGRIND_MEMPOOL_FREE(ctx, block);
+
+		free(block);
+	}
+
+	Assert(ctx->header.mem_allocated == sizeof(ProxyContext));
+	Assert(ProxyIsEmpty(context));
+
+	/*
+	 * Instruct Valgrind to throw away all the vchunks associated with this
+	 * context, except for the one covering the ProxyContext.  This gets rid
+	 * of the vchunks for whatever user data is getting discarded by the
+	 * context reset.
+	 */
+	VALGRIND_MEMPOOL_TRIM(ctx, ctx, PROXY_CONTEXTSIZE);
+}
+
+/*
+ * ProxyAlloc
+ *		Delete this Proxy memory context.
+ */
+void
+ProxyDelete(MemoryContext context)
+{
+	ProxyContext *ctx = (ProxyContext *) context;
+
+	Assert(ProxyIsValid(ctx));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	ProxyCheck(context);
+#endif
+
+	ProxyReset(context);
+
+	VALGRIND_DESTROY_MEMPOOL(context);
+
+	free(ctx);
+}
+
+/*
+ * ProxyGetChunkContext
+ *		Return the MemoryContext that 'pointer' belongs to.
+ */
+MemoryContext
+ProxyGetChunkContext(void *pointer)
+{
+	MemoryChunk	   *chunk = PointerGetMemoryChunk(pointer);
+	ProxyBlock	   *block;
+	ProxyContext   *ctx;
+
+	VALGRIND_MAKE_MEM_DEFINED(chunk, PROXY_CHUNKHDRSZ);
+
+	Assert(MemoryChunkIsExternal(chunk));
+	block = ExternalChunkGetBlock(chunk);
+
+	VALGRIND_MAKE_MEM_NOACCESS(chunk, PROXY_CHUNKHDRSZ);
+
+	ctx = block->context;
+
+	Assert(ProxyIsValid(ctx));
+
+	return (MemoryContext) ctx;
+}
+
+/*
+ * ProxyGetChunkSpace
+*		Given a palloc'd chunk, determine the total space
+ *		it occupies (including all memory-allocation overhead).
+ */
+size_t
+ProxyGetChunkSpace(void *pointer)
+{
+	MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
+	ProxyBlock *block;
+
+	VALGRIND_MAKE_MEM_DEFINED(chunk, PROXY_CHUNKHDRSZ);
+
+	Assert(MemoryChunkIsExternal(chunk));
+	block = ExternalChunkGetBlock(chunk);
+
+	VALGRIND_MAKE_MEM_NOACCESS(chunk, PROXY_CHUNKHDRSZ);
+
+	return block->sz;
+}
+
+/*
+ * ProxyIsEmpty
+*		Is the ProxyContext empty of any allocated space?
+ */
+bool
+ProxyIsEmpty(MemoryContext context)
+{
+	ProxyContext *ctx = (ProxyContext *) context;
+
+	Assert(ProxyIsValid(ctx));
+
+	return dlist_is_empty(&ctx->allocations);
+}
+
+/*
+ * ProxyStats
+ *		Compute stats about memory consumption of a Proxy context.
+ *
+ * printfunc: if not NULL, pass a human-readable stats string to this.
+ * passthru: pass this pointer through to printfunc.
+ * totals: if not NULL, add stats about this context into *totals.
+ * print_to_stderr: print stats to stderr if true, elog otherwise.
+ */
+void
+ProxyStats(MemoryContext context, MemoryStatsPrintFunc printfunc,
+		   void *passthru, MemoryContextCounters *totals,
+		   bool print_to_stderr)
+{
+	ProxyContext *ctx = (ProxyContext *) context;
+	dlist_iter	iter;
+	size_t		totalspace;
+	size_t		nchunks = 0;
+
+	totalspace = MAXALIGN(sizeof(ProxyContext));
+
+	dlist_foreach(iter, &ctx->allocations)
+	{
+		ProxyBlock *block = dlist_container(ProxyBlock, node, iter.cur);
+
+		nchunks++;
+		totalspace += block->sz;
+	}
+
+
+	if (printfunc)
+	{
+		char		stats_string[200];
+
+		snprintf(stats_string, sizeof(stats_string),
+				 "%zu total in %zu chunks;",
+				 totalspace, nchunks);
+		printfunc(context, passthru, stats_string, print_to_stderr);
+	}
+
+	if (totals)
+	{
+		totals->nblocks += nchunks;
+		totals->totalspace += totalspace;
+	}
+}
+
+#ifdef MEMORY_CONTEXT_CHECKING
+/*
+ * ProxyCheck
+ *		Walk through chunks and check consistency of memory.
+ *
+ * NOTE: report errors as WARNING, *not* ERROR or FATAL.  Otherwise you'll
+ * find yourself in an infinite loop when trouble occurs, because this
+ * routine will be entered again when elog cleanup tries to release memory!
+ */
+void
+ProxyCheck(MemoryContext context)
+{
+	ProxyContext *ctx = (ProxyContext *) context;
+	const char *name = context->name;
+	dlist_iter	iter;
+	size_t		total_allocated = sizeof(ProxyContext);
+
+	/* walk all blocks in this context */
+	dlist_foreach(iter, &ctx->allocations)
+	{
+		ProxyBlock *block = dlist_container(ProxyBlock, node, iter.cur);
+		MemoryChunk *chunk = (MemoryChunk *) ((char *) block + PROXY_BLOCKHDRSZ);
+
+		total_allocated += block->sz;
+
+		if (block->sz != MAXALIGN(chunk->requested_size + 1) + PROXY_BLOCKHDRSZ + PROXY_CHUNKHDRSZ)
+		{
+			elog(WARNING, "problem in Proxy %s: bad single-chunk %p in block %p",
+				 name, chunk, block);
+		}
+
+		if (!sentinel_ok(chunk, chunk->requested_size + PROXY_CHUNKHDRSZ))
+		{
+			elog(WARNING, "problem in Proxy %s: detected write past chunk end in block %p, chunk %p",
+				 name, block, chunk);
+		}
+	}
+
+	Assert(total_allocated == context->mem_allocated);
+
+	if (total_allocated != ctx->header.mem_allocated)
+	{
+		elog(WARNING, "problem in Proxy %s: sum of memory %zd does not match header's %zd",
+			 name, total_allocated, ctx->header.mem_allocated);
+	}
+}
+#endif
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index 4b24778fe1e..a9a4e06d86b 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -147,6 +147,7 @@ typedef struct MemoryContextData
 	 (IsA((context), AllocSetContext) || \
 	  IsA((context), SlabContext) || \
 	  IsA((context), GenerationContext) || \
-	  IsA((context), BumpContext)))
+	  IsA((context), BumpContext) || \
+	  IsA((context), ProxyContext)))
 
 #endif							/* MEMNODES_H */
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 38036c7c703..ff531742c8f 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -150,6 +150,10 @@ extern MemoryContext BumpContextCreate(MemoryContext parent,
 									   Size initBlockSize,
 									   Size maxBlockSize);
 
+/* proxy.c */
+extern MemoryContext ProxyContextCreate(MemoryContext parent,
+										const char *name);
+
 /*
  * Recommended default alloc parameters, suitable for "ordinary" contexts
  * that might hold quite a lot of data.
diff --git a/src/include/utils/memutils_internal.h b/src/include/utils/memutils_internal.h
index 475e91b336b..aef1396027e 100644
--- a/src/include/utils/memutils_internal.h
+++ b/src/include/utils/memutils_internal.h
@@ -93,6 +93,22 @@ extern void BumpStats(MemoryContext context, MemoryStatsPrintFunc printfunc,
 					  bool print_to_stderr);
 #ifdef MEMORY_CONTEXT_CHECKING
 extern void BumpCheck(MemoryContext context);
+#endif
+
+ /* These functions implement the MemoryContext API for the Proxy context. */
+extern void *ProxyAlloc(MemoryContext context, size_t size, int flags);
+extern void ProxyFree(void *pointer);
+extern void *ProxyRealloc(void *pointer, size_t size, int flags);
+extern void ProxyReset(MemoryContext context);
+extern void ProxyDelete(MemoryContext context);
+extern MemoryContext ProxyGetChunkContext(void *pointer);
+extern size_t ProxyGetChunkSpace(void *pointer);
+extern bool ProxyIsEmpty(MemoryContext context);
+extern void ProxyStats(MemoryContext context, MemoryStatsPrintFunc printfunc,
+					   void *passthru, MemoryContextCounters *totals,
+					   bool print_to_stderr);
+#ifdef MEMORY_CONTEXT_CHECKING
+extern void ProxyCheck(MemoryContext context);
 #endif
 
 /*
@@ -128,7 +144,7 @@ typedef enum MemoryContextMethodID
 	MCTX_SLAB_ID,
 	MCTX_ALIGNED_REDIRECT_ID,
 	MCTX_BUMP_ID,
-	MCTX_8_UNUSED_ID,
+	MCTX_PROXY_ID,
 	MCTX_9_UNUSED_ID,
 	MCTX_10_UNUSED_ID,
 	MCTX_11_UNUSED_ID,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1040a65bc14..127aaad3e03 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2431,6 +2431,8 @@ ProjectionInfo
 ProjectionPath
 PromptInterruptContext
 ProtocolVersion
+ProxyContext
+ProxyChunk
 PrsStorage
 PruneFreezeParams
 PruneFreezeResult
-- 
2.50.1 (Apple Git-155)

