Skip to content

Commit 17a5871

Browse files
committed
Optimize escaping of JSON strings
There were quite a few places where we either had a non-NUL-terminated string or a text Datum which we needed to call escape_json() on. Many of these places required that a temporary string was created due to the fact that escape_json() needs a NUL-terminated cstring. For text types, those first had to be converted to cstring before calling escape_json() on them. Here we introduce two new functions to make escaping JSON more optimal: escape_json_text() can be given a text Datum to append onto the given buffer. This is more optimal as it foregoes the need to convert the text Datum into a cstring. A temporary allocation is only required if the text Datum needs to be detoasted. escape_json_with_len() can be used when the length of the cstring is already known or the given string isn't NUL-terminated. Having this allows various places which were creating a temporary NUL-terminated string to just call escape_json_with_len() without any temporary memory allocations. Discussion: https://postgr.es/m/CAApHDvpLXwMZvbCKcdGfU9XQjGCDm7tFpRdTXuB9PVgpNUYfEQ@mail.gmail.com Reviewed-by: Melih Mutlu, Heikki Linnakangas
1 parent 67427f1 commit 17a5871

File tree

7 files changed

+151
-101
lines changed

7 files changed

+151
-101
lines changed

contrib/hstore/hstore_io.c

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,23 +1343,20 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
13431343
int count = HS_COUNT(in);
13441344
char *base = STRPTR(in);
13451345
HEntry *entries = ARRPTR(in);
1346-
StringInfoData tmp,
1347-
dst;
1346+
StringInfoData dst;
13481347

13491348
if (count == 0)
13501349
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
13511350

1352-
initStringInfo(&tmp);
13531351
initStringInfo(&dst);
13541352

13551353
appendStringInfoChar(&dst, '{');
13561354

13571355
for (i = 0; i < count; i++)
13581356
{
1359-
resetStringInfo(&tmp);
1360-
appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i),
1361-
HSTORE_KEYLEN(entries, i));
1362-
escape_json(&dst, tmp.data);
1357+
escape_json_with_len(&dst,
1358+
HSTORE_KEY(entries, base, i),
1359+
HSTORE_KEYLEN(entries, i));
13631360
appendStringInfoString(&dst, ": ");
13641361
if (HSTORE_VALISNULL(entries, i))
13651362
appendStringInfoString(&dst, "null");
@@ -1372,13 +1369,13 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
13721369
appendStringInfoString(&dst, "false");
13731370
else
13741371
{
1375-
resetStringInfo(&tmp);
1376-
appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i),
1377-
HSTORE_VALLEN(entries, i));
1378-
if (IsValidJsonNumber(tmp.data, tmp.len))
1379-
appendBinaryStringInfo(&dst, tmp.data, tmp.len);
1372+
char *str = HSTORE_VAL(entries, base, i);
1373+
int len = HSTORE_VALLEN(entries, i);
1374+
1375+
if (IsValidJsonNumber(str, len))
1376+
appendBinaryStringInfo(&dst, str, len);
13801377
else
1381-
escape_json(&dst, tmp.data);
1378+
escape_json_with_len(&dst, str, len);
13821379
}
13831380

13841381
if (i + 1 != count)
@@ -1398,32 +1395,28 @@ hstore_to_json(PG_FUNCTION_ARGS)
13981395
int count = HS_COUNT(in);
13991396
char *base = STRPTR(in);
14001397
HEntry *entries = ARRPTR(in);
1401-
StringInfoData tmp,
1402-
dst;
1398+
StringInfoData dst;
14031399

14041400
if (count == 0)
14051401
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
14061402

1407-
initStringInfo(&tmp);
14081403
initStringInfo(&dst);
14091404

14101405
appendStringInfoChar(&dst, '{');
14111406

14121407
for (i = 0; i < count; i++)
14131408
{
1414-
resetStringInfo(&tmp);
1415-
appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i),
1416-
HSTORE_KEYLEN(entries, i));
1417-
escape_json(&dst, tmp.data);
1409+
escape_json_with_len(&dst,
1410+
HSTORE_KEY(entries, base, i),
1411+
HSTORE_KEYLEN(entries, i));
14181412
appendStringInfoString(&dst, ": ");
14191413
if (HSTORE_VALISNULL(entries, i))
14201414
appendStringInfoString(&dst, "null");
14211415
else
14221416
{
1423-
resetStringInfo(&tmp);
1424-
appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i),
1425-
HSTORE_VALLEN(entries, i));
1426-
escape_json(&dst, tmp.data);
1417+
escape_json_with_len(&dst,
1418+
HSTORE_VAL(entries, base, i),
1419+
HSTORE_VALLEN(entries, i));
14271420
}
14281421

14291422
if (i + 1 != count)

src/backend/backup/backup_manifest.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ AddFileToBackupManifest(backup_manifest_info *manifest, Oid spcoid,
148148
pg_verify_mbstr(PG_UTF8, pathname, pathlen, true))
149149
{
150150
appendStringInfoString(&buf, "{ \"Path\": ");
151-
escape_json(&buf, pathname);
151+
escape_json_with_len(&buf, pathname, pathlen);
152152
appendStringInfoString(&buf, ", ");
153153
}
154154
else

src/backend/utils/adt/json.c

Lines changed: 102 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "utils/builtins.h"
2424
#include "utils/date.h"
2525
#include "utils/datetime.h"
26+
#include "utils/fmgroids.h"
2627
#include "utils/json.h"
2728
#include "utils/jsonfuncs.h"
2829
#include "utils/lsyscache.h"
@@ -285,9 +286,16 @@ datum_to_json_internal(Datum val, bool is_null, StringInfo result,
285286
pfree(jsontext);
286287
break;
287288
default:
288-
outputstr = OidOutputFunctionCall(outfuncoid, val);
289-
escape_json(result, outputstr);
290-
pfree(outputstr);
289+
/* special-case text types to save useless palloc/memcpy cycles */
290+
if (outfuncoid == F_TEXTOUT || outfuncoid == F_VARCHAROUT ||
291+
outfuncoid == F_BPCHAROUT)
292+
escape_json_text(result, (text *) DatumGetPointer(val));
293+
else
294+
{
295+
outputstr = OidOutputFunctionCall(outfuncoid, val);
296+
escape_json(result, outputstr);
297+
pfree(outputstr);
298+
}
291299
break;
292300
}
293301
}
@@ -1391,7 +1399,6 @@ json_object(PG_FUNCTION_ARGS)
13911399
count,
13921400
i;
13931401
text *rval;
1394-
char *v;
13951402

13961403
switch (ndims)
13971404
{
@@ -1434,19 +1441,16 @@ json_object(PG_FUNCTION_ARGS)
14341441
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
14351442
errmsg("null value not allowed for object key")));
14361443

1437-
v = TextDatumGetCString(in_datums[i * 2]);
14381444
if (i > 0)
14391445
appendStringInfoString(&result, ", ");
1440-
escape_json(&result, v);
1446+
escape_json_text(&result, (text *) DatumGetPointer(in_datums[i * 2]));
14411447
appendStringInfoString(&result, " : ");
1442-
pfree(v);
14431448
if (in_nulls[i * 2 + 1])
14441449
appendStringInfoString(&result, "null");
14451450
else
14461451
{
1447-
v = TextDatumGetCString(in_datums[i * 2 + 1]);
1448-
escape_json(&result, v);
1449-
pfree(v);
1452+
escape_json_text(&result,
1453+
(text *) DatumGetPointer(in_datums[i * 2 + 1]));
14501454
}
14511455
}
14521456

@@ -1483,7 +1487,6 @@ json_object_two_arg(PG_FUNCTION_ARGS)
14831487
val_count,
14841488
i;
14851489
text *rval;
1486-
char *v;
14871490

14881491
if (nkdims > 1 || nkdims != nvdims)
14891492
ereport(ERROR,
@@ -1512,20 +1515,15 @@ json_object_two_arg(PG_FUNCTION_ARGS)
15121515
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
15131516
errmsg("null value not allowed for object key")));
15141517

1515-
v = TextDatumGetCString(key_datums[i]);
15161518
if (i > 0)
15171519
appendStringInfoString(&result, ", ");
1518-
escape_json(&result, v);
1520+
escape_json_text(&result, (text *) DatumGetPointer(key_datums[i]));
15191521
appendStringInfoString(&result, " : ");
1520-
pfree(v);
15211522
if (val_nulls[i])
15221523
appendStringInfoString(&result, "null");
15231524
else
1524-
{
1525-
v = TextDatumGetCString(val_datums[i]);
1526-
escape_json(&result, v);
1527-
pfree(v);
1528-
}
1525+
escape_json_text(&result,
1526+
(text *) DatumGetPointer(val_datums[i]));
15291527
}
15301528

15311529
appendStringInfoChar(&result, '}');
@@ -1541,50 +1539,100 @@ json_object_two_arg(PG_FUNCTION_ARGS)
15411539
PG_RETURN_TEXT_P(rval);
15421540
}
15431541

1542+
/*
1543+
* escape_json_char
1544+
* Inline helper function for escape_json* functions
1545+
*/
1546+
static pg_attribute_always_inline void
1547+
escape_json_char(StringInfo buf, char c)
1548+
{
1549+
switch (c)
1550+
{
1551+
case '\b':
1552+
appendStringInfoString(buf, "\\b");
1553+
break;
1554+
case '\f':
1555+
appendStringInfoString(buf, "\\f");
1556+
break;
1557+
case '\n':
1558+
appendStringInfoString(buf, "\\n");
1559+
break;
1560+
case '\r':
1561+
appendStringInfoString(buf, "\\r");
1562+
break;
1563+
case '\t':
1564+
appendStringInfoString(buf, "\\t");
1565+
break;
1566+
case '"':
1567+
appendStringInfoString(buf, "\\\"");
1568+
break;
1569+
case '\\':
1570+
appendStringInfoString(buf, "\\\\");
1571+
break;
1572+
default:
1573+
if ((unsigned char) c < ' ')
1574+
appendStringInfo(buf, "\\u%04x", (int) c);
1575+
else
1576+
appendStringInfoCharMacro(buf, c);
1577+
break;
1578+
}
1579+
}
15441580

15451581
/*
1546-
* Produce a JSON string literal, properly escaping characters in the text.
1582+
* escape_json
1583+
* Produce a JSON string literal, properly escaping the NUL-terminated
1584+
* cstring.
15471585
*/
15481586
void
15491587
escape_json(StringInfo buf, const char *str)
15501588
{
1551-
const char *p;
1589+
appendStringInfoCharMacro(buf, '"');
1590+
1591+
for (; *str != '\0'; str++)
1592+
escape_json_char(buf, *str);
15521593

15531594
appendStringInfoCharMacro(buf, '"');
1554-
for (p = str; *p; p++)
1555-
{
1556-
switch (*p)
1557-
{
1558-
case '\b':
1559-
appendStringInfoString(buf, "\\b");
1560-
break;
1561-
case '\f':
1562-
appendStringInfoString(buf, "\\f");
1563-
break;
1564-
case '\n':
1565-
appendStringInfoString(buf, "\\n");
1566-
break;
1567-
case '\r':
1568-
appendStringInfoString(buf, "\\r");
1569-
break;
1570-
case '\t':
1571-
appendStringInfoString(buf, "\\t");
1572-
break;
1573-
case '"':
1574-
appendStringInfoString(buf, "\\\"");
1575-
break;
1576-
case '\\':
1577-
appendStringInfoString(buf, "\\\\");
1578-
break;
1579-
default:
1580-
if ((unsigned char) *p < ' ')
1581-
appendStringInfo(buf, "\\u%04x", (int) *p);
1582-
else
1583-
appendStringInfoCharMacro(buf, *p);
1584-
break;
1585-
}
1586-
}
1595+
}
1596+
1597+
/*
1598+
* escape_json_with_len
1599+
* Produce a JSON string literal, properly escaping the possibly not
1600+
* NUL-terminated characters in 'str'. 'len' defines the number of bytes
1601+
* from 'str' to process.
1602+
*/
1603+
void
1604+
escape_json_with_len(StringInfo buf, const char *str, int len)
1605+
{
15871606
appendStringInfoCharMacro(buf, '"');
1607+
1608+
for (int i = 0; i < len; i++)
1609+
escape_json_char(buf, str[i]);
1610+
1611+
appendStringInfoCharMacro(buf, '"');
1612+
}
1613+
1614+
/*
1615+
* escape_json_text
1616+
* Append 'txt' onto 'buf' and escape using escape_json_with_len.
1617+
*
1618+
* This is more efficient than calling text_to_cstring and appending the
1619+
* result as that could require an additional palloc and memcpy.
1620+
*/
1621+
void
1622+
escape_json_text(StringInfo buf, const text *txt)
1623+
{
1624+
/* must cast away the const, unfortunately */
1625+
text *tunpacked = pg_detoast_datum_packed(unconstify(text *, txt));
1626+
int len = VARSIZE_ANY_EXHDR(tunpacked);
1627+
char *str;
1628+
1629+
str = VARDATA_ANY(tunpacked);
1630+
1631+
escape_json_with_len(buf, str, len);
1632+
1633+
/* pfree any detoasted values */
1634+
if (tunpacked != txt)
1635+
pfree(tunpacked);
15881636
}
15891637

15901638
/* Semantic actions for key uniqueness check */

src/backend/utils/adt/jsonb.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal)
354354
appendBinaryStringInfo(out, "null", 4);
355355
break;
356356
case jbvString:
357-
escape_json(out, pnstrdup(scalarVal->val.string.val, scalarVal->val.string.len));
357+
escape_json_with_len(out, scalarVal->val.string.val, scalarVal->val.string.len);
358358
break;
359359
case jbvNumeric:
360360
appendStringInfoString(out,

src/backend/utils/adt/jsonfuncs.c

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3133,18 +3133,6 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv,
31333133

31343134
json = jsv->val.json.str;
31353135
Assert(json);
3136-
if (len >= 0)
3137-
{
3138-
/* Need to copy non-null-terminated string */
3139-
str = palloc(len + 1 * sizeof(char));
3140-
memcpy(str, json, len);
3141-
str[len] = '\0';
3142-
}
3143-
else
3144-
{
3145-
/* string is already null-terminated */
3146-
str = unconstify(char *, json);
3147-
}
31483136

31493137
/* If converting to json/jsonb, make string into valid JSON literal */
31503138
if ((typid == JSONOID || typid == JSONBOID) &&
@@ -3153,12 +3141,24 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv,
31533141
StringInfoData buf;
31543142

31553143
initStringInfo(&buf);
3156-
escape_json(&buf, str);
3157-
/* free temporary buffer */
3158-
if (str != json)
3159-
pfree(str);
3144+
if (len >= 0)
3145+
escape_json_with_len(&buf, json, len);
3146+
else
3147+
escape_json(&buf, json);
31603148
str = buf.data;
31613149
}
3150+
else if (len >= 0)
3151+
{
3152+
/* create a NUL-terminated version */
3153+
str = palloc(len + 1);
3154+
memcpy(str, json, len);
3155+
str[len] = '\0';
3156+
}
3157+
else
3158+
{
3159+
/* string is already NUL-terminated */
3160+
str = unconstify(char *, json);
3161+
}
31623162
}
31633163
else
31643164
{
@@ -5936,7 +5936,7 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype
59365936
{
59375937
text *out = _state->action(_state->action_state, token, strlen(token));
59385938

5939-
escape_json(_state->strval, text_to_cstring(out));
5939+
escape_json_text(_state->strval, out);
59405940
}
59415941
else
59425942
appendStringInfoString(_state->strval, token);

0 commit comments

Comments
 (0)