From 5e889d2f52c9f4b5f4a76f6f6d89afbe091d4f1a Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Mon, 2 Mar 2026 11:24:34 +1300
Subject: [PATCH v2 19/19] Use stack buffer for AppendRelInfos in planner.

---
 src/backend/optimizer/path/joinrels.c   | 18 ++++++---
 src/backend/optimizer/plan/planner.c    | 23 ++++++++----
 src/backend/optimizer/util/appendinfo.c | 32 ++++++++++------
 src/backend/partitioning/partprune.c    | 13 ++++---
 src/include/optimizer/appendinfo.h      | 49 ++++++++++++++++++++++++-
 src/tools/pgindent/typedefs.list        |  1 +
 6 files changed, 105 insertions(+), 31 deletions(-)

diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 443e2dca7c0..c54d771eda6 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -23,6 +23,7 @@
 #include "optimizer/planner.h"
 #include "partitioning/partbounds.h"
 #include "utils/memutils.h"
+#include "utils/stack_buffer.h"
 
 
 static void make_rels_by_clause_joins(PlannerInfo *root,
@@ -1619,6 +1620,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 	ListCell   *lcr1 = NULL;
 	ListCell   *lcr2 = NULL;
 	int			cnt_parts;
+	AppendRelInfoBuffer appinfos_buffer = {0};
+
+	DECLARE_STACK_BUFFER();
 
 	/* Guard against stack overflow due to overly deep partition hierarchy. */
 	check_stack_depth();
@@ -1774,8 +1778,10 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 
 		/* Find the AppendRelInfo structures */
 		child_relids = bms_union(child_rel1->relids, child_rel2->relids);
-		appinfos = find_appinfos_by_relids(root, child_relids,
-										   &nappinfos);
+		appinfos = find_appinfos_by_relids_with_buffer(&appinfos_buffer,
+													   root,
+													   child_relids,
+													   &nappinfos);
 
 		/*
 		 * Construct restrictions applicable to the child join from those
@@ -1820,10 +1826,10 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 		 * are only needed within the loop.  Free these local objects eagerly
 		 * at the end of each iteration.
 		 */
-		pfree(appinfos);
 		bms_free(child_relids);
 		free_child_join_sjinfo(child_sjinfo, parent_sjinfo);
 	}
+	free_appinfos_buffer(&appinfos_buffer);
 }
 
 /*
@@ -1844,6 +1850,8 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
 	AppendRelInfo **right_appinfos;
 	int			right_nappinfos;
 
+	DECLARE_STACK_BUFFER();
+
 	/* Dummy SpecialJoinInfos can be created without any translation. */
 	if (parent_sjinfo->jointype == JOIN_INNER)
 	{
@@ -1874,8 +1882,8 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
 															 right_nappinfos,
 															 right_appinfos);
 
-	pfree(left_appinfos);
-	pfree(right_appinfos);
+	free_appinfos(left_appinfos);
+	free_appinfos(right_appinfos);
 
 	return sjinfo;
 }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 24ad41d4ec6..64b4e98638f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -7966,6 +7966,8 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
 	PathTarget *scanjoin_target;
 	ListCell   *lc;
 
+	DECLARE_STACK_BUFFER();
+
 	/* This recurses, so be paranoid. */
 	check_stack_depth();
 
@@ -8111,6 +8113,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
 	{
 		List	   *live_children = NIL;
 		int			i;
+		AppendRelInfoBuffer appinfos_buffer = {0};
 
 		/* Adjust each partition. */
 		i = -1;
@@ -8128,8 +8131,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
 				continue;
 
 			/* Translate scan/join targets for this child. */
-			appinfos = find_appinfos_by_relids(root, child_rel->relids,
-											   &nappinfos);
+			appinfos = find_appinfos_by_relids_with_buffer(&appinfos_buffer,
+														   root,
+														   child_rel->relids,
+														   &nappinfos);
 			foreach(lc, scanjoin_targets)
 			{
 				PathTarget *target = lfirst_node(PathTarget, lc);
@@ -8142,7 +8147,6 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
 				child_scanjoin_targets = lappend(child_scanjoin_targets,
 												 target);
 			}
-			pfree(appinfos);
 
 			/* Recursion does the real work. */
 			apply_scanjoin_target_to_paths(root, child_rel,
@@ -8155,6 +8159,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
 			if (!IS_DUMMY_REL(child_rel))
 				live_children = lappend(live_children, child_rel);
 		}
+		free_appinfos_buffer(&appinfos_buffer);
 
 		/* Build new paths for this relation by appending child paths. */
 		add_paths_to_append_rel(root, rel, live_children);
@@ -8210,6 +8215,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root,
 	PathTarget *target = grouped_rel->reltarget;
 	bool		partial_grouping_valid = true;
 	int			i;
+	AppendRelInfoBuffer appinfos_buffer = {0};
+
+	DECLARE_STACK_BUFFER();
 
 	Assert(patype != PARTITIONWISE_AGGREGATE_NONE);
 	Assert(patype != PARTITIONWISE_AGGREGATE_PARTIAL ||
@@ -8241,8 +8249,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root,
 		 */
 		memcpy(&child_extra, extra, sizeof(child_extra));
 
-		appinfos = find_appinfos_by_relids(root, child_input_rel->relids,
-										   &nappinfos);
+		appinfos = find_appinfos_by_relids_with_buffer(&appinfos_buffer,
+													   root,
+													   child_input_rel->relids,
+													   &nappinfos);
 
 		child_target->exprs = (List *)
 			adjust_appendrel_attrs(root,
@@ -8296,9 +8306,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root,
 			grouped_live_children = lappend(grouped_live_children,
 											child_grouped_rel);
 		}
-
-		pfree(appinfos);
 	}
+	free_appinfos_buffer(&appinfos_buffer);
 
 	/*
 	 * Try to create append paths for partially grouped children. For full
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index eadecd0bb92..10760235697 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -25,6 +25,7 @@
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/stack_buffer.h"
 #include "utils/syscache.h"
 
 
@@ -600,6 +601,8 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	AppendRelInfo **appinfos;
 	int			nappinfos;
 
+	DECLARE_STACK_BUFFER();
+
 	/* Recurse if immediate parent is not the top parent. */
 	if (childrel->parent != parentrel)
 	{
@@ -612,11 +615,12 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	}
 
 	/* Now translate for this child. */
-	appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
+	appinfos = find_appinfos_by_relids(root, childrel->relids,
+									   &nappinfos);
 
 	node = adjust_appendrel_attrs(root, node, nappinfos, appinfos);
 
-	pfree(appinfos);
+	free_appinfos(appinfos);
 
 	return node;
 }
@@ -667,6 +671,8 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 	AppendRelInfo **appinfos;
 	int			nappinfos;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * If the given relids set doesn't contain any of the parent relids, it
 	 * will remain unchanged.
@@ -686,11 +692,13 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 	}
 
 	/* Now translate for this child. */
-	appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
+	appinfos = find_appinfos_by_relids(root,
+									   childrel->relids,
+									   &nappinfos);
 
 	relids = adjust_child_relids(relids, nappinfos, appinfos);
 
-	pfree(appinfos);
+	free_appinfos(appinfos);
 
 	return relids;
 }
@@ -801,19 +809,19 @@ get_translated_update_targetlist(PlannerInfo *root, Index relid,
  * include outer-join RT indexes in addition to baserels.  We silently
  * ignore the outer joins.
  *
- * The AppendRelInfos are returned in an array, which can be pfree'd by the
- * caller. *nappinfos is set to the number of entries in the array.
+ * A caller-supplied output array must have enough space for
+ * bms_num_members(relids) AppendRelInfo pointer.  The destination address is
+ * returned.
  */
 AppendRelInfo **
-find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
+find_appinfos_by_relids_in_place(AppendRelInfo **appinfos,
+								 PlannerInfo *root,
+								 Relids relids,
+								 int *nappinfos)
 {
-	AppendRelInfo **appinfos;
 	int			cnt = 0;
 	int			i;
 
-	/* Allocate an array that's certainly big enough */
-	appinfos = palloc_array(AppendRelInfo *, bms_num_members(relids));
-
 	i = -1;
 	while ((i = bms_next_member(relids, i)) >= 0)
 	{
@@ -830,11 +838,11 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
 
 		appinfos[cnt++] = appinfo;
 	}
+
 	*nappinfos = cnt;
 	return appinfos;
 }
 
-
 /*****************************************************************************
  *
  *		ROW-IDENTITY VARIABLE MANAGEMENT
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 8c4bf1d657e..58478747513 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -460,6 +460,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 	ListCell   *lc;
 	int			rti;
 	int			i;
+	AppendRelInfoBuffer appinfos_buffer = {0};
 
 	DECLARE_STACK_BUFFER();
 
@@ -517,16 +518,17 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 			if (!bms_equal(parentrel->relids, subpart->relids))
 			{
 				int			nappinfos;
-				AppendRelInfo **appinfos = find_appinfos_by_relids(root,
-																   subpart->relids,
-																   &nappinfos);
+				AppendRelInfo **appinfos;
+
+				appinfos = find_appinfos_by_relids_with_buffer(&appinfos_buffer,
+															   root,
+															   subpart->relids,
+															   &nappinfos);
 
 				prunequal = (List *) adjust_appendrel_attrs(root, (Node *)
 															prunequal,
 															nappinfos,
 															appinfos);
-
-				pfree(appinfos);
 			}
 
 			partprunequal = prunequal;
@@ -544,6 +546,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 												  subpart,
 												  targetpart);
 		}
+		free_appinfos_buffer(&appinfos_buffer);
 
 		/*
 		 * Convert pruning qual to pruning steps.  We may need to do this
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index b59a6218853..03d44c5ba74 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -38,8 +38,10 @@ extern List *adjust_inherited_attnums_multilevel(PlannerInfo *root,
 extern void get_translated_update_targetlist(PlannerInfo *root, Index relid,
 											 List **processed_tlist,
 											 List **update_colnos);
-extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
-											   Relids relids, int *nappinfos);
+extern AppendRelInfo **find_appinfos_by_relids_in_place(AppendRelInfo **appinfos,
+														PlannerInfo *root,
+														Relids relids,
+														int *nappinfos);
 extern void add_row_identity_var(PlannerInfo *root, Var *orig_var,
 								 Index rtindex, const char *rowid_name);
 extern void add_row_identity_columns(PlannerInfo *root, Index rtindex,
@@ -47,4 +49,47 @@ extern void add_row_identity_columns(PlannerInfo *root, Index rtindex,
 									 Relation target_relation);
 extern void distribute_row_identity_vars(PlannerInfo *root);
 
+typedef struct AppendRelInfoBuffer
+{
+	AppendRelInfo **appinfos;
+	int			capacity;
+	int			want;
+} AppendRelInfoBuffer;
+
+/*
+ * The following are written as a macros so they can use a stack buffer to
+ * place the results in the calling stack frame if possible.  A stack buffer
+ * must be declared in the current scope to use them.
+ */
+
+#define find_appinfos_by_relids(root, relids, nappinfos)				\
+	find_appinfos_by_relids_in_place(stack_buffer_alloc_array(AppendRelInfo *, \
+															  bms_num_members(relids)), \
+									 (root),							\
+									 (relids),							\
+									 (nappinfos))
+#define free_appinfos(appinfos)					\
+	if (appinfos)								\
+		stack_buffer_free(appinfos)
+
+#define find_appinfos_by_relids_with_buffer(buffer, root, relids, nappinfos) \
+	((buffer)->want = bms_num_members(relids),							\
+	 (buffer)->appinfos = ((buffer)->capacity >= (buffer)->want ?		\
+						   (buffer)->appinfos :							\
+						   ((buffer)->capacity = (buffer)->want,		\
+							stack_buffer_alloc_array(AppendRelInfo *,	\
+													 (buffer)->capacity))), \
+	 find_appinfos_by_relids_in_place((buffer)->appinfos,				\
+									  (root),							\
+									  (relids),							\
+									  (nappinfos)))
+
+#define free_appinfos_buffer(buffer)				\
+	if ((buffer)->appinfos)							\
+	{												\
+		stack_buffer_free((buffer)->appinfos);		\
+		(buffer)->appinfos = NULL;					\
+		(buffer)->capacity = 0;						\
+	}
+
 #endif							/* APPENDINFO_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 141b9d6e077..5b9b56d6a0e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -127,6 +127,7 @@ Append
 AppendPath
 AppendPathInput
 AppendRelInfo
+AppendRelInfoBuffer
 AppendState
 ApplyErrorCallbackArg
 ApplyExecutionData
-- 
2.53.0

