diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index bf2c91f02ea..0db37234fc9 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -52,6 +52,25 @@ typedef struct OkeysState int sent_count; } OkeysState; +/* state for iterate_json_string_values function */ +typedef struct IterateJsonStringValuesState +{ + JsonLexContext *lex; + JsonIterateStringValuesAction action; /* an action that will be applied + to each json value */ + void *action_state; /* any necessary context for iteration */ +} IterateJsonStringValuesState; + +/* state for transform_json_string_values function */ +typedef struct TransformJsonStringValuesState +{ + JsonLexContext *lex; + StringInfo strval; /* resulting json */ + JsonTransformStringValuesAction action; /* an action that will be applied + to each json value */ + void *action_state; /* any necessary context for transformation */ +} TransformJsonStringValuesState; + /* state for json_get* functions */ typedef struct GetState { @@ -271,6 +290,18 @@ static void setPathArray(JsonbIterator **it, Datum *path_elems, int level, Jsonb *newval, uint32 nelems, int op_type); static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb); +/* function supporting iterate_json_string_values */ +static void iterate_string_values_scalar(void *state, char *token, JsonTokenType tokentype); + +/* functions supporting transform_json_string_values */ +static void transform_string_values_object_start(void *state); +static void transform_string_values_object_end(void *state); +static void transform_string_values_array_start(void *state); +static void transform_string_values_array_end(void *state); +static void transform_string_values_object_field_start(void *state, char *fname, bool isnull); +static void transform_string_values_array_element_start(void *state, bool isnull); +static void transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype); + /* * SQL function json_object_keys @@ -4130,3 +4161,208 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, } } } + +/* + * Iterate over jsonb string values or elements, and pass them together with an + * iteration state to a specified JsonIterateStringValuesAction. + */ +void +iterate_jsonb_string_values(Jsonb *jb, void *state, JsonIterateStringValuesAction action) +{ + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken type; + + it = JsonbIteratorInit(&jb->root); + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString) + { + action(state, v.val.string.val, v.val.string.len); + } + } +} + +/* + * Iterate over json string values or elements, and pass them together with an + * iteration state to a specified JsonIterateStringValuesAction. + */ +void +iterate_json_string_values(text *json, void *action_state, JsonIterateStringValuesAction action) +{ + JsonLexContext *lex = makeJsonLexContext(json, true); + JsonSemAction *sem = palloc0(sizeof(JsonSemAction)); + IterateJsonStringValuesState *state = palloc0(sizeof(IterateJsonStringValuesState)); + + state->lex = lex; + state->action = action; + state->action_state = action_state; + + sem->semstate = (void *) state; + sem->scalar = iterate_string_values_scalar; + + pg_parse_json(lex, sem); +} + +/* + * An auxiliary function for iterate_json_string_values to invoke a specified + * JsonIterateStringValuesAction. + */ +static void +iterate_string_values_scalar(void *state, char *token, JsonTokenType tokentype) +{ + IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state; + if (tokentype == JSON_TOKEN_STRING) + (*_state->action) (_state->action_state, token, strlen(token)); +} + +/* + * Iterate over a jsonb, and apply a specified JsonTransformStringValuesAction + * to every string value or element. Any necessary context for a + * JsonTransformStringValuesAction can be passed in the action_state variable. + * Function returns a copy of an original jsonb object with transformed values. + */ +Jsonb * +transform_jsonb_string_values(Jsonb *jsonb, void *action_state, + JsonTransformStringValuesAction transform_action) +{ + JsonbIterator *it; + JsonbValue v, *res = NULL; + JsonbIteratorToken type; + JsonbParseState *st = NULL; + text *out; + bool is_scalar = false; + + it = JsonbIteratorInit(&jsonb->root); + is_scalar = it->isScalar; + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString) + { + out = transform_action(action_state, v.val.string.val, v.val.string.len); + v.val.string.val = VARDATA_ANY(out); + v.val.string.len = VARSIZE_ANY_EXHDR(out); + res = pushJsonbValue(&st, type, type < WJB_BEGIN_ARRAY ? &v : NULL); + } + else + { + res = pushJsonbValue(&st, type, (type == WJB_KEY || + type == WJB_VALUE || + type == WJB_ELEM) ? &v : NULL); + } + } + + if (res->type == jbvArray) + res->val.array.rawScalar = is_scalar; + + return JsonbValueToJsonb(res); +} + +/* + * Iterate over a json, and apply a specified JsonTransformStringValuesAction + * to every string value or element. Any necessary context for a + * JsonTransformStringValuesAction can be passed in the action_state variable. + * Function returns a StringInfo, which is a copy of an original json with + * transformed values. + */ +text * +transform_json_string_values(text *json, void *action_state, + JsonTransformStringValuesAction transform_action) +{ + JsonLexContext *lex = makeJsonLexContext(json, true); + JsonSemAction *sem = palloc0(sizeof(JsonSemAction)); + TransformJsonStringValuesState *state = palloc0(sizeof(TransformJsonStringValuesState)); + + state->lex = lex; + state->strval = makeStringInfo(); + state->action = transform_action; + state->action_state = action_state; + + sem->semstate = (void *) state; + sem->scalar = transform_string_values_scalar; + sem->object_start = transform_string_values_object_start; + sem->object_end = transform_string_values_object_end; + sem->array_start = transform_string_values_array_start; + sem->array_end = transform_string_values_array_end; + sem->scalar = transform_string_values_scalar; + sem->array_element_start = transform_string_values_array_element_start; + sem->object_field_start = transform_string_values_object_field_start; + + pg_parse_json(lex, sem); + + return cstring_to_text_with_len(state->strval->data, state->strval->len); +} + +/* + * Set of auxiliary functions for transform_json_string_values to invoke a + * specified JsonTransformStringValuesAction for all values and left everything + * else untouched. + */ +static void +transform_string_values_object_start(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + appendStringInfoCharMacro(_state->strval, '{'); +} + +static void +transform_string_values_object_end(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + appendStringInfoCharMacro(_state->strval, '}'); +} + +static void +transform_string_values_array_start(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + appendStringInfoCharMacro(_state->strval, '['); +} + +static void +transform_string_values_array_end(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + appendStringInfoCharMacro(_state->strval, ']'); +} + +static void +transform_string_values_object_field_start(void *state, char *fname, bool isnull) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + if (_state->strval->data[_state->strval->len - 1] != '{') + appendStringInfoCharMacro(_state->strval, ','); + + /* + * Unfortunately we don't have the quoted and escaped string any more, so + * we have to re-escape it. + */ + escape_json(_state->strval, fname); + appendStringInfoCharMacro(_state->strval, ':'); +} + +static void +transform_string_values_array_element_start(void *state, bool isnull) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + if (_state->strval->data[_state->strval->len - 1] != '[') + appendStringInfoCharMacro(_state->strval, ','); +} + +static void +transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + if (tokentype == JSON_TOKEN_STRING) + { + text *out = (*_state->action) (_state->action_state, token, strlen(token)); + escape_json(_state->strval, text_to_cstring(out)); + } + else + appendStringInfoString(_state->strval, token); +} diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 8f132d732be..50c29c3a866 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -14,6 +14,7 @@ #ifndef JSONAPI_H #define JSONAPI_H +#include "jsonb.h" #include "lib/stringinfo.h" typedef enum @@ -131,4 +132,19 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json, */ extern bool IsValidJsonNumber(const char *str, int len); +/* an action that will be applied to each value in iterate_json(b)_string_vaues functions */ +typedef void (*JsonIterateStringValuesAction) (void *state, char *elem_value, int elem_len); + +/* an action that will be applied to each value in transform_json(b)_string_values functions */ +typedef text * (*JsonTransformStringValuesAction) (void *state, char *elem_value, int elem_len); + +extern void iterate_jsonb_string_values(Jsonb *jb, void *state, + JsonIterateStringValuesAction action); +extern void iterate_json_string_values(text *json, void *action_state, + JsonIterateStringValuesAction action); +extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state, + JsonTransformStringValuesAction transform_action); +extern text *transform_json_string_values(text *json, void *action_state, + JsonTransformStringValuesAction transform_action); + #endif /* JSONAPI_H */