From 65a8c0112e3bd2bce3c20fd5333d7fe205b5088d Mon Sep 17 00:00:00 2001 From: shihao zhong 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)