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

---
 src/backend/optimizer/path/allpaths.c     |  7 +++--
 src/backend/optimizer/path/equivclass.c   |  9 ++++--
 src/backend/optimizer/path/indxpath.c     | 14 ++++++---
 src/backend/optimizer/path/pathkeys.c     | 15 +++++----
 src/backend/optimizer/plan/analyzejoins.c |  7 ++++-
 src/backend/optimizer/plan/planner.c      | 37 ++++++++++++++---------
 src/backend/optimizer/prep/prepagg.c      |  7 +++--
 src/backend/optimizer/prep/prepunion.c    |  7 +++--
 src/backend/optimizer/util/clauses.c      | 17 +++++++----
 src/backend/optimizer/util/plancat.c      |  7 +++--
 10 files changed, 84 insertions(+), 43 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 5eceb321828..1c8efc5e3cd 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -49,6 +49,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
+#include "utils/stack_buffer.h"
 
 
 /* Bitmask flags for pushdown_safety_info.unsafeFlags */
@@ -1021,6 +1022,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	int			nattrs;
 	ListCell   *l;
 
+	DECLARE_STACK_BUFFER();
+
 	/* Guard against stack overflow due to overly deep inheritance tree. */
 	check_stack_depth();
 
@@ -1065,7 +1068,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	parent_rows = 0;
 	parent_size = 0;
 	nattrs = rel->max_attr - rel->min_attr + 1;
-	parent_attrsizes = (double *) palloc0(nattrs * sizeof(double));
+	parent_attrsizes = stack_buffer_alloc0_array(double, nattrs);
 
 	foreach(l, root->append_rel_list)
 	{
@@ -1296,7 +1299,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		set_dummy_rel_pathlist(rel);
 	}
 
-	pfree(parent_attrsizes);
+	stack_buffer_free(parent_attrsizes);
 }
 
 /*
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index e3697df51a2..28a3162b98e 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -32,6 +32,7 @@
 #include "optimizer/restrictinfo.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
+#include "utils/stack_buffer.h"
 
 
 static EquivalenceMember *make_eq_member(EquivalenceClass *ec,
@@ -1373,6 +1374,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * We scan the EC members once and track the last-seen member for each
 	 * base relation.  When we see another member of the same base relation,
@@ -1381,8 +1384,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 	 * ordering would succeed.  XXX FIXME: use a UNION-FIND algorithm similar
 	 * to the way we build merged ECs.  (Use a list-of-lists for each rel.)
 	 */
-	prev_ems = (EquivalenceMember **)
-		palloc0(root->simple_rel_array_size * sizeof(EquivalenceMember *));
+	prev_ems = stack_buffer_alloc0_array(EquivalenceMember *,
+										 root->simple_rel_array_size);
 
 	/* We don't expect any children yet */
 	Assert(ec->ec_childmembers == NULL);
@@ -1444,7 +1447,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 		prev_ems[relid] = cur_em;
 	}
 
-	pfree(prev_ems);
+	stack_buffer_free(prev_ems);
 
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 67d9dc35f44..b01c48650c8 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -34,6 +34,7 @@
 #include "optimizer/restrictinfo.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
+#include "utils/stack_buffer.h"
 
 
 /* XXX see PartCollMatchesExprColl */
@@ -1281,6 +1282,8 @@ group_similar_or_args(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo)
 	List	   *result = NIL;
 	Index		relid = rel->relid;
 
+	DECLARE_STACK_BUFFER();
+
 	Assert(IsA(rinfo->orclause, BoolExpr));
 	orargs = ((BoolExpr *) rinfo->orclause)->args;
 	n = list_length(orargs);
@@ -1291,7 +1294,7 @@ group_similar_or_args(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo)
 	 * which will be used to sort these arguments at the next step.
 	 */
 	i = -1;
-	matches = palloc_array(OrArgIndexMatch, n);
+	matches = stack_buffer_alloc_array(OrArgIndexMatch, n);
 	foreach(lc, orargs)
 	{
 		Node	   *arg = lfirst(lc);
@@ -1421,7 +1424,7 @@ group_similar_or_args(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo)
 	 */
 	if (!matched)
 	{
-		pfree(matches);
+		stack_buffer_free(matches);
 		return orargs;
 	}
 
@@ -1526,7 +1529,7 @@ group_similar_or_args(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo)
 			group_start = i;
 		}
 	}
-	pfree(matches);
+	stack_buffer_free(matches);
 	return result;
 }
 
@@ -1794,6 +1797,8 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
 				j;
 	ListCell   *l;
 
+	DECLARE_STACK_BUFFER();
+
 	Assert(npaths > 0);			/* else caller error */
 	if (npaths == 1)
 		return (Path *) linitial(paths);	/* easy case */
@@ -1853,7 +1858,7 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
 	 * same set of clauses; keep only the cheapest-to-scan of any such groups.
 	 * The surviving paths are put into an array for qsort'ing.
 	 */
-	pathinfoarray = palloc_array(PathClauseUsage *, npaths);
+	pathinfoarray = stack_buffer_alloc_array(PathClauseUsage *, npaths);
 	clauselist = NIL;
 	npaths = 0;
 	foreach(l, paths)
@@ -1979,6 +1984,7 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
 		/* some easy cleanup (we don't try real hard though) */
 		list_free(qualsofar);
 	}
+	stack_buffer_free(pathinfoarray);
 
 	if (list_length(bestpaths) == 1)
 		return (Path *) linitial(bestpaths);	/* no need for AND */
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 5eb71635d15..a0e3194f6fd 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -26,6 +26,7 @@
 #include "optimizer/paths.h"
 #include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/stack_buffer.h"
 #include "utils/lsyscache.h"
 
 /* Consider reordering of GROUP BY keys? */
@@ -1668,6 +1669,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	ListCell   *lc;
 	int			j;
 
+	DECLARE_STACK_BUFFER();
+
 	/* Might have no mergeclauses */
 	if (nClauses == 0)
 		return NIL;
@@ -1676,8 +1679,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	 * Make arrays of the ECs used by the mergeclauses (dropping any
 	 * duplicates) and their "popularity" scores.
 	 */
-	ecs = (EquivalenceClass **) palloc(nClauses * sizeof(EquivalenceClass *));
-	scores = (int *) palloc(nClauses * sizeof(int));
+	ecs = stack_buffer_alloc_array(EquivalenceClass *, nClauses);
+	scores = stack_buffer_alloc_array(int, nClauses);
 	necs = 0;
 
 	foreach(lc, mergeclauses)
@@ -1784,8 +1787,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 			pathkeys = list_copy_head(root->query_pathkeys, matches);
 
 			/* we have all of the join pathkeys, so nothing more to do */
-			pfree(ecs);
-			pfree(scores);
+			stack_buffer_free(ecs);
+			stack_buffer_free(scores);
 
 			return pathkeys;
 		}
@@ -1827,8 +1830,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 		pathkeys = lappend(pathkeys, pathkey);
 	}
 
-	pfree(ecs);
-	pfree(scores);
+	stack_buffer_free(ecs);
+	stack_buffer_free(scores);
 
 	return pathkeys;
 }
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 12e9ed0d0c7..e3d462af416 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -34,6 +34,7 @@
 #include "parser/parse_agg.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
+#include "utils/stack_buffer.h"
 
 /*
  * Utility structure.  A sorting procedure is needed to simplify the search
@@ -2314,6 +2315,8 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove)
 	int			j;
 	int			numRels;
 
+	DECLARE_STACK_BUFFER();
+
 	/* Collect indexes of base relations of the join tree */
 	foreach(jl, joinlist)
 	{
@@ -2363,7 +2366,7 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove)
 	 * In order to find relations with the same oid we first build an array of
 	 * candidates and then sort it by oid.
 	 */
-	candidates = palloc_array(SelfJoinCandidate, numRels);
+	candidates = stack_buffer_alloc_array(SelfJoinCandidate, numRels);
 	i = -1;
 	j = 0;
 	while ((i = bms_next_member(relids, i)) >= 0)
@@ -2431,6 +2434,8 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove)
 
 	Assert(bms_is_empty(relids));
 
+	stack_buffer_free(candidates);
+
 	return toRemove;
 }
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 42604a0f75c..24ad41d4ec6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -63,6 +63,7 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
+#include "utils/stack_buffer.h"
 
 /* GUC parameters */
 double		cursor_tuple_fraction = DEFAULT_CURSOR_TUPLE_FRACTION;
@@ -3020,6 +3021,8 @@ extract_rollup_sets(List *groupingSets)
 	ListCell   *lc1 = list_head(groupingSets);
 	ListCell   *lc;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * Start by stripping out empty sets.  The algorithm doesn't require this,
 	 * but the planner currently needs all empty sets to be returned in the
@@ -3053,10 +3056,10 @@ extract_rollup_sets(List *groupingSets)
 	 * to leave 0 free for the NIL node in the graph algorithm.
 	 *----------
 	 */
-	orig_sets = palloc0((num_sets_raw + 1) * sizeof(List *));
-	set_masks = palloc0((num_sets_raw + 1) * sizeof(Bitmapset *));
-	adjacency = palloc0((num_sets_raw + 1) * sizeof(short *));
-	adjacency_buf = palloc((num_sets_raw + 1) * sizeof(short));
+	orig_sets = stack_buffer_alloc0_array(List *, num_sets_raw + 1);
+	set_masks = stack_buffer_alloc0_array(Bitmapset *, num_sets_raw + 1);
+	adjacency = stack_buffer_alloc0_array(short *, num_sets_raw + 1);
+	adjacency_buf = stack_buffer_alloc_array(short, num_sets_raw + 1);
 
 	j_size = 0;
 	j = 0;
@@ -3141,7 +3144,7 @@ extract_rollup_sets(List *groupingSets)
 	 * pair_vu[v] = u (both will be true, but we check both so that we can do
 	 * it in one pass)
 	 */
-	chains = palloc0((num_sets + 1) * sizeof(int));
+	chains = stack_buffer_alloc0_array(int, num_sets + 1);
 
 	for (i = 1; i <= num_sets; ++i)
 	{
@@ -3157,7 +3160,7 @@ extract_rollup_sets(List *groupingSets)
 	}
 
 	/* build result lists. */
-	results = palloc0((num_chains + 1) * sizeof(List *));
+	results = stack_buffer_alloc0_array(List *, (num_chains + 1));
 
 	for (i = 1; i <= num_sets; ++i)
 	{
@@ -3183,17 +3186,17 @@ extract_rollup_sets(List *groupingSets)
 	 * tied up a nontrivial amount of memory.)
 	 */
 	BipartiteMatchFree(state);
-	pfree(results);
-	pfree(chains);
+	stack_buffer_free(results);
+	stack_buffer_free(chains);
 	for (i = 1; i <= num_sets; ++i)
 		if (adjacency[i])
 			pfree(adjacency[i]);
-	pfree(adjacency);
-	pfree(adjacency_buf);
-	pfree(orig_sets);
+	stack_buffer_free(adjacency);
+	stack_buffer_free(adjacency_buf);
+	stack_buffer_free(orig_sets);
 	for (i = 1; i <= num_sets; ++i)
 		bms_free(set_masks[i]);
-	pfree(set_masks);
+	stack_buffer_free(set_masks);
 
 	return result;
 }
@@ -6033,8 +6036,12 @@ select_active_windows(PlannerInfo *root, WindowFuncLists *wflists)
 	List	   *result = NIL;
 	ListCell   *lc;
 	int			nActive = 0;
-	WindowClauseSortData *actives = palloc_array(WindowClauseSortData,
-												 list_length(windowClause));
+	WindowClauseSortData *actives;
+
+	DECLARE_STACK_BUFFER();
+
+	actives = stack_buffer_alloc_array(WindowClauseSortData,
+									   list_length(windowClause));
 
 	/* First, construct an array of the active windows */
 	foreach(lc, windowClause)
@@ -6093,7 +6100,7 @@ select_active_windows(PlannerInfo *root, WindowFuncLists *wflists)
 	for (int i = 0; i < nActive; i++)
 		result = lappend(result, actives[i].wc);
 
-	pfree(actives);
+	stack_buffer_free(actives);
 
 	return result;
 }
diff --git a/src/backend/optimizer/prep/prepagg.c b/src/backend/optimizer/prep/prepagg.c
index 3737cc15ba1..f1d0466504a 100644
--- a/src/backend/optimizer/prep/prepagg.c
+++ b/src/backend/optimizer/prep/prepagg.c
@@ -49,6 +49,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/stack_buffer.h"
 #include "utils/syscache.h"
 
 static bool preprocess_aggrefs_walker(Node *node, PlannerInfo *root);
@@ -525,11 +526,13 @@ GetAggInitVal(Datum textInitVal, Oid transtype)
 	char	   *strInitVal;
 	Datum		initVal;
 
+	DECLARE_STACK_BUFFER();
+
 	getTypeInputInfo(transtype, &typinput, &typioparam);
-	strInitVal = TextDatumGetCString(textInitVal);
+	strInitVal = stack_buffer_text_datum_to_cstring(textInitVal);
 	initVal = OidInputFunctionCall(typinput, strInitVal,
 								   typioparam, -1);
-	pfree(strInitVal);
+	stack_buffer_free(strInitVal);
 	return initVal;
 }
 
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f50c296e3d9..9816e315bab 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -38,6 +38,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
 #include "utils/selfuncs.h"
+#include "utils/stack_buffer.h"
 
 
 static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1628,13 +1629,15 @@ generate_append_tlist(List *colTypes, List *colCollations,
 	ListCell   *tlistl;
 	int32	   *colTypmods;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * First extract typmods to use.
 	 *
 	 * If the inputs all agree on type and typmod of a particular column, use
 	 * that typmod; else use -1.
 	 */
-	colTypmods = palloc_array(int32, list_length(colTypes));
+	colTypmods = stack_buffer_alloc_array(int32, list_length(colTypes));
 
 	foreach(tlistl, input_tlists)
 	{
@@ -1705,7 +1708,7 @@ generate_append_tlist(List *colTypes, List *colCollations,
 		tlist = lappend(tlist, tle);
 	}
 
-	pfree(colTypmods);
+	stack_buffer_free(colTypmods);
 
 	return tlist;
 }
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f0f8e2515ec..4b50c56991d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -59,6 +59,7 @@
 #include "utils/jsonpath.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/stack_buffer.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -5069,11 +5070,13 @@ fetch_function_defaults(HeapTuple func_tuple)
 	Datum		proargdefaults;
 	char	   *str;
 
+	DECLARE_STACK_BUFFER();
+
 	proargdefaults = SysCacheGetAttrNotNull(PROCOID, func_tuple,
 											Anum_pg_proc_proargdefaults);
-	str = TextDatumGetCString(proargdefaults);
+	str = stack_buffer_text_datum_to_cstring(proargdefaults);
 	defaults = castNode(List, stringToNode(str));
-	pfree(str);
+	stack_buffer_free(str);
 	return defaults;
 }
 
@@ -6273,10 +6276,12 @@ make_SAOP_expr(Oid oper, Node *leftexpr, Oid coltype, Oid arraycollid,
 		int			dims[1] = {list_length(exprs)};
 		int			lbs[1] = {1};
 
+		DECLARE_STACK_BUFFER();
+
 		get_typlenbyvalalign(coltype, &typlen, &typbyval, &typalign);
 
-		elems = palloc_array(Datum, list_length(exprs));
-		nulls = palloc_array(bool, list_length(exprs));
+		elems = stack_buffer_alloc_array(Datum, list_length(exprs));
+		nulls = stack_buffer_alloc_array(bool, list_length(exprs));
 		foreach_node(Const, value, exprs)
 		{
 			elems[i] = value->constvalue;
@@ -6289,8 +6294,8 @@ make_SAOP_expr(Oid oper, Node *leftexpr, Oid coltype, Oid arraycollid,
 									   -1, PointerGetDatum(arrayConst),
 									   false, false);
 
-		pfree(elems);
-		pfree(nulls);
+		stack_buffer_free(elems);
+		stack_buffer_free(nulls);
 		list_free(exprs);
 	}
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b2fbd6a082b..a080ba0f241 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -51,6 +51,7 @@
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
+#include "utils/stack_buffer.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
@@ -1788,9 +1789,11 @@ get_relation_statistics(PlannerInfo *root, RelOptInfo *rel,
 			{
 				char	   *exprsString;
 
-				exprsString = TextDatumGetCString(datum);
+				DECLARE_STACK_BUFFER();
+
+				exprsString = stack_buffer_text_datum_to_cstring(datum);
 				exprs = (List *) stringToNode(exprsString);
-				pfree(exprsString);
+				stack_buffer_free(exprsString);
 
 				/*
 				 * Modify the copies we obtain from the relcache to have the
-- 
2.53.0

