From 65a8c0112e3bd2bce3c20fd5333d7fe205b5088d Mon Sep 17 00:00:00 2001
From: shihao zhong <zhong950419@gmail.com>
Date: Mon, 12 Oct 2026 03:14:06 -0400
Subject: [PATCH v1 1/2] Fix jsonb_object_agg() used as a window function.

The final function sorted and shrank the stored pairs array in place.
When it ran once per row, a later duplicate key could lose to an older
value. Make the final function work on a copy of the pairs. The copy is
shallow and costs little next to building the result.
---
 src/backend/utils/adt/jsonb.c | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da0061ba1b8..cf1e89e244e 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1709,6 +1709,7 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
 	JsonbAggState *arg;
 	JsonbInState result;
 	Jsonb	   *out;
+	int			npairs;
 
 	/* cannot be called directly because of internal-type argument */
 	Assert(AggCheckCallContext(fcinfo, NULL));
@@ -1719,17 +1720,23 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
 	arg = (JsonbAggState *) PG_GETARG_POINTER(0);
 
 	/*
-	 * The final function can be called more than once, so we must not change
-	 * the stored JsonbValue data structure.  Fortunately, the WJB_END_OBJECT
-	 * action will only destructively change fields in the JsonbInState struct
-	 * itself, so we can simply invoke pushJsonbValue on a local copy of that.
-	 * Note that this will run uniqueifyJsonbObject each time; that's hard to
-	 * avoid, since duplicate pairs may have been added since the previous
-	 * finalization.  We assume uniqueifyJsonbObject can be applied repeatedly
-	 * (with the same unique_keys/skip_nulls options) without damaging the
-	 * data structure.
+	 * The final function can be called more than once, for example once per
+	 * row in a window, so the stored state must not change.  The
+	 * WJB_END_OBJECT action runs uniqueifyJsonbObject, which sorts and
+	 * shrinks the pairs array in place, so work on a copy of the top parse
+	 * state and its pairs. The object is the only open container, so there is
+	 * no outer state.
 	 */
+	Assert(arg->pstate.parseState != NULL &&
+		   arg->pstate.parseState->next == NULL);
 	result = arg->pstate;
+	result.parseState = palloc_object(JsonbParseState);
+	*result.parseState = *arg->pstate.parseState;
+	npairs = arg->pstate.parseState->contVal.val.object.nPairs;
+	result.parseState->contVal.val.object.pairs = palloc_array(JsonbPair, npairs);
+	memcpy(result.parseState->contVal.val.object.pairs,
+		   arg->pstate.parseState->contVal.val.object.pairs,
+		   npairs * sizeof(JsonbPair));
 
 	pushJsonbValue(&result, WJB_END_OBJECT, NULL);
 
-- 
2.37.1 (Apple Git-137.1)

