From a9d199f6d3b998929cdb8e8aa61e5cd8db9b220f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 2 Mar 2016 23:31:39 -0500 Subject: [PATCH] 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. --- src/backend/utils/adt/jsonfuncs.c | 2 +- src/test/regress/expected/json.out | 16 ++++++++++++++++ src/test/regress/expected/jsonb.out | 16 ++++++++++++++++ src/test/regress/sql/json.sql | 7 +++++++ src/test/regress/sql/jsonb.sql | 8 ++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 88225aae4d..363afa7699 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -2438,7 +2438,7 @@ hash_object_field_end(void *state, char *fname, bool isnull) /* * Ignore nested fields. */ - if (_state->lex->lex_level > 2) + if (_state->lex->lex_level > 1) return; /* diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 502f983889..efcdc4141e 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -1599,6 +1599,22 @@ select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":fa 2 | {"d":"bar"} | f (2 rows) +select *, c is null as c_is_null +from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json) + as t(a int, b json, c text, x int); + a | b | c | x | c_is_null +---+-----------------+---+---+----------- + 1 | {"c":16, "d":2} | | 8 | t +(1 row) + +select *, c is null as c_is_null +from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json) + as t(a int, b json, c text, x int); + a | b | c | x | c_is_null +---+-----------------+---+---+----------- + 1 | {"c":16, "d":2} | | 8 | t +(1 row) + -- json_strip_nulls select json_strip_nulls(null); json_strip_nulls diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 4789e4e57b..416918dd9f 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2001,6 +2001,22 @@ select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar", 2 | bar | t (2 rows) +select *, c is null as c_is_null +from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb) + as t(a int, b jsonb, c text, x int); + a | b | c | x | c_is_null +---+-------------------+---+---+----------- + 1 | {"c": 16, "d": 2} | | 8 | t +(1 row) + +select *, c is null as c_is_null +from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) + as t(a int, b jsonb, c text, x int); + a | b | c | x | c_is_null +---+-------------------+---+---+----------- + 1 | {"c": 16, "d": 2} | | 8 | t +(1 row) + -- indexing SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; count diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 39f1b70f4d..603288bd1a 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -519,6 +519,13 @@ select * from json_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar"," select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]') as x(a int, b json, c boolean); +select *, c is null as c_is_null +from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json) + as t(a int, b json, c text, x int); + +select *, c is null as c_is_null +from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json) + as t(a int, b json, c text, x int); -- json_strip_nulls diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 4b24477609..c671684134 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -508,6 +508,14 @@ select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}') select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","c":true}]') as x(a int, b text, c boolean); +select *, c is null as c_is_null +from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb) + as t(a int, b jsonb, c text, x int); + +select *, c is null as c_is_null +from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) + as t(a int, b jsonb, c text, x int); + -- indexing SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';