Skip to content

Commit a1fc364

Browse files
committed
Fix handling of nested JSON objects in json_populate_recordset and friends.
populate_recordset_object_start() improperly created a new hash table (overwriting the link to the existing one) if called at nest levels greater than one. This resulted in previous fields not appearing in the final output, as reported by Matti Hameister in bug #10728. In 9.4 the problem also affects json_to_recordset. This perhaps missed detection earlier because the default behavior is to throw an error for nested objects: you have to pass use_json_as_text = true to see the problem. In addition, fix query-lifespan leakage of the hashtable created by json_populate_record(). This is pretty much the same problem recently fixed in dblink: creating an intended-to-be-temporary context underneath the executor's per-tuple context isn't enough to make it go away at the end of the tuple cycle, because MemoryContextReset is not MemoryContextResetAndDeleteChildren. Michael Paquier and Tom Lane
1 parent cc58418 commit a1fc364

File tree

4 files changed

+38
-6
lines changed

4 files changed

+38
-6
lines changed

src/backend/utils/adt/jsonfuncs.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,8 +1282,10 @@ json_populate_record(PG_FUNCTION_ARGS)
12821282
* nulls.
12831283
*/
12841284
if (hash_get_num_entries(json_hash) == 0 && rec)
1285+
{
1286+
hash_destroy(json_hash);
12851287
PG_RETURN_POINTER(rec);
1286-
1288+
}
12871289

12881290
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
12891291
ncolumns = tupdesc->natts;
@@ -1408,6 +1410,8 @@ json_populate_record(PG_FUNCTION_ARGS)
14081410

14091411
ReleaseTupleDesc(tupdesc);
14101412

1413+
hash_destroy(json_hash);
1414+
14111415
PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
14121416
}
14131417

@@ -1698,16 +1702,23 @@ populate_recordset_object_start(void *state)
16981702
int lex_level = _state->lex->lex_level;
16991703
HASHCTL ctl;
17001704

1705+
/* Reject object at top level: we must have an array at level 0 */
17011706
if (lex_level == 0)
17021707
ereport(ERROR,
17031708
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17041709
errmsg("cannot call json_populate_recordset on an object")));
1705-
else if (lex_level > 1 && !_state->use_json_as_text)
1706-
ereport(ERROR,
1707-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1708-
errmsg("cannot call json_populate_recordset with nested objects")));
17091710

1710-
/* set up a new hash for this entry */
1711+
/* Nested objects, if allowed, require no special processing */
1712+
if (lex_level > 1)
1713+
{
1714+
if (!_state->use_json_as_text)
1715+
ereport(ERROR,
1716+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1717+
errmsg("cannot call json_populate_recordset with nested objects")));
1718+
return;
1719+
}
1720+
1721+
/* Object at level 1: set up a new hash table for this object */
17111722
memset(&ctl, 0, sizeof(ctl));
17121723
ctl.keysize = NAMEDATALEN;
17131724
ctl.entrysize = sizeof(JsonHashEntry);
@@ -1734,9 +1745,11 @@ populate_recordset_object_end(void *state)
17341745
HeapTupleHeader rec = _state->rec;
17351746
HeapTuple rettuple;
17361747

1748+
/* Nested objects require no special processing */
17371749
if (_state->lex->lex_level > 1)
17381750
return;
17391751

1752+
/* Otherwise, construct and return a tuple based on this level-1 object */
17401753
values = (Datum *) palloc(ncolumns * sizeof(Datum));
17411754
nulls = (bool *) palloc(ncolumns * sizeof(bool));
17421755

@@ -1828,7 +1841,9 @@ populate_recordset_object_end(void *state)
18281841

18291842
tuplestore_puttuple(_state->tuple_store, rettuple);
18301843

1844+
/* Done with hash for this object */
18311845
hash_destroy(json_hash);
1846+
_state->json_hash = NULL;
18321847
}
18331848

18341849
static void

src/test/regress/expected/json.out

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,13 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
901901

902902
select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
903903
ERROR: invalid input syntax for type timestamp: "[100,200,300]"
904+
create type jpop2 as (a int, b json, c int, d int);
905+
select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
906+
a | b | c | d
907+
---+---------+---+---
908+
2 | {"z":4} | 3 | 6
909+
(1 row)
910+
904911
-- using the default use_json_as_text argument
905912
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
906913
a | b | c

src/test/regress/expected/json_1.out

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,13 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
901901

902902
select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
903903
ERROR: invalid input syntax for type timestamp: "[100,200,300]"
904+
create type jpop2 as (a int, b json, c int, d int);
905+
select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
906+
a | b | c | d
907+
---+---------+---+---
908+
2 | {"z":4} | 3 | 6
909+
(1 row)
910+
904911
-- using the default use_json_as_text argument
905912
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
906913
a | b | c

src/test/regress/sql/json.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","
290290
select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
291291
select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
292292

293+
create type jpop2 as (a int, b json, c int, d int);
294+
select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
295+
293296
-- using the default use_json_as_text argument
294297

295298
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;

0 commit comments

Comments
 (0)