Skip to content

Commit 8571ecb

Browse files
committed
Fix JSON aggregates to work properly when final function is re-executed.
Davide S. reported that json_agg() sometimes produced multiple trailing right brackets. This turns out to be because json_agg_finalfn() attaches the final right bracket, and was doing so by modifying the aggregate state in-place. That's verboten, though unfortunately it seems there's no way for nodeAgg.c to check for such mistakes. Fix that back to 9.3 where the broken code was introduced. In 9.4 and HEAD, likewise fix json_object_agg(), which had copied the erroneous logic. Make some cosmetic cleanups as well.
1 parent 10b81fb commit 8571ecb

File tree

1 file changed

+28
-4
lines changed

1 file changed

+28
-4
lines changed

src/backend/utils/adt/json.c

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ static void json_categorize_type(Oid typoid,
8585
Oid *outfuncoid);
8686
static void datum_to_json(Datum val, bool is_null, StringInfo result,
8787
JsonTypeCategory tcategory, Oid outfuncoid);
88+
static text *catenate_stringinfo_string(StringInfo buffer, const char *addon);
8889

8990
/* the null action object used for pure validation */
9091
static JsonSemAction nullSemAction =
@@ -166,7 +167,7 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
166167

167168
/* utility function to check if a string is a valid JSON number */
168169
extern bool
169-
IsValidJsonNumber(const char * str, int len)
170+
IsValidJsonNumber(const char *str, int len)
170171
{
171172
bool numeric_error;
172173
JsonLexContext dummy_lex;
@@ -191,7 +192,7 @@ IsValidJsonNumber(const char * str, int len)
191192

192193
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
193194

194-
return ! numeric_error;
195+
return !numeric_error;
195196
}
196197

197198
/*
@@ -1359,6 +1360,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
13591360
break;
13601361
case JSONTYPE_NUMERIC:
13611362
outputstr = OidOutputFunctionCall(outfuncoid, val);
1363+
13621364
/*
13631365
* Don't call escape_json for a non-key if it's a valid JSON
13641366
* number.
@@ -1646,6 +1648,8 @@ to_json(PG_FUNCTION_ARGS)
16461648

16471649
/*
16481650
* json_agg transition function
1651+
*
1652+
* aggregate input column as a json array value.
16491653
*/
16501654
Datum
16511655
json_agg_transfn(PG_FUNCTION_ARGS)
@@ -1732,12 +1736,32 @@ json_agg_finalfn(PG_FUNCTION_ARGS)
17321736

17331737
state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
17341738

1739+
/* NULL result for no rows in, as is standard with aggregates */
17351740
if (state == NULL)
17361741
PG_RETURN_NULL();
17371742

1738-
appendStringInfoChar(state, ']');
1743+
/* Else return state with appropriate array terminator added */
1744+
PG_RETURN_TEXT_P(catenate_stringinfo_string(state, "]"));
1745+
}
1746+
1747+
/*
1748+
* Helper function for aggregates: return given StringInfo's contents plus
1749+
* specified trailing string, as a text datum. We need this because aggregate
1750+
* final functions are not allowed to modify the aggregate state.
1751+
*/
1752+
static text *
1753+
catenate_stringinfo_string(StringInfo buffer, const char *addon)
1754+
{
1755+
/* custom version of cstring_to_text_with_len */
1756+
int buflen = buffer->len;
1757+
int addlen = strlen(addon);
1758+
text *result = (text *) palloc(buflen + addlen + VARHDRSZ);
1759+
1760+
SET_VARSIZE(result, buflen + addlen + VARHDRSZ);
1761+
memcpy(VARDATA(result), buffer->data, buflen);
1762+
memcpy(VARDATA(result) + buflen, addon, addlen);
17391763

1740-
PG_RETURN_TEXT_P(cstring_to_text(state->data));
1764+
return result;
17411765
}
17421766

17431767
/*

0 commit comments

Comments
 (0)