From 6f34fcbbd5ad5a6546710e7b90e6331cacfd36dc Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 11 Jun 2019 13:33:08 -0400 Subject: 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 --- src/backend/utils/adt/jsonfuncs.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'src/backend/utils/adt/jsonfuncs.c') diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 28bdc7243fd..9e7035c71a1 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -2803,26 +2803,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)); @@ -2830,7 +2811,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 { -- cgit v1.2.3