diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 4eeeeaf0a6..058aade2af 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -1111,7 +1111,14 @@ json_object_agg_transfn_worker(FunctionCallInfo fcinfo, if (unique_keys) { - const char *key = &out->data[key_offset]; + /* + * Copy the key first, instead of pointing into the buffer. It will be + * added to the hash table, but the buffer may get reallocated as + * we're appending more data to it. That would invalidate pointers to + * keys in the current buffer. + */ + const char *key = MemoryContextStrdup(aggcontext, + &out->data[key_offset]); if (!json_unique_check_key(&state->unique_check.check, key, 0)) ereport(ERROR, @@ -1274,8 +1281,15 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const if (unique_keys) { - /* check key uniqueness after key appending */ - const char *key = &out->data[key_offset]; + /* + * check key uniqueness after key appending + * + * Copy the key first, instead of pointing into the buffer. It + * will be added to the hash table, but the buffer may get + * reallocated as we're appending more data to it. That would + * invalidate pointers to keys in the current buffer. + */ + const char *key = pstrdup(&out->data[key_offset]); if (!json_unique_check_key(&unique_check.check, key, 0)) ereport(ERROR, diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 7df11c2f38..96c40911cb 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -2330,6 +2330,9 @@ select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}'); {"a" : "1", "b" : "2", "" : "3", "d e f" : "a b c"} (1 row) +-- json_object_agg_unique requires unique keys +select json_object_agg_unique(mod(i,100), i) from generate_series(0, 199) i; +ERROR: duplicate JSON object key value: "0" -- json_to_record and json_to_recordset select * from json_to_record('{"a":1,"b":"foo","c":"bar"}') as x(a int, b text, d text); diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 4f91e2117e..9adfbe15f6 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -537,6 +537,8 @@ SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL); {"a" : "1", "c" : 2} (1 row) +SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, repeat('x', 1000): 1, 2: repeat('a', 100) WITH UNIQUE); +ERROR: duplicate JSON object key value: "2" SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE); ERROR: duplicate JSON object key value: "1" SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE); @@ -921,6 +923,9 @@ FROM (VALUES (1, 1), (0, NULL),(4, null), (5, null),(6, null),(2, 2)) foo(k, v); {"1": 1, "2": 2} (1 row) +SELECT JSON_OBJECTAGG(mod(i,100): (i)::text FORMAT JSON WITH UNIQUE) +FROM generate_series(0, 199) i; +ERROR: duplicate JSON object key value: "0" -- Test JSON_OBJECT deparsing EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 5c886cd6b3..8251f4f400 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -755,6 +755,8 @@ select json_object('{a,b,NULL,"d e f"}','{1,2,3,"a b c"}'); select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}'); +-- json_object_agg_unique requires unique keys +select json_object_agg_unique(mod(i,100), i) from generate_series(0, 199) i; -- json_to_record and json_to_recordset diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index bb2487e864..42dbec26d6 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -138,6 +138,8 @@ SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL); SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL); +SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, repeat('x', 1000): 1, 2: repeat('a', 100) WITH UNIQUE); + SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE); SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE); SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb); @@ -283,6 +285,9 @@ FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) FROM (VALUES (1, 1), (0, NULL),(4, null), (5, null),(6, null),(2, 2)) foo(k, v); +SELECT JSON_OBJECTAGG(mod(i,100): (i)::text FORMAT JSON WITH UNIQUE) +FROM generate_series(0, 199) i; + -- Test JSON_OBJECT deparsing EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);