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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy