diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index bf8d72e9ff9f1d2e027e2823b416d525247729bd..b1e94d7b9e202efe5572d251993270086b047fe6 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10293,6 +10293,32 @@ table2-mapping <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry> <entry><literal>'["a", "b"]'::jsonb ?& array['a', 'b']</literal></entry> </row> + <row> + <entry><literal>||</literal></entry> + <entry><type>jsonb</type></entry> + <entry>Concatentate two jsonb values into a new jsonb value</entry> + <entry><literal>'["a", "b"]'::jsonb || '["c", "d"]'::jsonb</literal></entry> + </row> + <row> + <entry><literal>-</literal></entry> + <entry><type>text</type></entry> + <entry>Delete the field with a specified key, or element with this + value</entry> + <entry><literal>'{"a": "b"}'::jsonb - 'a' </literal></entry> + </row> + <row> + <entry><literal>-</literal></entry> + <entry><type>integer</type></entry> + <entry>Delete the field or element with specified index (Negative + integers count from the end)</entry> + <entry><literal>'["a", "b"]'::jsonb - 1 </literal></entry> + </row> + <row> + <entry><literal>-</literal></entry> + <entry><type>text[]</type></entry> + <entry>Delete the field or element with specified path</entry> + <entry><literal>'["a", {"b":1}]'::jsonb - '{1,b}'::text[] </literal></entry> + </row> </tbody> </tgroup> </table> @@ -10803,6 +10829,42 @@ table2-mapping <entry><literal>json_strip_nulls('[{"f1":1,"f2":null},2,null,3]')</literal></entry> <entry><literal>[{"f1":1},2,null,3]</literal></entry> </row> + <row> + <entry><para><literal>jsonb_replace(target jsonb, path text[], replacement jsonb)</literal> + </para></entry> + <entry><para><type>jsonb</type></para></entry> + <entry> + Returns <replaceable>target</replaceable> + with the section designated by <replaceable>path</replaceable> + replaced by <replaceable>replacement</replaceable>. + </entry> + <entry><literal>jsonb_replace('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]')</literal></entry> + <entry><literal>[{"f1":[2,3,4],"f2":null},2,null,3]</literal> + </entry> + </row> + <row> + <entry><para><literal>jsonb_pretty(from_json jsonb)</literal> + </para></entry> + <entry><para><type>text</type></para></entry> + <entry> + Returns <replaceable>from_json</replaceable> + as indented json text. + </entry> + <entry><literal>jsonb_pretty('[{"f1":1,"f2":null},2,null,3]')</literal></entry> + <entry> +<programlisting> + [ + { + "f1": 1, + "f2": null + }, + 2, + null, + 3 + ] +</programlisting> + </entry> + </row> </tbody> </tgroup> </table> diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 7e2c359b2197171b3811f1061881ea33309bf5d4..bccc6696a4ff0f9dce810fc7c0b8030c1b4302f1 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -85,6 +85,8 @@ static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, static void add_jsonb(Datum val, bool is_null, JsonbInState *result, Oid val_type, bool key_scalar); static JsonbParseState * clone_parse_state(JsonbParseState * state); +static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent); +static void add_indent(StringInfo out, bool indent, int level); /* * jsonb type input function @@ -421,6 +423,24 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) */ char * JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) +{ + return JsonbToCStringWorker(out, in, estimated_len, false); +} + +/* + * same thing but with indentation turned on + */ +char * +JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len) +{ + return JsonbToCStringWorker(out, in, estimated_len, true); +} + +/* + * common worker for above two functions + */ +static char * +JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent) { bool first = true; JsonbIterator *it; @@ -428,6 +448,15 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) JsonbValue v; int level = 0; bool redo_switch = false; + /* If we are indenting, don't add a space after a comma */ + int ispaces = indent ? 1 : 2; + /* + * Don't indent the very first item. This gets set to the indent flag + * at the bottom of the loop. + */ + bool use_indent = false; + bool raw_scalar = false; + bool last_was_key = false; if (out == NULL) out = makeStringInfo(); @@ -444,26 +473,36 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) { case WJB_BEGIN_ARRAY: if (!first) - appendBinaryStringInfo(out, ", ", 2); - first = true; + appendBinaryStringInfo(out, ", ", ispaces); if (!v.val.array.rawScalar) - appendStringInfoChar(out, '['); + { + add_indent(out, use_indent && !last_was_key, level); + appendStringInfoCharMacro(out, '['); + } + else + raw_scalar = true; + + first = true; level++; break; case WJB_BEGIN_OBJECT: if (!first) - appendBinaryStringInfo(out, ", ", 2); - first = true; + appendBinaryStringInfo(out, ", ", ispaces); + + add_indent(out, use_indent && !last_was_key, level); appendStringInfoCharMacro(out, '{'); + first = true; level++; break; case WJB_KEY: if (!first) - appendBinaryStringInfo(out, ", ", 2); + appendBinaryStringInfo(out, ", ", ispaces); first = true; + add_indent(out, use_indent, level); + /* json rules guarantee this is a string */ jsonb_put_escaped_value(out, &v); appendBinaryStringInfo(out, ": ", 2); @@ -488,26 +527,33 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) break; case WJB_ELEM: if (!first) - appendBinaryStringInfo(out, ", ", 2); - else - first = false; + appendBinaryStringInfo(out, ", ", ispaces); + first = false; + if (! raw_scalar) + add_indent(out, use_indent, level); jsonb_put_escaped_value(out, &v); break; case WJB_END_ARRAY: level--; - if (!v.val.array.rawScalar) - appendStringInfoChar(out, ']'); + if (! raw_scalar) + { + add_indent(out, use_indent, level); + appendStringInfoCharMacro(out, ']'); + } first = false; break; case WJB_END_OBJECT: level--; + add_indent(out, use_indent, level); appendStringInfoCharMacro(out, '}'); first = false; break; default: elog(ERROR, "unknown jsonb iterator token type"); } + use_indent = indent; + last_was_key = redo_switch; } Assert(level == 0); @@ -515,6 +561,19 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) return out->data; } +static void +add_indent(StringInfo out, bool indent, int level) +{ + if (indent) + { + int i; + + appendStringInfoCharMacro(out, '\n'); + for (i = 0; i < level; i++) + appendBinaryStringInfo(out, " ", 4); + } +} + /* * Determine how we want to render values of a given type in datum_to_jsonb. diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 5f61cfcb73ae7f1510f489e91868b192f1336398..2d4145512766bbd9c187d9cb519d3edbea27763b 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -124,6 +124,21 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container, char *key, uint32 keylen); +/* functions supporting jsonb_delete, jsonb_replace and jsonb_concat */ +static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, + JsonbParseState **state); +static JsonbValue *walkJsonb(JsonbIterator **it, JsonbParseState **state, bool stop_at_level_zero); +static JsonbValue *replacePath(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, + JsonbParseState **st, int level, Jsonb *newval); +static void replacePathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + Jsonb *newval, uint32 nelems); +static void replacePathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + Jsonb *newval, uint32 npairs); +static void addJsonbToParseState(JsonbParseState **jbps, Jsonb * jb); + /* state for json_object_keys */ typedef struct OkeysState { @@ -3199,3 +3214,705 @@ jsonb_strip_nulls(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(res)); } + +/* + * Add values from the jsonb to the parse state. + * + * If the parse state container is an object, the jsonb is pushed as + * a value, not a key. + * + * This needs to be done using an iterator because pushJsonbValue doesn't + * like getting jbvBinary values, so we can't just push jb as a whole. + */ +static void +addJsonbToParseState(JsonbParseState **jbps, Jsonb * jb) +{ + + JsonbIterator *it; + JsonbValue *o = &(*jbps)->contVal; + int type; + JsonbValue v; + + it = JsonbIteratorInit(&jb->root); + + Assert(o->type == jbvArray || o->type == jbvObject); + + if (JB_ROOT_IS_SCALAR(jb)) + { + (void) JsonbIteratorNext(&it, &v, false); /* skip array header */ + (void) JsonbIteratorNext(&it, &v, false); /* fetch scalar value */ + + switch (o->type) + { + case jbvArray: + (void) pushJsonbValue(jbps, WJB_ELEM, &v); + break; + case jbvObject: + (void) pushJsonbValue(jbps, WJB_VALUE, &v); + break; + default: + elog(ERROR, "unexpected parent of nested structure"); + } + } + else + { + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + if (type == WJB_KEY || type == WJB_VALUE || type == WJB_ELEM) + (void) pushJsonbValue(jbps, type, &v); + else + (void) pushJsonbValue(jbps, type, NULL); + } + } + +} + +/* + * SQL function jsonb_pretty (jsonb) + * + * Pretty-printed text for the jsonb + */ +Datum +jsonb_pretty(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB(0); + StringInfo str = makeStringInfo(); + + JsonbToCStringIndent(str, &jb->root, VARSIZE(jb)); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(str->data, str->len)); +} + + +/* + * SQL function jsonb_concat (jsonb, jsonb) + * + * function for || operator + */ +Datum +jsonb_concat(PG_FUNCTION_ARGS) +{ + Jsonb *jb1 = PG_GETARG_JSONB(0); + Jsonb *jb2 = PG_GETARG_JSONB(1); + Jsonb *out = palloc(VARSIZE(jb1) + VARSIZE(jb2)); + JsonbParseState *state = NULL; + JsonbValue *res; + JsonbIterator *it1, + *it2; + + /* + * If one of the jsonb is empty, just return other. + */ + if (JB_ROOT_COUNT(jb1) == 0) + { + memcpy(out, jb2, VARSIZE(jb2)); + PG_RETURN_POINTER(out); + } + else if (JB_ROOT_COUNT(jb2) == 0) + { + memcpy(out, jb1, VARSIZE(jb1)); + PG_RETURN_POINTER(out); + } + + it1 = JsonbIteratorInit(&jb1->root); + it2 = JsonbIteratorInit(&jb2->root); + + res = IteratorConcat(&it1, &it2, &state); + + if (res == NULL || (res->type == jbvArray && res->val.array.nElems == 0) || + (res->type == jbvObject && res->val.object.nPairs == 0)) + { + SET_VARSIZE(out, VARHDRSZ); + } + else + { + if (res->type == jbvArray && res->val.array.nElems > 1) + res->val.array.rawScalar = false; + + out = JsonbValueToJsonb(res); + } + + PG_RETURN_POINTER(out); +} + + +/* + * SQL function jsonb_delete (jsonb, text) + * + * return a copy of the jsonb with the indicated item + * removed. + */ +Datum +jsonb_delete(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB(0); + text *key = PG_GETARG_TEXT_PP(1); + char *keyptr = VARDATA_ANY(key); + int keylen = VARSIZE_ANY_EXHDR(key); + Jsonb *out = palloc(VARSIZE(in)); + JsonbParseState *state = NULL; + JsonbIterator *it; + uint32 r; + JsonbValue v, + *res = NULL; + bool skipNested = false; + + SET_VARSIZE(out, VARSIZE(in)); + + if (JB_ROOT_COUNT(in) == 0) + PG_RETURN_POINTER(out); + + it = JsonbIteratorInit(&in->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != 0) + { + skipNested = true; + + if ((r == WJB_ELEM || r == WJB_KEY) && + (v.type == jbvString && keylen == v.val.string.len && + memcmp(keyptr, v.val.string.val, keylen) == 0)) + { + /* skip corresponding value as well */ + if (r == WJB_KEY) + JsonbIteratorNext(&it, &v, true); + + continue; + } + + res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + + if (res == NULL || (res->type == jbvArray && res->val.array.nElems == 0) || + (res->type == jbvObject && res->val.object.nPairs == 0)) + SET_VARSIZE(out, VARHDRSZ); + else + out = JsonbValueToJsonb(res); + + PG_RETURN_POINTER(out); +} + +/* + * SQL function jsonb_delete (jsonb, int) + * + * return a copy of the jsonb with the indicated item + * removed. Negative int means count back from the + * end of the items. + */ +Datum +jsonb_delete_idx(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB(0); + int idx = PG_GETARG_INT32(1); + Jsonb *out = palloc(VARSIZE(in)); + JsonbParseState *state = NULL; + JsonbIterator *it; + uint32 r, + i = 0, + n; + JsonbValue v, + *res = NULL; + + if (JB_ROOT_COUNT(in) == 0) + { + memcpy(out, in, VARSIZE(in)); + PG_RETURN_POINTER(out); + } + + it = JsonbIteratorInit(&in->root); + + r = JsonbIteratorNext(&it, &v, false); + if (r == WJB_BEGIN_ARRAY) + n = v.val.array.nElems; + else + n = v.val.object.nPairs; + + if (idx < 0) + { + if (-idx > n) + idx = n; + else + idx = n + idx; + } + + if (idx >= n) + { + memcpy(out, in, VARSIZE(in)); + PG_RETURN_POINTER(out); + } + + pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + + while ((r = JsonbIteratorNext(&it, &v, true)) != 0) + { + if (r == WJB_ELEM || r == WJB_KEY) + { + if (i++ == idx) + { + if (r == WJB_KEY) + JsonbIteratorNext(&it, &v, true); /* skip value */ + continue; + } + } + + res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + + if (res == NULL || (res->type == jbvArray && res->val.array.nElems == 0) || + (res->type == jbvObject && res->val.object.nPairs == 0)) + SET_VARSIZE(out, VARHDRSZ); + else + out = JsonbValueToJsonb(res); + + PG_RETURN_POINTER(out); +} + +/* + * SQL function jsonb_replace(jsonb, text[], jsonb) + */ +Datum +jsonb_replace(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + Jsonb *newval = PG_GETARG_JSONB(2); + Jsonb *out = palloc(VARSIZE(in) + VARSIZE(newval)); + JsonbValue *res = NULL; + Datum *path_elems; + bool *path_nulls; + int path_len; + JsonbIterator *it; + JsonbParseState *st = NULL; + + if (ARR_NDIM(path) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (JB_ROOT_COUNT(in) == 0) + { + memcpy(out, in, VARSIZE(in)); + PG_RETURN_POINTER(out); + } + + deconstruct_array(path, TEXTOID, -1, false, 'i', + &path_elems, &path_nulls, &path_len); + + if (path_len == 0) + { + memcpy(out, in, VARSIZE(in)); + PG_RETURN_POINTER(out); + } + + it = JsonbIteratorInit(&in->root); + + res = replacePath(&it, path_elems, path_nulls, path_len, &st, 0, newval); + + if (res == NULL) + SET_VARSIZE(out, VARHDRSZ); + else + out = JsonbValueToJsonb(res); + + PG_RETURN_POINTER(out); +} + + +/* + * SQL function jsonb_delete(jsonb, text[]) + */ +Datum +jsonb_delete_path(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + Jsonb *out = palloc(VARSIZE(in)); + JsonbValue *res = NULL; + Datum *path_elems; + bool *path_nulls; + int path_len; + JsonbIterator *it; + JsonbParseState *st = NULL; + + if (ARR_NDIM(path) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (JB_ROOT_COUNT(in) == 0) + { + memcpy(out, in, VARSIZE(in)); + PG_RETURN_POINTER(out); + } + + deconstruct_array(path, TEXTOID, -1, false, 'i', + &path_elems, &path_nulls, &path_len); + + if (path_len == 0) + { + memcpy(out, in, VARSIZE(in)); + PG_RETURN_POINTER(out); + } + + it = JsonbIteratorInit(&in->root); + + res = replacePath(&it, path_elems, path_nulls, path_len, &st, 0, NULL); + + if (res == NULL) + SET_VARSIZE(out, VARHDRSZ); + else + out = JsonbValueToJsonb(res); + + PG_RETURN_POINTER(out); +} + + +/* + * Iterate over all jsonb objects and merge them into one. + * The logic of this function copied from the same hstore function, + * except the case, when it1 & it2 represents jbvObject. + * In that case we just append the content of it2 to it1 without any + * verifications. + */ +static JsonbValue * +IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, + JsonbParseState **state) +{ + uint32 r1, + r2, + rk1, + rk2; + JsonbValue v1, + v2, + *res = NULL; + + r1 = rk1 = JsonbIteratorNext(it1, &v1, false); + r2 = rk2 = JsonbIteratorNext(it2, &v2, false); + + /* + * Both elements are objects. + */ + if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT) + { + int level = 1; + + /* + * Append the all tokens from v1 to res, exept last WJB_END_OBJECT + * (because res will not be finished yet). + */ + (void) pushJsonbValue(state, r1, NULL); + while ((r1 = JsonbIteratorNext(it1, &v1, false)) != 0) + { + if (r1 == WJB_BEGIN_OBJECT) + ++level; + else if (r1 == WJB_END_OBJECT) + --level; + + if (level != 0) + res = pushJsonbValue(state, r1, r1 < WJB_BEGIN_ARRAY ? &v1 : NULL); + } + + /* + * Append the all tokens from v2 to res, include last WJB_END_OBJECT + * (the concatenation will be completed). + */ + while ((r2 = JsonbIteratorNext(it2, &v2, false)) != 0) + res = pushJsonbValue(state, r2, r2 < WJB_BEGIN_ARRAY ? &v2 : NULL); + } + + /* + * Both elements are arrays (either can be scalar). + */ + else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY) + { + res = pushJsonbValue(state, r1, NULL); + for (;;) + { + r1 = JsonbIteratorNext(it1, &v1, true); + if (r1 == WJB_END_OBJECT || r1 == WJB_END_ARRAY) + break; + + Assert(r1 == WJB_KEY || r1 == WJB_VALUE || r1 == WJB_ELEM); + pushJsonbValue(state, r1, &v1); + } + + while ((r2 = JsonbIteratorNext(it2, &v2, true)) != 0) + { + if (!(r2 == WJB_END_OBJECT || r2 == WJB_END_ARRAY)) + { + if (rk1 == WJB_BEGIN_OBJECT) + { + pushJsonbValue(state, WJB_KEY, NULL); + r2 = JsonbIteratorNext(it2, &v2, true); + Assert(r2 == WJB_ELEM); + pushJsonbValue(state, WJB_VALUE, &v2); + } + else + pushJsonbValue(state, WJB_ELEM, &v2); + } + } + + res = pushJsonbValue(state, + (rk1 == WJB_BEGIN_OBJECT) ? WJB_END_OBJECT : WJB_END_ARRAY, + NULL /* signal to sort */ ); + } + /* have we got array || object or object || array? */ + else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) || + (rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar))) + { + + JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2; + JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2; + + bool prepend = (rk1 == WJB_BEGIN_OBJECT) ? true : false; + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + if (prepend) + { + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + walkJsonb(it_object, state, false); + + res = walkJsonb(it_array, state, false); + } + else + { + walkJsonb(it_array, state, true); + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + walkJsonb(it_object, state, false); + + res = pushJsonbValue(state, WJB_END_ARRAY, NULL); + } + } + else + { + /* + * This must be scalar || object or object || scalar, as that's all + * that's left. Both of these make no sense, so error out. + */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid concatenation of jsonb objects"))); + } + + return res; +} + +/* + * copy elements from the iterator to the parse state + * stopping at level zero if required. + */ +static JsonbValue * +walkJsonb(JsonbIterator **it, JsonbParseState **state, bool stop_at_level_zero) +{ + uint32 r, + level = 1; + JsonbValue v; + JsonbValue *res = NULL; + + while ((r = JsonbIteratorNext(it, &v, false)) != WJB_DONE) + { + if (r == WJB_BEGIN_OBJECT || r == WJB_BEGIN_ARRAY) + ++level; + else if (r == WJB_END_OBJECT || r == WJB_END_ARRAY) + --level; + + if (stop_at_level_zero && level == 0) + break; + + res = pushJsonbValue(state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + + return res; +} + + +/* + * do most of the heavy work for jsonb_replace + */ +static JsonbValue * +replacePath(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, + JsonbParseState **st, int level, Jsonb *newval) +{ + JsonbValue v; + JsonbValue *res = NULL; + int r; + + r = JsonbIteratorNext(it, &v, false); + + switch (r) + { + case WJB_BEGIN_ARRAY: + (void) pushJsonbValue(st, r, NULL); + replacePathArray(it, path_elems, path_nulls, path_len, st, level, + newval, v.val.array.nElems); + r = JsonbIteratorNext(it, &v, false); + Assert(r == WJB_END_ARRAY); + res = pushJsonbValue(st, r, NULL); + + break; + case WJB_BEGIN_OBJECT: + (void) pushJsonbValue(st, r, NULL); + replacePathObject(it, path_elems, path_nulls, path_len, st, level, + newval, v.val.object.nPairs); + r = JsonbIteratorNext(it, &v, true); + Assert(r == WJB_END_OBJECT); + res = pushJsonbValue(st, r, NULL); + + break; + case WJB_ELEM: + case WJB_VALUE: + res = pushJsonbValue(st, r, &v); + break; + default: + elog(ERROR, "impossible state"); + } + + return res; +} + +/* + * Object walker for replacePath + */ +static void +replacePathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + Jsonb *newval, uint32 nelems) +{ + JsonbValue v; + int i; + JsonbValue k; + bool done = false; + + if (level >= path_len || path_nulls[level]) + done = true; + + for (i = 0; i < nelems; i++) + { + int r = JsonbIteratorNext(it, &k, true); + Assert(r == WJB_KEY); + + if (!done && + k.val.string.len == VARSIZE_ANY_EXHDR(path_elems[level]) && + memcmp(k.val.string.val, VARDATA_ANY(path_elems[level]), + k.val.string.len) == 0) + { + if (level == path_len - 1) + { + r = JsonbIteratorNext(it, &v, true); /* skip */ + if (newval != NULL) + { + (void) pushJsonbValue(st, WJB_KEY, &k); + addJsonbToParseState(st, newval); + } + } + else + { + (void) pushJsonbValue(st, r, &k); + replacePath(it, path_elems, path_nulls, path_len, + st, level + 1, newval); + } + } + else + { + (void) pushJsonbValue(st, r, &k); + r = JsonbIteratorNext(it, &v, false); + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + { + int walking_level = 1; + + while (walking_level != 0) + { + r = JsonbIteratorNext(it, &v, false); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + ++walking_level; + if (r == WJB_END_ARRAY || r == WJB_END_OBJECT) + --walking_level; + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + } + } + } +} + +/* + * Array walker for replacePath + */ +static void +replacePathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + Jsonb *newval, uint32 npairs) +{ + JsonbValue v; + int idx, + i; + char *badp; + + /* pick correct index */ + if (level < path_len && !path_nulls[level]) + { + char *c = VARDATA_ANY(path_elems[level]); + + errno = 0; + idx = (int) strtol(c, &badp, 10); + if (errno != 0 || badp == c) + idx = npairs; + } + else + idx = npairs; + + if (idx < 0) + { + if (-idx > npairs) + idx = npairs; + else + idx = npairs + idx; + } + + if (idx > npairs) + idx = npairs; + + /* iterate over the array elements */ + for (i = 0; i < npairs; i++) + { + int r; + + if (i == idx && level < path_len) + { + if (level == path_len - 1) + { + r = JsonbIteratorNext(it, &v, true); /* skip */ + if (newval != NULL) + addJsonbToParseState(st, newval); + } + else + (void) replacePath(it, path_elems, path_nulls, path_len, + st, level + 1, newval); + } + else + { + r = JsonbIteratorNext(it, &v, false); + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + { + int walking_level = 1; + + while (walking_level != 0) + { + r = JsonbIteratorNext(it, &v, false); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + ++walking_level; + if (r == WJB_END_ARRAY || r == WJB_END_OBJECT) + --walking_level; + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + } + } + } +} diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index e22eb27bd523c89375f4686846b8fa4adda4115e..34ebb50ea5f7cb361831ea00626ef299c004985c 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -1809,6 +1809,14 @@ DATA(insert OID = 3249 ( "?&" PGNSP PGUID b f f 3802 1009 16 0 0 jsonb_exist DESCR("exists all"); DATA(insert OID = 3250 ( "<@" PGNSP PGUID b f f 3802 3802 16 3246 0 jsonb_contained contsel contjoinsel )); DESCR("is contained by"); +DATA(insert OID = 3284 ( "||" PGNSP PGUID b f f 3802 3802 3802 0 0 jsonb_concat - - )); +DESCR("concatenate"); +DATA(insert OID = 3285 ( "-" PGNSP PGUID b f f 3802 25 3802 0 0 3302 - - )); +DESCR("delete"); +DATA(insert OID = 3286 ( "-" PGNSP PGUID b f f 3802 23 3802 0 0 3303 - - )); +DESCR("delete"); +DATA(insert OID = 3287 ( "-" PGNSP PGUID b f f 3802 1009 3802 0 0 3304 - - )); +DESCR("delete"); /* * function prototypes diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 41838c0a8d36d318fac4d0a31f34b10b0e7c2b84..a1e244223d6ab0595513cf565c2b91a59d353e93 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4841,7 +4841,14 @@ DATA(insert OID = 3487 ( gin_consistent_jsonb_path PGNSP PGUID 12 1 0 0 0 f f DESCR("GIN support"); DATA(insert OID = 3489 ( gin_triconsistent_jsonb_path PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 18 "2281 21 2277 23 2281 2281 2281" _null_ _null_ _null_ _null_ _null_ gin_triconsistent_jsonb_path _null_ _null_ _null_ )); DESCR("GIN support"); - +DATA(insert OID = 3301 ( jsonb_concat PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 3802" _null_ _null_ _null_ _null_ _null_ jsonb_concat _null_ _null_ _null_ )); +DATA(insert OID = 3302 ( jsonb_delete PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 25" _null_ _null_ _null_ _null_ _null_ jsonb_delete _null_ _null_ _null_ )); +DATA(insert OID = 3303 ( jsonb_delete PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 23" _null_ _null_ _null_ _null_ _null_ jsonb_delete_idx _null_ _null_ _null_ )); +DATA(insert OID = 3304 ( jsonb_delete PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ _null_ _null_ _null_ jsonb_delete_path _null_ _null_ _null_ )); +DATA(insert OID = 3305 ( jsonb_replace PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 3802 "3802 1009 3802" _null_ _null_ _null_ _null_ _null_ jsonb_replace _null_ _null_ _null_ )); +DESCR("Replace part of a jsonb"); +DATA(insert OID = 3306 ( jsonb_pretty PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ _null_ jsonb_pretty _null_ _null_ _null_ )); +DESCR("Indented text from jsonb"); /* txid */ DATA(insert OID = 2939 ( txid_snapshot_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2970 "2275" _null_ _null_ _null_ _null_ _null_ txid_snapshot_in _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 9d1770e4c942829df641f6c4378fbf4eb6b56187..7b561759f7f22d01f80de12369250f1fa972fbcf 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -394,6 +394,20 @@ extern Datum gin_extract_jsonb_query_path(PG_FUNCTION_ARGS); extern Datum gin_consistent_jsonb_path(PG_FUNCTION_ARGS); extern Datum gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS); +/* pretty printer, returns text */ +extern Datum jsonb_pretty(PG_FUNCTION_ARGS); + +/* concatenation */ +extern Datum jsonb_concat(PG_FUNCTION_ARGS); + +/* deletion */ +Datum jsonb_delete(PG_FUNCTION_ARGS); +Datum jsonb_delete_idx(PG_FUNCTION_ARGS); +Datum jsonb_delete_path(PG_FUNCTION_ARGS); + +/* replacement */ +extern Datum jsonb_replace(PG_FUNCTION_ARGS); + /* Support functions */ extern uint32 getJsonbOffset(const JsonbContainer *jc, int index); extern uint32 getJsonbLength(const JsonbContainer *jc, int index); @@ -413,8 +427,11 @@ extern bool JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained); extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash); -/* jsonb.c support function */ +/* jsonb.c support functions */ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len); +extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, + int estimated_len); + #endif /* __JSONB_H__ */ diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 0d558901e9d84302077c8ce1856d0549b6ea7dc1..83201fb6ca500546f196d8b7519b4c00ae65b556 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2276,7 +2276,7 @@ SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * 894 (1 row) -SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j); +SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j); j ---- {} @@ -2753,3 +2753,425 @@ select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); {"a": {}, "d": {}} (1 row) +select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); + jsonb_pretty +---------------------------- + { + + "a": "test", + + "b": [ + + 1, + + 2, + + 3 + + ], + + "c": "test3", + + "d": { + + "dd": "test4", + + "dd2": { + + "ddd": "test5"+ + } + + } + + } +(1 row) + +select jsonb_pretty('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]'); + jsonb_pretty +--------------------------- + [ + + { + + "f1": 1, + + "f2": null + + }, + + 2, + + null, + + [ + + [ + + { + + "x": true+ + }, + + 6, + + 7 + + ], + + 8 + + ], + + 3 + + ] +(1 row) + +select jsonb_pretty('{"a":["b", "c"], "d": {"e":"f"}}'); + jsonb_pretty +------------------ + { + + "a": [ + + "b", + + "c" + + ], + + "d": { + + "e": "f"+ + } + + } +(1 row) + +select jsonb_concat('{"d": "test", "a": [1, 2]}', '{"g": "test2", "c": {"c1":1, "c2":2}}'); + jsonb_concat +------------------------------------------------------------------- + {"a": [1, 2], "c": {"c1": 1, "c2": 2}, "d": "test", "g": "test2"} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +--------------------------------------------- + {"b": "g", "aa": 1, "cq": "l", "fg": false} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aq":"l"}'; + ?column? +--------------------------------------- + {"b": 2, "aa": 1, "aq": "l", "cq": 3} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aa":"l"}'; + ?column? +------------------------------ + {"b": 2, "aa": "l", "cq": 3} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{}'; + ?column? +---------------------------- + {"b": 2, "aa": 1, "cq": 3} +(1 row) + +select '["a", "b"]'::jsonb || '["c"]'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '["a", "b"]'::jsonb || '["c", "d"]'; + ?column? +---------------------- + ["a", "b", "c", "d"] +(1 row) + +select '["c"]' || '["a", "b"]'::jsonb; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +select '["a", "b"]'::jsonb || '"c"'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '"c"' || '["a", "b"]'::jsonb; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +select '"a"'::jsonb || '{"a":1}'; +ERROR: invalid concatenation of jsonb objects +select '{"a":1}' || '"a"'::jsonb; +ERROR: invalid concatenation of jsonb objects +select '["a", "b"]'::jsonb || '{"c":1}'; + ?column? +---------------------- + ["a", "b", {"c": 1}] +(1 row) + +select '{"c": 1}'::jsonb || '["a", "b"]'; + ?column? +---------------------- + [{"c": 1}, "a", "b"] +(1 row) + +select '{}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +------------------------------------ + {"b": "g", "cq": "l", "fg": false} +(1 row) + +select pg_column_size('{}'::jsonb || '{}'::jsonb) = pg_column_size('{}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{"aa":1}'::jsonb || '{"b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{"aa":1, "b":2}'::jsonb || '{}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{}'::jsonb || '{"aa":1, "b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); + jsonb_delete +------------------ + {"b": 2, "c": 3} +(1 row) + +select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); + jsonb_delete +------------------ + {"b": 2, "c": 3} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); + jsonb_delete +------------------ + {"a": 1, "c": 3} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); + jsonb_delete +------------------ + {"a": 1, "b": 2} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); + jsonb_delete +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'::text; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":null , "b":2, "c":3}'::jsonb - 'a'::text; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'::text; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'::text; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'::text; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select pg_column_size('{"a":1 , "b":2, "c":3}'::jsonb - 'b'::text) = pg_column_size('{"a":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select '["a","b","c"]'::jsonb - 3; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - 2; + ?column? +------------ + ["a", "b"] +(1 row) + +select '["a","b","c"]'::jsonb - 1; + ?column? +------------ + ["a", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - 0; + ?column? +------------ + ["b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -1; + ?column? +------------ + ["a", "b"] +(1 row) + +select '["a","b","c"]'::jsonb - -2; + ?column? +------------ + ["a", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -3; + ?column? +------------ + ["b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -4; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 3; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 2; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 1; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 0; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -1; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -2; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -3; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -4; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '[1,2,3]'); + jsonb_replace +-------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": [1, 2, 3]} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '[1,2,3]'); + jsonb_replace +----------------------------------------------------------------------------- + {"a": 1, "b": [1, [1, 2, 3]], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '[1,2,3]'); + jsonb_replace +----------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [[1, 2, 3], 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '[1,2,3]'); + jsonb_replace +--------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '{"1": 2}'); + jsonb_replace +------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": {"1": 2}} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"1": 2}'); + jsonb_replace +---------------------------------------------------------------------------- + {"a": 1, "b": [1, {"1": 2}], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '{"1": 2}'); + jsonb_replace +---------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [{"1": 2}, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '{"1": 2}'); + jsonb_replace +--------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '"test"'); + jsonb_replace +-------------------------------------------------------------------------- + {"a": 1, "b": [1, "test"], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"f": "test"}'); + jsonb_replace +--------------------------------------------------------------------------------- + {"a": 1, "b": [1, {"f": "test"}], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'::text[]); + jsonb_delete +---------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}} +(1 row) + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'::text[]); + jsonb_delete +------------------------------------------------------------------ + {"a": 1, "b": [1], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'::text[]); + jsonb_delete +------------------------------------------------------------------ + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [3]}, "n": null} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{n}'::text[]; + ?column? +---------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{b,-1}'::text[]; + ?column? +------------------------------------------------------------------ + {"a": 1, "b": [1], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{d,1,0}'::text[]; + ?column? +------------------------------------------------------------------ + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [3]}, "n": null} +(1 row) + diff --git a/src/test/regress/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out index 694b6ea5f5caf8169d5fbaa5854a3e0c4101c912..8c5759414666f760c00d0b39407750ad5888ad2e 100644 --- a/src/test/regress/expected/jsonb_1.out +++ b/src/test/regress/expected/jsonb_1.out @@ -2276,7 +2276,7 @@ SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * 894 (1 row) -SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j); +SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j); j ---- {} @@ -2753,3 +2753,425 @@ select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); {"a": {}, "d": {}} (1 row) +select jsonb_indent('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); + jsonb_indent +---------------------------- + { + + "a": "test", + + "b": [ + + 1, + + 2, + + 3 + + ], + + "c": "test3", + + "d": { + + "dd": "test4", + + "dd2": { + + "ddd": "test5"+ + } + + } + + } +(1 row) + +select jsonb_indent('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]'); + jsonb_indent +--------------------------- + [ + + { + + "f1": 1, + + "f2": null + + }, + + 2, + + null, + + [ + + [ + + { + + "x": true+ + }, + + 6, + + 7 + + ], + + 8 + + ], + + 3 + + ] +(1 row) + +select jsonb_indent('{"a":["b", "c"], "d": {"e":"f"}}'); + jsonb_indent +------------------ + { + + "a": [ + + "b", + + "c" + + ], + + "d": { + + "e": "f"+ + } + + } +(1 row) + +select jsonb_concat('{"d": "test", "a": [1, 2]}', '{"g": "test2", "c": {"c1":1, "c2":2}}'); + jsonb_concat +------------------------------------------------------------------- + {"a": [1, 2], "c": {"c1": 1, "c2": 2}, "d": "test", "g": "test2"} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +--------------------------------------------- + {"b": "g", "aa": 1, "cq": "l", "fg": false} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aq":"l"}'; + ?column? +--------------------------------------- + {"b": 2, "aa": 1, "aq": "l", "cq": 3} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aa":"l"}'; + ?column? +------------------------------ + {"b": 2, "aa": "l", "cq": 3} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{}'; + ?column? +---------------------------- + {"b": 2, "aa": 1, "cq": 3} +(1 row) + +select '["a", "b"]'::jsonb || '["c"]'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '["a", "b"]'::jsonb || '["c", "d"]'; + ?column? +---------------------- + ["a", "b", "c", "d"] +(1 row) + +select '["c"]' || '["a", "b"]'::jsonb; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +select '["a", "b"]'::jsonb || '"c"'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '"c"' || '["a", "b"]'::jsonb; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +select '"a"'::jsonb || '{"a":1}'; +ERROR: invalid concatenation of jsonb objects +select '{"a":1}' || '"a"'::jsonb; +ERROR: invalid concatenation of jsonb objects +select '["a", "b"]'::jsonb || '{"c":1}'; + ?column? +---------------------- + ["a", "b", {"c": 1}] +(1 row) + +select '{"c": 1}'::jsonb || '["a", "b"]'; + ?column? +---------------------- + [{"c": 1}, "a", "b"] +(1 row) + +select '{}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +------------------------------------ + {"b": "g", "cq": "l", "fg": false} +(1 row) + +select pg_column_size('{}'::jsonb || '{}'::jsonb) = pg_column_size('{}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{"aa":1}'::jsonb || '{"b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{"aa":1, "b":2}'::jsonb || '{}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{}'::jsonb || '{"aa":1, "b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); + jsonb_delete +------------------ + {"b": 2, "c": 3} +(1 row) + +select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); + jsonb_delete +------------------ + {"b": 2, "c": 3} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); + jsonb_delete +------------------ + {"a": 1, "c": 3} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); + jsonb_delete +------------------ + {"a": 1, "b": 2} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); + jsonb_delete +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'::text; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":null , "b":2, "c":3}'::jsonb - 'a'::text; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'::text; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'::text; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'::text; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select pg_column_size('{"a":1 , "b":2, "c":3}'::jsonb - 'b'::text) = pg_column_size('{"a":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select '["a","b","c"]'::jsonb - 3; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - 2; + ?column? +------------ + ["a", "b"] +(1 row) + +select '["a","b","c"]'::jsonb - 1; + ?column? +------------ + ["a", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - 0; + ?column? +------------ + ["b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -1; + ?column? +------------ + ["a", "b"] +(1 row) + +select '["a","b","c"]'::jsonb - -2; + ?column? +------------ + ["a", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -3; + ?column? +------------ + ["b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -4; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 3; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 2; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 1; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - 0; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -1; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -2; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -3; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1, "b":2, "c":3}'::jsonb - -4; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '[1,2,3]'); + jsonb_replace +-------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": [1, 2, 3]} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '[1,2,3]'); + jsonb_replace +----------------------------------------------------------------------------- + {"a": 1, "b": [1, [1, 2, 3]], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '[1,2,3]'); + jsonb_replace +----------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [[1, 2, 3], 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '[1,2,3]'); + jsonb_replace +--------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '{"1": 2}'); + jsonb_replace +------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": {"1": 2}} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"1": 2}'); + jsonb_replace +---------------------------------------------------------------------------- + {"a": 1, "b": [1, {"1": 2}], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '{"1": 2}'); + jsonb_replace +---------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [{"1": 2}, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '{"1": 2}'); + jsonb_replace +--------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '"test"'); + jsonb_replace +-------------------------------------------------------------------------- + {"a": 1, "b": [1, "test"], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"f": "test"}'); + jsonb_replace +--------------------------------------------------------------------------------- + {"a": 1, "b": [1, {"f": "test"}], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'::text[]); + jsonb_delete +---------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}} +(1 row) + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'::text[]); + jsonb_delete +------------------------------------------------------------------ + {"a": 1, "b": [1], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'::text[]); + jsonb_delete +------------------------------------------------------------------ + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [3]}, "n": null} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{n}'::text[]; + ?column? +---------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{b,-1}'::text[]; + ?column? +------------------------------------------------------------------ + {"a": 1, "b": [1], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{d,1,0}'::text[]; + ?column? +------------------------------------------------------------------ + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [3]}, "n": null} +(1 row) + diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 676e1a7d4c93767602b96bdfc6a39a13f020cc45..808da9c027021b142ffb02413805ac5b4afeeb10 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -557,7 +557,7 @@ SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * SET enable_hashagg = on; SET enable_sort = off; SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; -SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j); +SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j); SET enable_sort = on; RESET enable_hashagg; @@ -684,3 +684,86 @@ select jsonb_strip_nulls('[1,{"a":1,"b":null,"c":2},3]'); -- an empty object is not null and should not be stripped select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); + + +select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); +select jsonb_pretty('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]'); +select jsonb_pretty('{"a":["b", "c"], "d": {"e":"f"}}'); + +select jsonb_concat('{"d": "test", "a": [1, 2]}', '{"g": "test2", "c": {"c1":1, "c2":2}}'); + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aq":"l"}'; +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aa":"l"}'; +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{}'; + +select '["a", "b"]'::jsonb || '["c"]'; +select '["a", "b"]'::jsonb || '["c", "d"]'; +select '["c"]' || '["a", "b"]'::jsonb; + +select '["a", "b"]'::jsonb || '"c"'; +select '"c"' || '["a", "b"]'::jsonb; + +select '"a"'::jsonb || '{"a":1}'; +select '{"a":1}' || '"a"'::jsonb; + +select '["a", "b"]'::jsonb || '{"c":1}'; +select '{"c": 1}'::jsonb || '["a", "b"]'; + +select '{}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + +select pg_column_size('{}'::jsonb || '{}'::jsonb) = pg_column_size('{}'::jsonb); +select pg_column_size('{"aa":1}'::jsonb || '{"b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); +select pg_column_size('{"aa":1, "b":2}'::jsonb || '{}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); +select pg_column_size('{}'::jsonb || '{"aa":1, "b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); +select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); +select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'::text; +select '{"a":null , "b":2, "c":3}'::jsonb - 'a'::text; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'::text; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'::text; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'::text; +select pg_column_size('{"a":1 , "b":2, "c":3}'::jsonb - 'b'::text) = pg_column_size('{"a":1, "b":2}'::jsonb); + +select '["a","b","c"]'::jsonb - 3; +select '["a","b","c"]'::jsonb - 2; +select '["a","b","c"]'::jsonb - 1; +select '["a","b","c"]'::jsonb - 0; +select '["a","b","c"]'::jsonb - -1; +select '["a","b","c"]'::jsonb - -2; +select '["a","b","c"]'::jsonb - -3; +select '["a","b","c"]'::jsonb - -4; + +select '{"a":1, "b":2, "c":3}'::jsonb - 3; +select '{"a":1, "b":2, "c":3}'::jsonb - 2; +select '{"a":1, "b":2, "c":3}'::jsonb - 1; +select '{"a":1, "b":2, "c":3}'::jsonb - 0; +select '{"a":1, "b":2, "c":3}'::jsonb - -1; +select '{"a":1, "b":2, "c":3}'::jsonb - -2; +select '{"a":1, "b":2, "c":3}'::jsonb - -3; +select '{"a":1, "b":2, "c":3}'::jsonb - -4; + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '[1,2,3]'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '[1,2,3]'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '[1,2,3]'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '[1,2,3]'); + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '{"1": 2}'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"1": 2}'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '{"1": 2}'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '{"1": 2}'); + +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '"test"'); +select jsonb_replace('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"f": "test"}'); + +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'::text[]); +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'::text[]); +select jsonb_delete('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'::text[]); + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{n}'::text[]; +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{b,-1}'::text[]; +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb - '{d,1,0}'::text[];