diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2019-06-11 13:33:08 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2019-06-11 13:33:08 -0400 |
commit | b6f5689aadf42b9afa3bf972d3deee839541a3ba (patch) | |
tree | bf716d155e64b7a6ab51592cb8bda0c5305027d2 /src | |
parent | 52ad5fc0a6c353a72513cc3730d062a2de1d4952 (diff) |
Fix conversion of JSON strings to JSON output columns in json_to_record().
json_to_record(), when an output column is declared as type json or jsonb,
should emit the corresponding field of the input JSON object. But it got
this slightly wrong when the field is just a string literal: it failed to
escape the contents of the string. That typically resulted in syntax
errors if the string contained any double quotes or backslashes.
jsonb_to_record() handles such cases correctly, but I added corresponding
test cases for it too, to prevent future backsliding.
Improve the documentation, as it provided only a very hand-wavy
description of the conversion rules used by these functions.
Per bug report from Robert Vollmert. Back-patch to v10 where the
error was introduced (by commit cf35346e8).
Note that PG 9.4 - 9.6 also get this case wrong, but differently so:
they feed the de-escaped contents of the string literal to json[b]_in.
That behavior is less obviously wrong, so possibly it's being depended on
in the field, so I won't risk trying to make the older branches behave
like the newer ones.
Discussion: https://postgr.es/m/D6921B37-BD8E-4664-8D5F-DB3525765DCD@vllmrt.net
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 37 | ||||
-rw-r--r-- | src/test/regress/expected/json.out | 36 | ||||
-rw-r--r-- | src/test/regress/expected/jsonb.out | 36 | ||||
-rw-r--r-- | src/test/regress/sql/json.sql | 7 | ||||
-rw-r--r-- | src/test/regress/sql/jsonb.sql | 7 |
5 files changed, 102 insertions, 21 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 25f62c91b81..02cbc2658b8 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -2762,26 +2762,7 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) json = jsv->val.json.str; Assert(json); - - /* already done the hard work in the json case */ - if ((typid == JSONOID || typid == JSONBOID) && - jsv->val.json.type == JSON_TOKEN_STRING) - { - /* - * Add quotes around string value (should be already escaped) if - * converting to json/jsonb. - */ - - if (len < 0) - len = strlen(json); - - str = palloc(len + sizeof(char) * 3); - str[0] = '"'; - memcpy(&str[1], json, len); - str[len + 1] = '"'; - str[len + 2] = '\0'; - } - else if (len >= 0) + if (len >= 0) { /* Need to copy non-null-terminated string */ str = palloc(len + 1 * sizeof(char)); @@ -2789,7 +2770,21 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) str[len] = '\0'; } else - str = json; /* null-terminated string */ + str = json; /* string is already null-terminated */ + + /* If converting to json/jsonb, make string into valid JSON literal */ + if ((typid == JSONOID || typid == JSONBOID) && + jsv->val.json.type == JSON_TOKEN_STRING) + { + StringInfoData buf; + + initStringInfo(&buf); + escape_json(&buf, str); + /* free temporary buffer */ + if (str != json) + pfree(str); + str = buf.data; + } } else { diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index ce33367da83..a0b1a4cda29 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -2200,6 +2200,42 @@ select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); {{{1},{2},{3}}} (1 row) +select * from json_to_record('{"out": {"key": 1}}') as x(out json); + out +------------ + {"key": 1} +(1 row) + +select * from json_to_record('{"out": [{"key": 1}]}') as x(out json); + out +-------------- + [{"key": 1}] +(1 row) + +select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out json); + out +---------------- + "{\"key\": 1}" +(1 row) + +select * from json_to_record('{"out": {"key": 1}}') as x(out jsonb); + out +------------ + {"key": 1} +(1 row) + +select * from json_to_record('{"out": [{"key": 1}]}') as x(out jsonb); + out +-------------- + [{"key": 1}] +(1 row) + +select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb); + out +---------------- + "{\"key\": 1}" +(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 40594e0be32..b4056e1ffc2 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2578,6 +2578,42 @@ select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); {{{1},{2},{3}}} (1 row) +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out json); + out +------------ + {"key": 1} +(1 row) + +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out json); + out +-------------- + [{"key": 1}] +(1 row) + +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out json); + out +---------------- + "{\"key\": 1}" +(1 row) + +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out jsonb); + out +------------ + {"key": 1} +(1 row) + +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out jsonb); + out +-------------- + [{"key": 1}] +(1 row) + +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb); + out +---------------- + "{\"key\": 1}" +(1 row) + -- test type info caching in jsonb_populate_record() CREATE TEMP TABLE jsbpoptest (js jsonb); INSERT INTO jsbpoptest diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index d51243e5979..3a27267d206 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -712,6 +712,13 @@ select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); +select * from json_to_record('{"out": {"key": 1}}') as x(out json); +select * from json_to_record('{"out": [{"key": 1}]}') as x(out json); +select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out json); +select * from json_to_record('{"out": {"key": 1}}') as x(out jsonb); +select * from json_to_record('{"out": [{"key": 1}]}') as x(out jsonb); +select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb); + -- json_strip_nulls select json_strip_nulls(null); diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 1385dc8f21b..418cdfaa4f3 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -681,6 +681,13 @@ select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out json); +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out json); +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out json); +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out jsonb); +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out jsonb); +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb); + -- test type info caching in jsonb_populate_record() CREATE TEMP TABLE jsbpoptest (js jsonb); |