v1-0001-Add-len-parameter-to-escape_json.patch
application/octet-stream
Filename: v1-0001-Add-len-parameter-to-escape_json.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v1-0001
Subject: Add 'len' parameter to escape_json()
| File | + | − |
|---|---|---|
| contrib/hstore/hstore_io.c | 17 | 24 |
| src/backend/backup/backup_manifest.c | 1 | 1 |
| src/backend/commands/explain.c | 19 | 11 |
| src/backend/parser/parse_jsontable.c | 1 | 1 |
| src/backend/utils/adt/jsonb.c | 1 | 1 |
| src/backend/utils/adt/json.c | 96 | 54 |
| src/backend/utils/adt/jsonfuncs.c | 9 | 6 |
| src/backend/utils/adt/jsonpath.c | 11 | 4 |
| src/backend/utils/error/jsonlog.c | 4 | 4 |
| src/include/utils/json.h | 3 | 1 |
From 695ab50057adb87caa95d8ca3ff77849f33ba399 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Tue, 21 May 2024 17:14:06 +1200
Subject: [PATCH v1 1/2] Add 'len' parameter to escape_json()
---
contrib/hstore/hstore_io.c | 41 +++-----
src/backend/backup/backup_manifest.c | 2 +-
src/backend/commands/explain.c | 30 ++++--
src/backend/parser/parse_jsontable.c | 2 +-
src/backend/utils/adt/json.c | 150 +++++++++++++++++----------
src/backend/utils/adt/jsonb.c | 2 +-
src/backend/utils/adt/jsonfuncs.c | 15 +--
src/backend/utils/adt/jsonpath.c | 15 ++-
src/backend/utils/error/jsonlog.c | 8 +-
src/include/utils/json.h | 4 +-
10 files changed, 162 insertions(+), 107 deletions(-)
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 999ddad76d..374b8d05ef 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -1343,23 +1343,20 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
int count = HS_COUNT(in);
char *base = STRPTR(in);
HEntry *entries = ARRPTR(in);
- StringInfoData tmp,
- dst;
+ StringInfoData dst;
if (count == 0)
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
- initStringInfo(&tmp);
initStringInfo(&dst);
appendStringInfoChar(&dst, '{');
for (i = 0; i < count; i++)
{
- resetStringInfo(&tmp);
- appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i),
- HSTORE_KEYLEN(entries, i));
- escape_json(&dst, tmp.data);
+ escape_json(&dst,
+ HSTORE_KEY(entries, base, i),
+ HSTORE_KEYLEN(entries, i));
appendStringInfoString(&dst, ": ");
if (HSTORE_VALISNULL(entries, i))
appendStringInfoString(&dst, "null");
@@ -1372,13 +1369,13 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
appendStringInfoString(&dst, "false");
else
{
- resetStringInfo(&tmp);
- appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i),
- HSTORE_VALLEN(entries, i));
- if (IsValidJsonNumber(tmp.data, tmp.len))
- appendBinaryStringInfo(&dst, tmp.data, tmp.len);
+ char *str = HSTORE_VAL(entries, base, i);
+ int len = HSTORE_VALLEN(entries, i);
+
+ if (IsValidJsonNumber(str, len))
+ appendBinaryStringInfo(&dst, str, len);
else
- escape_json(&dst, tmp.data);
+ escape_json(&dst, str, len);
}
if (i + 1 != count)
@@ -1398,32 +1395,28 @@ hstore_to_json(PG_FUNCTION_ARGS)
int count = HS_COUNT(in);
char *base = STRPTR(in);
HEntry *entries = ARRPTR(in);
- StringInfoData tmp,
- dst;
+ StringInfoData dst;
if (count == 0)
PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
- initStringInfo(&tmp);
initStringInfo(&dst);
appendStringInfoChar(&dst, '{');
for (i = 0; i < count; i++)
{
- resetStringInfo(&tmp);
- appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i),
- HSTORE_KEYLEN(entries, i));
- escape_json(&dst, tmp.data);
+ escape_json(&dst,
+ HSTORE_KEY(entries, base, i),
+ HSTORE_KEYLEN(entries, i));
appendStringInfoString(&dst, ": ");
if (HSTORE_VALISNULL(entries, i))
appendStringInfoString(&dst, "null");
else
{
- resetStringInfo(&tmp);
- appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i),
- HSTORE_VALLEN(entries, i));
- escape_json(&dst, tmp.data);
+ escape_json(&dst,
+ HSTORE_VAL(entries, base, i),
+ HSTORE_VALLEN(entries, i));
}
if (i + 1 != count)
diff --git a/src/backend/backup/backup_manifest.c b/src/backend/backup/backup_manifest.c
index b360a13547..3da8da00d6 100644
--- a/src/backend/backup/backup_manifest.c
+++ b/src/backend/backup/backup_manifest.c
@@ -148,7 +148,7 @@ AddFileToBackupManifest(backup_manifest_info *manifest, Oid spcoid,
pg_verify_mbstr(PG_UTF8, pathname, pathlen, true))
{
appendStringInfoString(&buf, "{ \"Path\": ");
- escape_json(&buf, pathname);
+ escape_json(&buf, pathname, pathlen);
appendStringInfoString(&buf, ", ");
}
else
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 94511a5a02..399025aed3 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4661,13 +4661,15 @@ ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
case EXPLAIN_FORMAT_JSON:
ExplainJSONLineEnding(es);
appendStringInfoSpaces(es->str, es->indent * 2);
- escape_json(es->str, qlabel);
+ escape_json_cstring(es->str, qlabel);
appendStringInfoString(es->str, ": [");
foreach(lc, data)
{
+ const char *str = (const char *) lfirst(lc);
+
if (!first)
appendStringInfoString(es->str, ", ");
- escape_json(es->str, (const char *) lfirst(lc));
+ escape_json_cstring(es->str, str);
first = false;
}
appendStringInfoChar(es->str, ']');
@@ -4678,10 +4680,12 @@ ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
appendStringInfo(es->str, "%s: ", qlabel);
foreach(lc, data)
{
+ const char *str = (const char *) lfirst(lc);
+
appendStringInfoChar(es->str, '\n');
appendStringInfoSpaces(es->str, es->indent * 2 + 2);
appendStringInfoString(es->str, "- ");
- escape_yaml(es->str, (const char *) lfirst(lc));
+ escape_yaml(es->str, str);
}
break;
}
@@ -4710,9 +4714,11 @@ ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
appendStringInfoChar(es->str, '[');
foreach(lc, data)
{
+ const char *str = (const char *) lfirst(lc);
+
if (!first)
appendStringInfoString(es->str, ", ");
- escape_json(es->str, (const char *) lfirst(lc));
+ escape_json_cstring(es->str, str);
first = false;
}
appendStringInfoChar(es->str, ']');
@@ -4723,9 +4729,11 @@ ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
appendStringInfoString(es->str, "- [");
foreach(lc, data)
{
+ const char *str = (const char *) lfirst(lc);
+
if (!first)
appendStringInfoString(es->str, ", ");
- escape_yaml(es->str, (const char *) lfirst(lc));
+ escape_yaml(es->str, str);
first = false;
}
appendStringInfoChar(es->str, ']');
@@ -4775,12 +4783,12 @@ ExplainProperty(const char *qlabel, const char *unit, const char *value,
case EXPLAIN_FORMAT_JSON:
ExplainJSONLineEnding(es);
appendStringInfoSpaces(es->str, es->indent * 2);
- escape_json(es->str, qlabel);
+ escape_json_cstring(es->str, qlabel);
appendStringInfoString(es->str, ": ");
if (numeric)
appendStringInfoString(es->str, value);
else
- escape_json(es->str, value);
+ escape_json_cstring(es->str, value);
break;
case EXPLAIN_FORMAT_YAML:
@@ -4882,7 +4890,7 @@ ExplainOpenGroup(const char *objtype, const char *labelname,
appendStringInfoSpaces(es->str, 2 * es->indent);
if (labelname)
{
- escape_json(es->str, labelname);
+ escape_json_cstring(es->str, labelname);
appendStringInfoString(es->str, ": ");
}
appendStringInfoChar(es->str, labeled ? '{' : '[');
@@ -5090,10 +5098,10 @@ ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
appendStringInfoSpaces(es->str, 2 * es->indent);
if (labelname)
{
- escape_json(es->str, labelname);
+ escape_json_cstring(es->str, labelname);
appendStringInfoString(es->str, ": ");
}
- escape_json(es->str, objtype);
+ escape_json_cstring(es->str, objtype);
break;
case EXPLAIN_FORMAT_YAML:
@@ -5297,7 +5305,7 @@ ExplainYAMLLineStarting(ExplainState *es)
static void
escape_yaml(StringInfo buf, const char *str)
{
- escape_json(buf, str);
+ escape_json_cstring(buf, str);
}
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index b2519c2f32..03aac2f0cd 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -427,7 +427,7 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
initStringInfo(&path);
appendStringInfoString(&path, "$.");
- escape_json(&path, jtc->name);
+ escape_json_cstring(&path, jtc->name);
pathspec = makeStringConst(path.data, -1);
}
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index d719a61f16..7934cf62fb 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -286,7 +286,7 @@ datum_to_json_internal(Datum val, bool is_null, StringInfo result,
break;
default:
outputstr = OidOutputFunctionCall(outfuncoid, val);
- escape_json(result, outputstr);
+ escape_json_cstring(result, outputstr);
pfree(outputstr);
break;
}
@@ -560,7 +560,7 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
needsep = true;
attname = NameStr(att->attname);
- escape_json(result, attname);
+ escape_json_cstring(result, attname);
appendStringInfoChar(result, ':');
val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
@@ -1391,7 +1391,6 @@ json_object(PG_FUNCTION_ARGS)
count,
i;
text *rval;
- char *v;
switch (ndims)
{
@@ -1434,19 +1433,17 @@ 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_from_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_from_text(&result,
+ (text *) DatumGetPointer(in_datums[i * 2 + 1]));
}
}
@@ -1483,7 +1480,6 @@ json_object_two_arg(PG_FUNCTION_ARGS)
val_count,
i;
text *rval;
- char *v;
if (nkdims > 1 || nkdims != nvdims)
ereport(ERROR,
@@ -1512,20 +1508,17 @@ 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_from_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_from_text(&result,
+ (text *) DatumGetPointer(val_datums[i]));
}
appendStringInfoChar(&result, '}');
@@ -1541,52 +1534,101 @@ 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_cstring
+ * Produce a JSON string literal. Same as escape_json() except takes a
+ * NUL-terminated string as input.
*/
void
-escape_json(StringInfo buf, const char *str)
+escape_json_cstring(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;
- }
- }
+}
+
+/*
+ * 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(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_from_text
+ * Append 't' onto 'buf' and escape using escape_json.
+ *
+ * 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_from_text(StringInfo buf, const text *t)
+{
+ /* must cast away the const, unfortunately */
+ text *tunpacked = pg_detoast_datum_packed(unconstify(text *, t));
+ int len = VARSIZE_ANY_EXHDR(tunpacked);
+ char *str;
+
+ str = VARDATA_ANY(tunpacked);
+
+ escape_json(buf, str, len);
+
+ /* pfree any detoasted values */
+ if (tunpacked != t)
+ pfree(tunpacked);
+}
+
/* Semantic actions for key uniqueness check */
static JsonParseErrorType
json_unique_object_start(void *_state)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index e4562b3c6c..f24d8003be 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -354,7 +354,7 @@ jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal)
appendBinaryStringInfo(out, "null", 4);
break;
case jbvString:
- escape_json(out, pnstrdup(scalarVal->val.string.val, scalarVal->val.string.len));
+ escape_json(out, scalarVal->val.string.val, scalarVal->val.string.len);
break;
case jbvNumeric:
appendStringInfoString(out,
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 83125b06a4..743d47dae6 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3139,7 +3139,10 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv,
str[len] = '\0';
}
else
- str = json; /* string is already null-terminated */
+ {
+ str = json; /* string is already null-terminated */
+ len = strlen(str);
+ }
/* If converting to json/jsonb, make string into valid JSON literal */
if ((typid == JSONOID || typid == JSONBOID) &&
@@ -3148,7 +3151,7 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv,
StringInfoData buf;
initStringInfo(&buf);
- escape_json(&buf, str);
+ escape_json(&buf, str, len);
/* free temporary buffer */
if (str != json)
pfree(str);
@@ -4425,7 +4428,7 @@ sn_object_field_start(void *state, char *fname, bool isnull)
* Unfortunately we don't have the quoted and escaped string any more, so
* we have to re-escape it.
*/
- escape_json(_state->strval, fname);
+ escape_json_cstring(_state->strval, fname);
appendStringInfoCharMacro(_state->strval, ':');
@@ -4456,7 +4459,7 @@ sn_scalar(void *state, char *token, JsonTokenType tokentype)
}
if (tokentype == JSON_TOKEN_STRING)
- escape_json(_state->strval, token);
+ escape_json_cstring(_state->strval, token);
else
appendStringInfoString(_state->strval, token);
@@ -5888,7 +5891,7 @@ transform_string_values_object_field_start(void *state, char *fname, bool isnull
* Unfortunately we don't have the quoted and escaped string any more, so
* we have to re-escape it.
*/
- escape_json(_state->strval, fname);
+ escape_json_cstring(_state->strval, fname);
appendStringInfoCharMacro(_state->strval, ':');
return JSON_SUCCESS;
@@ -5914,7 +5917,7 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype
{
text *out = _state->action(_state->action_state, token, strlen(token));
- escape_json(_state->strval, text_to_cstring(out));
+ escape_json_from_text(_state->strval, out);
}
else
appendStringInfoString(_state->strval, token);
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 11e6193e96..82d65e4d4f 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -523,6 +523,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
{
JsonPathItem elem;
int i;
+ int32 len;
+ char *str;
check_stack_depth();
CHECK_FOR_INTERRUPTS();
@@ -533,7 +535,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
appendStringInfoString(buf, "null");
break;
case jpiString:
- escape_json(buf, jspGetString(v, NULL));
+ str = jspGetString(v, &len);
+ escape_json(buf, str, len);
break;
case jpiNumeric:
if (jspHasNext(v))
@@ -662,7 +665,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
case jpiKey:
if (inKey)
appendStringInfoChar(buf, '.');
- escape_json(buf, jspGetString(v, NULL));
+ str = jspGetString(v, &len);
+ escape_json(buf, str, len);
break;
case jpiCurrent:
Assert(!inKey);
@@ -674,7 +678,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
break;
case jpiVariable:
appendStringInfoChar(buf, '$');
- escape_json(buf, jspGetString(v, NULL));
+ str = jspGetString(v, &len);
+ escape_json(buf, str, len);
break;
case jpiFilter:
appendStringInfoString(buf, "?(");
@@ -732,7 +737,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
appendStringInfoString(buf, " like_regex ");
- escape_json(buf, v->content.like_regex.pattern);
+ escape_json(buf,
+ v->content.like_regex.pattern,
+ v->content.like_regex.patternlen);
if (v->content.like_regex.flags)
{
diff --git a/src/backend/utils/error/jsonlog.c b/src/backend/utils/error/jsonlog.c
index bd0124869d..bd21f6ef58 100644
--- a/src/backend/utils/error/jsonlog.c
+++ b/src/backend/utils/error/jsonlog.c
@@ -49,11 +49,11 @@ appendJSONKeyValue(StringInfo buf, const char *key, const char *value,
return;
appendStringInfoChar(buf, ',');
- escape_json(buf, key);
+ escape_json_cstring(buf, key);
appendStringInfoChar(buf, ':');
if (escape_value)
- escape_json(buf, value);
+ escape_json_cstring(buf, value);
else
appendStringInfoString(buf, value);
}
@@ -143,9 +143,9 @@ write_jsonlog(ErrorData *edata)
* First property does not use appendJSONKeyValue as it does not have
* comma prefix.
*/
- escape_json(&buf, "timestamp");
+ escape_json(&buf, "timestamp", strlen("timestamp"));
appendStringInfoChar(&buf, ':');
- escape_json(&buf, log_time);
+ escape_json_cstring(&buf, log_time);
/* username */
if (MyProcPort)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 6d7f1b387d..8ede8d0bd6 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -17,7 +17,9 @@
#include "lib/stringinfo.h"
/* functions in json.c */
-extern void escape_json(StringInfo buf, const char *str);
+extern void escape_json_cstring(StringInfo buf, const char *str);
+extern void escape_json(StringInfo buf, const char *str, int len);
+extern void escape_json_from_text(StringInfo buf, const text *t);
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
const int *tzp);
extern bool to_json_is_immutable(Oid typoid);
--
2.34.1