mirror of
https://github.com/postgres/postgres.git
synced 2025-11-21 00:42:43 +03:00
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
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/date.h"
|
||||
#include "utils/datetime.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/json.h"
|
||||
#include "utils/jsonfuncs.h"
|
||||
#include "utils/lsyscache.h"
|
||||
@@ -285,9 +286,16 @@ datum_to_json_internal(Datum val, bool is_null, StringInfo result,
|
||||
pfree(jsontext);
|
||||
break;
|
||||
default:
|
||||
outputstr = OidOutputFunctionCall(outfuncoid, val);
|
||||
escape_json(result, outputstr);
|
||||
pfree(outputstr);
|
||||
/* special-case text types to save useless palloc/memcpy cycles */
|
||||
if (outfuncoid == F_TEXTOUT || outfuncoid == F_VARCHAROUT ||
|
||||
outfuncoid == F_BPCHAROUT)
|
||||
escape_json_text(result, (text *) DatumGetPointer(val));
|
||||
else
|
||||
{
|
||||
outputstr = OidOutputFunctionCall(outfuncoid, val);
|
||||
escape_json(result, outputstr);
|
||||
pfree(outputstr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1391,7 +1399,6 @@ json_object(PG_FUNCTION_ARGS)
|
||||
count,
|
||||
i;
|
||||
text *rval;
|
||||
char *v;
|
||||
|
||||
switch (ndims)
|
||||
{
|
||||
@@ -1434,19 +1441,16 @@ json_object(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||
errmsg("null value not allowed for object key")));
|
||||
|
||||
v = TextDatumGetCString(in_datums[i * 2]);
|
||||
if (i > 0)
|
||||
appendStringInfoString(&result, ", ");
|
||||
escape_json(&result, v);
|
||||
escape_json_text(&result, (text *) DatumGetPointer(in_datums[i * 2]));
|
||||
appendStringInfoString(&result, " : ");
|
||||
pfree(v);
|
||||
if (in_nulls[i * 2 + 1])
|
||||
appendStringInfoString(&result, "null");
|
||||
else
|
||||
{
|
||||
v = TextDatumGetCString(in_datums[i * 2 + 1]);
|
||||
escape_json(&result, v);
|
||||
pfree(v);
|
||||
escape_json_text(&result,
|
||||
(text *) DatumGetPointer(in_datums[i * 2 + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1483,7 +1487,6 @@ json_object_two_arg(PG_FUNCTION_ARGS)
|
||||
val_count,
|
||||
i;
|
||||
text *rval;
|
||||
char *v;
|
||||
|
||||
if (nkdims > 1 || nkdims != nvdims)
|
||||
ereport(ERROR,
|
||||
@@ -1512,20 +1515,15 @@ json_object_two_arg(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||
errmsg("null value not allowed for object key")));
|
||||
|
||||
v = TextDatumGetCString(key_datums[i]);
|
||||
if (i > 0)
|
||||
appendStringInfoString(&result, ", ");
|
||||
escape_json(&result, v);
|
||||
escape_json_text(&result, (text *) DatumGetPointer(key_datums[i]));
|
||||
appendStringInfoString(&result, " : ");
|
||||
pfree(v);
|
||||
if (val_nulls[i])
|
||||
appendStringInfoString(&result, "null");
|
||||
else
|
||||
{
|
||||
v = TextDatumGetCString(val_datums[i]);
|
||||
escape_json(&result, v);
|
||||
pfree(v);
|
||||
}
|
||||
escape_json_text(&result,
|
||||
(text *) DatumGetPointer(val_datums[i]));
|
||||
}
|
||||
|
||||
appendStringInfoChar(&result, '}');
|
||||
@@ -1541,50 +1539,100 @@ json_object_two_arg(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_TEXT_P(rval);
|
||||
}
|
||||
|
||||
/*
|
||||
* escape_json_char
|
||||
* Inline helper function for escape_json* functions
|
||||
*/
|
||||
static pg_attribute_always_inline void
|
||||
escape_json_char(StringInfo buf, char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\b':
|
||||
appendStringInfoString(buf, "\\b");
|
||||
break;
|
||||
case '\f':
|
||||
appendStringInfoString(buf, "\\f");
|
||||
break;
|
||||
case '\n':
|
||||
appendStringInfoString(buf, "\\n");
|
||||
break;
|
||||
case '\r':
|
||||
appendStringInfoString(buf, "\\r");
|
||||
break;
|
||||
case '\t':
|
||||
appendStringInfoString(buf, "\\t");
|
||||
break;
|
||||
case '"':
|
||||
appendStringInfoString(buf, "\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
appendStringInfoString(buf, "\\\\");
|
||||
break;
|
||||
default:
|
||||
if ((unsigned char) c < ' ')
|
||||
appendStringInfo(buf, "\\u%04x", (int) c);
|
||||
else
|
||||
appendStringInfoCharMacro(buf, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Produce a JSON string literal, properly escaping characters in the text.
|
||||
* escape_json
|
||||
* Produce a JSON string literal, properly escaping the NUL-terminated
|
||||
* cstring.
|
||||
*/
|
||||
void
|
||||
escape_json(StringInfo buf, const char *str)
|
||||
{
|
||||
const char *p;
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
|
||||
for (; *str != '\0'; str++)
|
||||
escape_json_char(buf, *str);
|
||||
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
for (p = str; *p; p++)
|
||||
{
|
||||
switch (*p)
|
||||
{
|
||||
case '\b':
|
||||
appendStringInfoString(buf, "\\b");
|
||||
break;
|
||||
case '\f':
|
||||
appendStringInfoString(buf, "\\f");
|
||||
break;
|
||||
case '\n':
|
||||
appendStringInfoString(buf, "\\n");
|
||||
break;
|
||||
case '\r':
|
||||
appendStringInfoString(buf, "\\r");
|
||||
break;
|
||||
case '\t':
|
||||
appendStringInfoString(buf, "\\t");
|
||||
break;
|
||||
case '"':
|
||||
appendStringInfoString(buf, "\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
appendStringInfoString(buf, "\\\\");
|
||||
break;
|
||||
default:
|
||||
if ((unsigned char) *p < ' ')
|
||||
appendStringInfo(buf, "\\u%04x", (int) *p);
|
||||
else
|
||||
appendStringInfoCharMacro(buf, *p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* escape_json_with_len
|
||||
* Produce a JSON string literal, properly escaping the possibly not
|
||||
* NUL-terminated characters in 'str'. 'len' defines the number of bytes
|
||||
* from 'str' to process.
|
||||
*/
|
||||
void
|
||||
escape_json_with_len(StringInfo buf, const char *str, int len)
|
||||
{
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
escape_json_char(buf, str[i]);
|
||||
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
}
|
||||
|
||||
/*
|
||||
* escape_json_text
|
||||
* Append 'txt' onto 'buf' and escape using escape_json_with_len.
|
||||
*
|
||||
* This is more efficient than calling text_to_cstring and appending the
|
||||
* result as that could require an additional palloc and memcpy.
|
||||
*/
|
||||
void
|
||||
escape_json_text(StringInfo buf, const text *txt)
|
||||
{
|
||||
/* must cast away the const, unfortunately */
|
||||
text *tunpacked = pg_detoast_datum_packed(unconstify(text *, txt));
|
||||
int len = VARSIZE_ANY_EXHDR(tunpacked);
|
||||
char *str;
|
||||
|
||||
str = VARDATA_ANY(tunpacked);
|
||||
|
||||
escape_json_with_len(buf, str, len);
|
||||
|
||||
/* pfree any detoasted values */
|
||||
if (tunpacked != txt)
|
||||
pfree(tunpacked);
|
||||
}
|
||||
|
||||
/* Semantic actions for key uniqueness check */
|
||||
|
||||
Reference in New Issue
Block a user