Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 57d8c12

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 0f74827 commit 57d8c12

File tree

4 files changed

+58
-6
lines changed

4 files changed

+58
-6
lines changed

src/backend/utils/adt/jsonfuncs.c

+22-6
Original file line numberDiff line numberDiff line change
@@ -2075,8 +2075,10 @@ populate_record_worker(FunctionCallInfo fcinfo, bool have_record_arg)
20752075
* with domain nulls.
20762076
*/
20772077
if (hash_get_num_entries(json_hash) == 0 && rec)
2078+
{
2079+
hash_destroy(json_hash);
20782080
PG_RETURN_POINTER(rec);
2079-
2081+
}
20802082
}
20812083
else
20822084
{
@@ -2250,6 +2252,9 @@ populate_record_worker(FunctionCallInfo fcinfo, bool have_record_arg)
22502252

22512253
ReleaseTupleDesc(tupdesc);
22522254

2255+
if (json_hash)
2256+
hash_destroy(json_hash);
2257+
22532258
PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
22542259
}
22552260

@@ -2735,16 +2740,23 @@ populate_recordset_object_start(void *state)
27352740
int lex_level = _state->lex->lex_level;
27362741
HASHCTL ctl;
27372742

2743+
/* Reject object at top level: we must have an array at level 0 */
27382744
if (lex_level == 0)
27392745
ereport(ERROR,
27402746
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
27412747
errmsg("cannot call json_populate_recordset on an object")));
2742-
else if (lex_level > 1 && !_state->use_json_as_text)
2743-
ereport(ERROR,
2744-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2745-
errmsg("cannot call json_populate_recordset with nested objects")));
27462748

2747-
/* set up a new hash for this entry */
2749+
/* Nested objects, if allowed, require no special processing */
2750+
if (lex_level > 1)
2751+
{
2752+
if (!_state->use_json_as_text)
2753+
ereport(ERROR,
2754+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2755+
errmsg("cannot call json_populate_recordset with nested objects")));
2756+
return;
2757+
}
2758+
2759+
/* Object at level 1: set up a new hash table for this object */
27482760
memset(&ctl, 0, sizeof(ctl));
27492761
ctl.keysize = NAMEDATALEN;
27502762
ctl.entrysize = sizeof(JsonHashEntry);
@@ -2771,9 +2783,11 @@ populate_recordset_object_end(void *state)
27712783
HeapTupleHeader rec = _state->rec;
27722784
HeapTuple rettuple;
27732785

2786+
/* Nested objects require no special processing */
27742787
if (_state->lex->lex_level > 1)
27752788
return;
27762789

2790+
/* Otherwise, construct and return a tuple based on this level-1 object */
27772791
values = (Datum *) palloc(ncolumns * sizeof(Datum));
27782792
nulls = (bool *) palloc(ncolumns * sizeof(bool));
27792793

@@ -2865,7 +2879,9 @@ populate_recordset_object_end(void *state)
28652879

28662880
tuplestore_puttuple(_state->tuple_store, rettuple);
28672881

2882+
/* Done with hash for this object */
28682883
hash_destroy(json_hash);
2884+
_state->json_hash = NULL;
28692885
}
28702886

28712887
static void

src/test/regress/expected/json.out

+15
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,13 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
10071007

10081008
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;
10091009
ERROR: invalid input syntax for type timestamp: "[100,200,300]"
1010+
create type jpop2 as (a int, b json, c int, d int);
1011+
select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
1012+
a | b | c | d
1013+
---+---------+---+---
1014+
2 | {"z":4} | 3 | 6
1015+
(1 row)
1016+
10101017
-- using the default use_json_as_text argument
10111018
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
10121019
a | b | c
@@ -1223,3 +1230,11 @@ select * from json_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","
12231230
2 | bar | t
12241231
(2 rows)
12251232

1233+
select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]', true)
1234+
as x(a int, b json, c boolean);
1235+
a | b | c
1236+
---+-------------+---
1237+
1 | {"d":"foo"} | t
1238+
2 | {"d":"bar"} | f
1239+
(2 rows)
1240+

src/test/regress/expected/json_1.out

+15
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,13 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
10071007

10081008
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;
10091009
ERROR: invalid input syntax for type timestamp: "[100,200,300]"
1010+
create type jpop2 as (a int, b json, c int, d int);
1011+
select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
1012+
a | b | c | d
1013+
---+---------+---+---
1014+
2 | {"z":4} | 3 | 6
1015+
(1 row)
1016+
10101017
-- using the default use_json_as_text argument
10111018
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
10121019
a | b | c
@@ -1219,3 +1226,11 @@ select * from json_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","
12191226
2 | bar | t
12201227
(2 rows)
12211228

1229+
select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]', true)
1230+
as x(a int, b json, c boolean);
1231+
a | b | c
1232+
---+-------------+---
1233+
1 | {"d":"foo"} | t
1234+
2 | {"d":"bar"} | f
1235+
(2 rows)
1236+

src/test/regress/sql/json.sql

+6
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,9 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","
325325
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;
326326
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;
327327

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

330333
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
@@ -447,3 +450,6 @@ select * from json_to_record('{"a":1,"b":"foo","c":"bar"}',true)
447450

448451
select * from json_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","c":true}]',false)
449452
as x(a int, b text, c boolean);
453+
454+
select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]', true)
455+
as x(a int, b json, c boolean);

0 commit comments

Comments
 (0)