Skip to content

Commit 597e41e

Browse files
committed
Fix json_to_record() bug with nested objects.
A thinko concerning nesting depth caused json_to_record() to produce bogus output if a field of its input object contained a sub-object with a field name matching one of the requested output column names. Per bug #13996 from Johann Visagie. I added a regression test case based on his example, plus parallel tests for json_to_recordset, jsonb_to_record, jsonb_to_recordset. The latter three do not exhibit the same bug (which suggests that we may be missing some opportunities to share code...) but testing seems like a good idea in any case. Back-patch to 9.4 where these functions were introduced.
1 parent 55965eb commit 597e41e

File tree

5 files changed

+49
-2
lines changed

5 files changed

+49
-2
lines changed

src/backend/utils/adt/jsonfuncs.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -2350,7 +2350,7 @@ hash_object_field_end(void *state, char *fname, bool isnull)
23502350
/*
23512351
* Ignore nested fields.
23522352
*/
2353-
if (_state->lex->lex_level > 2)
2353+
if (_state->lex->lex_level > 1)
23542354
return;
23552355

23562356
/*

src/test/regress/expected/json.out

+16
Original file line numberDiff line numberDiff line change
@@ -1552,3 +1552,19 @@ select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":fa
15521552
2 | {"d":"bar"} | f
15531553
(2 rows)
15541554

1555+
select *, c is null as c_is_null
1556+
from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json)
1557+
as t(a int, b json, c text, x int);
1558+
a | b | c | x | c_is_null
1559+
---+-----------------+---+---+-----------
1560+
1 | {"c":16, "d":2} | | 8 | t
1561+
(1 row)
1562+
1563+
select *, c is null as c_is_null
1564+
from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json)
1565+
as t(a int, b json, c text, x int);
1566+
a | b | c | x | c_is_null
1567+
---+-----------------+---+---+-----------
1568+
1 | {"c":16, "d":2} | | 8 | t
1569+
(1 row)
1570+

src/test/regress/expected/jsonb.out

+16
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,22 @@ select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar",
17601760
2 | bar | t
17611761
(2 rows)
17621762

1763+
select *, c is null as c_is_null
1764+
from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb)
1765+
as t(a int, b jsonb, c text, x int);
1766+
a | b | c | x | c_is_null
1767+
---+-------------------+---+---+-----------
1768+
1 | {"c": 16, "d": 2} | | 8 | t
1769+
(1 row)
1770+
1771+
select *, c is null as c_is_null
1772+
from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb)
1773+
as t(a int, b jsonb, c text, x int);
1774+
a | b | c | x | c_is_null
1775+
---+-------------------+---+---+-----------
1776+
1 | {"c": 16, "d": 2} | | 8 | t
1777+
(1 row)
1778+
17631779
-- indexing
17641780
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
17651781
count

src/test/regress/sql/json.sql

+8-1
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,6 @@ select json_object('{a,b,NULL,"d e f"}','{1,2,3,"a b c"}');
487487

488488
select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
489489

490-
491490
-- json_to_record and json_to_recordset
492491

493492
select * from json_to_record('{"a":1,"b":"foo","c":"bar"}')
@@ -498,3 +497,11 @@ select * from json_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","
498497

499498
select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]')
500499
as x(a int, b json, c boolean);
500+
501+
select *, c is null as c_is_null
502+
from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json)
503+
as t(a int, b json, c text, x int);
504+
505+
select *, c is null as c_is_null
506+
from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json)
507+
as t(a int, b json, c text, x int);

src/test/regress/sql/jsonb.sql

+8
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,14 @@ select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
372372
select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","c":true}]')
373373
as x(a int, b text, c boolean);
374374

375+
select *, c is null as c_is_null
376+
from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb)
377+
as t(a int, b jsonb, c text, x int);
378+
379+
select *, c is null as c_is_null
380+
from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb)
381+
as t(a int, b jsonb, c text, x int);
382+
375383
-- indexing
376384
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
377385
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';

0 commit comments

Comments
 (0)