mogrify and indent features for jsonb

Started by Andrew Dunstanalmost 11 years ago31 messages
#1Andrew Dunstan
andrew@dunslane.net
1 attachment(s)

Attached is a patch to provide a number of very useful facilities to
jsonb that people have asked for. These are based on work by Dmitry
Dolgov in his jsonbx extension, but I take responsibility for any bugs.

The facilities are:

new operations:

concatenation: jsonb || jsonb -> jsonb
deletion: jsonb - text -> jsonb
deletion: jsonb - int -> text

new functions:

produce indented text: jsonb_indent(jsonb) -> text
change an element at a path: jsonb_replace(jsonb, text[], jsonb) -> jsonb.

It would be relatively trivial to add:

delete an element at a path: jsonb_delete(jsonb, text[]) -> json

and I think we should do that for the sake of completeness.

The docs might need a little extra work, and the indent code definitely
needs work, which I hope to complete in the next day or two, but I
wanted to put a stake in the ground.

cheers

andrew

Attachments:

jsonbxcore1.patchtext/x-patch; name=jsonbxcore1.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d57243a..9fdaee7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10256,6 +10256,24 @@ table2-mapping
         <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry>
         <entry><literal>'["a", "b"]'::jsonb ?&amp; array['a', 'b']</literal></entry>
        </row>
+       <row>
+        <entry><literal>||</literal></entry>
+        <entry><type>jsonb</type></entry>
+        <entry>Concatentate these two values to make a new 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 object field with this key</entry>
+        <entry><literal>'{"a": "b"}'::jsonb - 'a' </literal></entry>
+       </row>
+       <row>
+        <entry><literal>-</literal></entry>
+        <entry><type>integer</type></entry>
+        <entry>Delete the element with this index (Negative integers count from the end of the array.)</entry>
+        <entry><literal>'["a", "b"]'::jsonb - 1 </literal></entry>
+       </row>
       </tbody>
      </tgroup>
    </table>
@@ -10760,6 +10778,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_indent(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_indent('[{"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 644ea6d..4ba4951 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -77,6 +77,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
@@ -414,12 +416,25 @@ 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);
+}
+
+char *
+JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len)
+{
+	return JsonbToCStringWorker(out, in, estimated_len, true);
+}
+
+static char *
+JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent)
+{
 	bool		first = true;
 	JsonbIterator *it;
 	int			type = 0;
 	JsonbValue	v;
 	int			level = 0;
 	bool		redo_switch = false;
+	int			ispaces = indent ? 1 : 2;
 
 	if (out == NULL)
 		out = makeStringInfo();
@@ -436,26 +451,34 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 		{
 			case WJB_BEGIN_ARRAY:
 				if (!first)
-					appendBinaryStringInfo(out, ", ", 2);
+					appendBinaryStringInfo(out, ", ", ispaces);
 				first = true;
 
 				if (!v.val.array.rawScalar)
-					appendStringInfoChar(out, '[');
+				{
+					add_indent(out, indent, level);
+					appendStringInfoCharMacro(out, '[');
+				}
 				level++;
+				add_indent(out, indent, level);
 				break;
 			case WJB_BEGIN_OBJECT:
 				if (!first)
-					appendBinaryStringInfo(out, ", ", 2);
+					appendBinaryStringInfo(out, ", ", ispaces);
 				first = true;
+
+				add_indent(out, indent, level);
 				appendStringInfoCharMacro(out, '{');
 
 				level++;
 				break;
 			case WJB_KEY:
 				if (!first)
-					appendBinaryStringInfo(out, ", ", 2);
+					appendBinaryStringInfo(out, ", ", ispaces);
 				first = true;
 
+				add_indent(out, indent, level);
+
 				/* json rules guarantee this is a string */
 				jsonb_put_escaped_value(out, &v);
 				appendBinaryStringInfo(out, ": ", 2);
@@ -480,20 +503,24 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 				break;
 			case WJB_ELEM:
 				if (!first)
-					appendBinaryStringInfo(out, ", ", 2);
-				else
-					first = false;
+				{
+					appendBinaryStringInfo(out, ", ", ispaces);
+					add_indent(out, indent, level);
+				}
+				first = false;
 
 				jsonb_put_escaped_value(out, &v);
 				break;
 			case WJB_END_ARRAY:
 				level--;
+				add_indent(out, indent, level);
 				if (!v.val.array.rawScalar)
-					appendStringInfoChar(out, ']');
+					appendStringInfoCharMacro(out, ']');
 				first = false;
 				break;
 			case WJB_END_OBJECT:
 				level--;
+				add_indent(out, indent, level);
 				appendStringInfoCharMacro(out, '}');
 				first = false;
 				break;
@@ -508,6 +535,22 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 }
 
 
+
+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 3688163..0950bcf 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -124,6 +124,16 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container,
 							   char *key,
 							   uint32 keylen);
 
+/* functions supporting jsonb_delete, jsonb_replace and jsonb_concat */
+static bool h_atoi(char *c, int l, int *acc);
+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 addJsonbToParseState(JsonbParseState **jbps, Jsonb * jb);
+
 /* state for json_object_keys */
 typedef struct OkeysState
 {
@@ -3195,3 +3205,667 @@ jsonb_strip_nulls(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(JsonbValueToJsonb(res));
 }
+
+
+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);
+		}
+	}
+
+}
+
+Datum
+jsonb_indent(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));
+}
+
+
+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);
+}
+
+
+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))
+		{
+			if (r == WJB_KEY)
+			{
+				/* skip corresponding value */
+				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);
+}
+
+
+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);
+}
+
+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;
+
+	/* XXX : why do we need this assertion? The functions is declared to take text[] */
+	Assert(ARR_ELEMTYPE(path) == TEXTOID);
+
+	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);
+}
+
+
+/*
+ * 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
+	{
+		elog(ERROR, "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);
+
+	if (r == WJB_BEGIN_ARRAY)
+	{
+		int			idx,
+					i;
+		uint32		n = v.val.array.nElems;
+
+		idx = n;
+		if (level >= path_len || path_nulls[level] ||
+			h_atoi(VARDATA_ANY(path_elems[level]),
+				   VARSIZE_ANY_EXHDR(path_elems[level]), &idx) == false)
+		{
+			idx = n;
+		}
+		else if (idx < 0)
+		{
+			if (-idx > n)
+				idx = n;
+			else
+				idx = n + idx;
+		}
+
+		if (idx > n)
+			idx = n;
+
+		(void) pushJsonbValue(st, r, NULL);
+
+		for (i = 0; i < n; i++)
+		{
+			if (i == idx && level < path_len)
+			{
+				if (level == path_len - 1)
+				{
+					r = JsonbIteratorNext(it, &v, true);		/* skip */
+					addJsonbToParseState(st, newval);
+				}
+				else
+				{
+					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);
+					}
+				}
+
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, false);
+		Assert(r == WJB_END_ARRAY);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_BEGIN_OBJECT)
+	{
+		int			i;
+		uint32		n = v.val.object.nPairs;
+		JsonbValue	k;
+		bool		done = false;
+
+		(void) pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL);
+
+		if (level >= path_len || path_nulls[level])
+			done = true;
+
+		for (i = 0; i < n; i++)
+		{
+			r = JsonbIteratorNext(it, &k, true);
+			Assert(r == WJB_KEY);
+			(void) pushJsonbValue(st, r, &k);
+
+			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 */
+					addJsonbToParseState(st, newval);
+				}
+				else
+				{
+					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);
+					}
+				}
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, true);
+		Assert(r == WJB_END_OBJECT);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_ELEM || r == WJB_VALUE)
+	{
+		res = pushJsonbValue(st, r, &v);
+	}
+	else
+	{
+		elog(PANIC, "impossible state");
+	}
+
+	return res;
+}
+
+/*
+ * get the integer in the argument, if any,
+ * returning a success flag.
+ */
+static bool
+h_atoi(char *c, int l, int *acc)
+{
+	bool		negative = false;
+	char	   *p = c;
+
+	*acc = 0;
+
+	while (isspace(*p) && p - c < l)
+		p++;
+
+	if (p - c >= l)
+		return false;
+
+	if (*p == '-')
+	{
+		negative = true;
+		p++;
+	}
+	else if (*p == '+')
+	{
+		p++;
+	}
+
+	if (p - c >= l)
+		return false;
+
+
+	while (p - c < l)
+	{
+		if (!isdigit(*p))
+			return false;
+
+		*acc *= 10;
+		*acc += (*p - '0');
+		p++;
+	}
+
+	if (negative)
+		*acc = -*acc;
+
+	return true;
+}
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index af991d3..ecb0723 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1808,6 +1808,12 @@ 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 = 3277 (  "||"	   PGNSP PGUID b f f 3802 3802 3802 0 0 jsonb_concat - - ));
+DESCR("concatenate");
+DATA(insert OID = 3278 (  "-"	   PGNSP PGUID b f f 3802 25 3802 0 0 3388 - - ));
+DESCR("delete");
+DATA(insert OID = 3279 (  "-"	   PGNSP PGUID b f f 3802 23 3802 0 0 3389 - - ));
+DESCR("delete");
 
 /*
  * function prototypes
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 9edfdb8..654d018 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4760,7 +4760,13 @@ 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_ gin_triconsistent_jsonb_path _null_ _null_ _null_ ));
 DESCR("GIN support");
-
+DATA(insert OID = 3387 (  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_ jsonb_concat _null_ _null_ _null_ ));
+DATA(insert OID = 3388 (  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_ jsonb_delete _null_ _null_ _null_ ));
+DATA(insert OID = 3389 (  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_ jsonb_delete_idx _null_ _null_ _null_ ));
+DATA(insert OID = 3390 (  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_ jsonb_replace _null_ _null_ _null_ ));
+DESCR("Replace part of a jsonb");
+DATA(insert OID = 3391 (  jsonb_indent	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_indent _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_ txid_snapshot_in _null_ _null_ _null_ ));
 DESCR("I/O");
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 887eb9b..656668b 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -394,6 +394,19 @@ 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_indent(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);
+
+/* 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 +426,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 6c6ed95..b98acee 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2252,7 +2252,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  
 ----
  {}
@@ -2729,3 +2729,355 @@ 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_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)
+
diff --git a/src/test/regress/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out
index f30148d..535662b 100644
--- a/src/test/regress/expected/jsonb_1.out
+++ b/src/test/regress/expected/jsonb_1.out
@@ -2252,7 +2252,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  
 ----
  {}
@@ -2729,3 +2729,355 @@ 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_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)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 53cc239..3cdc5a6 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -551,7 +551,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;
@@ -678,3 +678,76 @@ 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_indent('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}');
+
+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"}');
#2Sehrope Sarkuni
sehrope@jackdb.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

For jsonb_indent, how about having it match up closer to the
JavaScript JSON.stringify(value, replacer, space)[1]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify? That way a user
can specify the indentation level and optionally filter the fields
they'd like to output.

In JS, the "replacer" parameter can be either a JS function or an
array of property names. I don't think the former is really possible
(in a SQL callable function) but the latter would be a text[]. The
"space" parameter can be either a string (used directly) or a number
(corresponding number of spaces).

The PG function signatures would be something like:

CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, replacer text[],
space text)
CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, replacer text[],
space int)

For convenience we could also include overloads with replacer removed
(since most people probably want the entire object):

CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, space text)
CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, space int)

Having json_stringify versions of these would be useful as well.

Regards,
-- Sehrope Sarkuni

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Andrew Dunstan
andrew@dunslane.net
In reply to: Sehrope Sarkuni (#2)
Re: mogrify and indent features for jsonb

On 02/15/2015 11:47 AM, Sehrope Sarkuni wrote:

For jsonb_indent, how about having it match up closer to the
JavaScript JSON.stringify(value, replacer, space)[1]? That way a user
can specify the indentation level and optionally filter the fields
they'd like to output.

In JS, the "replacer" parameter can be either a JS function or an
array of property names. I don't think the former is really possible
(in a SQL callable function) but the latter would be a text[]. The
"space" parameter can be either a string (used directly) or a number
(corresponding number of spaces).

The PG function signatures would be something like:

CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, replacer text[],
space text)
CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, replacer text[],
space int)

For convenience we could also include overloads with replacer removed
(since most people probably want the entire object):

CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, space text)
CREATE OR REPLACE FUNCTION jsonb_stringify(obj jsonb, space int)

Having json_stringify versions of these would be useful as well.

I think if you want these things, especially the filtering, you should
probably load PLV8.

We could probably do the rest, but I'm not sure it's worth doing given
that PLV8 is available for all of it.

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#1)
1 attachment(s)
Re: mogrify and indent features for jsonb

On 02/14/2015 10:06 PM, Andrew Dunstan wrote:

Attached is a patch to provide a number of very useful facilities to
jsonb that people have asked for. These are based on work by Dmitry
Dolgov in his jsonbx extension, but I take responsibility for any bugs.

The facilities are:

new operations:

concatenation: jsonb || jsonb -> jsonb
deletion: jsonb - text -> jsonb
deletion: jsonb - int -> text

new functions:

produce indented text: jsonb_indent(jsonb) -> text
change an element at a path: jsonb_replace(jsonb, text[], jsonb) ->
jsonb.

It would be relatively trivial to add:

delete an element at a path: jsonb_delete(jsonb, text[]) -> json

and I think we should do that for the sake of completeness.

The docs might need a little extra work, and the indent code
definitely needs work, which I hope to complete in the next day or
two, but I wanted to put a stake in the ground.

In this version the indent code now works correctly, and there is an
additional delete operator:

jsonb - text[] -> jsonb

Which deletes data at the designated path.

cheers

andrew

Attachments:

jsonbxcore2.patchtext/x-patch; name=jsonbxcore2.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d57243a..9936bff 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10256,6 +10256,30 @@ table2-mapping
         <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry>
         <entry><literal>'["a", "b"]'::jsonb ?&amp; array['a', 'b']</literal></entry>
        </row>
+       <row>
+        <entry><literal>||</literal></entry>
+        <entry><type>jsonb</type></entry>
+        <entry>Concatentate these two values to make a new 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 this 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 this 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 this path</entry>
+        <entry><literal>'["a", {"b":1}]'::jsonb - '{1,b}'::text[] </literal></entry>
+       </row>
       </tbody>
      </tgroup>
    </table>
@@ -10760,6 +10784,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_indent(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_indent('[{"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 644ea6d..133b9ba 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -77,6 +77,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
@@ -414,12 +416,40 @@ 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;
 	int			type = 0;
 	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;
 
 	if (out == NULL)
 		out = makeStringInfo();
@@ -436,26 +466,37 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 		{
 			case WJB_BEGIN_ARRAY:
 				if (!first)
-					appendBinaryStringInfo(out, ", ", 2);
+					appendBinaryStringInfo(out, ", ", ispaces);
 				first = true;
 
 				if (!v.val.array.rawScalar)
-					appendStringInfoChar(out, '[');
+				{
+					add_indent(out, use_indent, level);
+					appendStringInfoCharMacro(out, '[');
+				}
+				else
+				{
+					raw_scalar = true;
+				}
 				level++;
 				break;
 			case WJB_BEGIN_OBJECT:
 				if (!first)
-					appendBinaryStringInfo(out, ", ", 2);
+					appendBinaryStringInfo(out, ", ", ispaces);
 				first = true;
+
+				add_indent(out, use_indent, level);
 				appendStringInfoCharMacro(out, '{');
 
 				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);
@@ -480,26 +521,32 @@ 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 flag of jsonb iterator");
 		}
+		use_indent = indent;
 	}
 
 	Assert(level == 0);
@@ -508,6 +555,22 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 }
 
 
+
+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 3688163..089b9ca 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -124,6 +124,16 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container,
 							   char *key,
 							   uint32 keylen);
 
+/* functions supporting jsonb_delete, jsonb_replace and jsonb_concat */
+static bool h_atoi(char *c, int l, int *acc);
+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 addJsonbToParseState(JsonbParseState **jbps, Jsonb * jb);
+
 /* state for json_object_keys */
 typedef struct OkeysState
 {
@@ -3195,3 +3205,761 @@ 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_indent (jsonb)
+ *
+ * Pretty-printed text for the jsonb
+ */
+Datum
+jsonb_indent(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))
+		{
+			if (r == WJB_KEY)
+			{
+				/* skip corresponding value */
+				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;
+
+	/* XXX : why do we need this assertion? The functions is declared to take text[] */
+	Assert(ARR_ELEMTYPE(path) == TEXTOID);
+
+	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;
+
+	/* XXX : why do we need this assertion? The functions is declared to take text[] */
+	Assert(ARR_ELEMTYPE(path) == TEXTOID);
+
+	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
+	{
+		elog(ERROR, "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);
+
+	if (r == WJB_BEGIN_ARRAY)
+	{
+		int			idx,
+					i;
+		uint32		n = v.val.array.nElems;
+
+		idx = n;
+		if (level >= path_len || path_nulls[level] ||
+			h_atoi(VARDATA_ANY(path_elems[level]),
+				   VARSIZE_ANY_EXHDR(path_elems[level]), &idx) == false)
+		{
+			idx = n;
+		}
+		else if (idx < 0)
+		{
+			if (-idx > n)
+				idx = n;
+			else
+				idx = n + idx;
+		}
+
+		if (idx > n)
+			idx = n;
+
+		(void) pushJsonbValue(st, r, NULL);
+
+		for (i = 0; i < n; i++)
+		{
+			if (i == idx && level < path_len)
+			{
+				if (level == path_len - 1)
+				{
+					r = JsonbIteratorNext(it, &v, true);		/* skip */
+					if (newval != NULL)
+						addJsonbToParseState(st, newval);
+				}
+				else
+				{
+					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);
+					}
+				}
+
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, false);
+		Assert(r == WJB_END_ARRAY);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_BEGIN_OBJECT)
+	{
+		int			i;
+		uint32		n = v.val.object.nPairs;
+		JsonbValue	k;
+		bool		done = false;
+
+		(void) pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL);
+
+		if (level >= path_len || path_nulls[level])
+			done = true;
+
+		for (i = 0; i < n; i++)
+		{
+			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);
+					}
+				}
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, true);
+		Assert(r == WJB_END_OBJECT);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_ELEM || r == WJB_VALUE)
+	{
+		res = pushJsonbValue(st, r, &v);
+	}
+	else
+	{
+		elog(PANIC, "impossible state");
+	}
+
+	return res;
+}
+
+/*
+ * get the integer in the argument, if any,
+ * returning a success flag.
+ */
+static bool
+h_atoi(char *c, int l, int *acc)
+{
+	bool		negative = false;
+	char	   *p = c;
+
+	*acc = 0;
+
+	while (isspace(*p) && p - c < l)
+		p++;
+
+	if (p - c >= l)
+		return false;
+
+	if (*p == '-')
+	{
+		negative = true;
+		p++;
+	}
+	else if (*p == '+')
+	{
+		p++;
+	}
+
+	if (p - c >= l)
+		return false;
+
+
+	while (p - c < l)
+	{
+		if (!isdigit(*p))
+			return false;
+
+		*acc *= 10;
+		*acc += (*p - '0');
+		p++;
+	}
+
+	if (negative)
+		*acc = -*acc;
+
+	return true;
+}
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index af991d3..57e521b 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1808,6 +1808,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 = 3277 (  "||"	   PGNSP PGUID b f f 3802 3802 3802 0 0 jsonb_concat - - ));
+DESCR("concatenate");
+DATA(insert OID = 3278 (  "-"	   PGNSP PGUID b f f 3802 25 3802 0 0 3388 - - ));
+DESCR("delete");
+DATA(insert OID = 3279 (  "-"	   PGNSP PGUID b f f 3802 23 3802 0 0 3389 - - ));
+DESCR("delete");
+DATA(insert OID = 3280 (  "-"	   PGNSP PGUID b f f 3802 1009 3802 0 0 3390 - - ));
+DESCR("delete");
 
 /*
  * function prototypes
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 9edfdb8..eaa8362 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4760,7 +4760,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_ gin_triconsistent_jsonb_path _null_ _null_ _null_ ));
 DESCR("GIN support");
-
+DATA(insert OID = 3387 (  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_ jsonb_concat _null_ _null_ _null_ ));
+DATA(insert OID = 3388 (  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_ jsonb_delete _null_ _null_ _null_ ));
+DATA(insert OID = 3389 (  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_ jsonb_delete_idx _null_ _null_ _null_ ));
+DATA(insert OID = 3390 (  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_ jsonb_delete_path _null_ _null_ _null_ ));
+DATA(insert OID = 3391 (  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_ jsonb_replace _null_ _null_ _null_ ));
+DESCR("Replace part of a jsonb");
+DATA(insert OID = 3392 (  jsonb_indent	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_indent _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_ txid_snapshot_in _null_ _null_ _null_ ));
 DESCR("I/O");
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 887eb9b..656668b 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -394,6 +394,19 @@ 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_indent(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);
+
+/* 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 +426,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 6c6ed95..e8bd5d2 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2252,7 +2252,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  
 ----
  {}
@@ -2729,3 +2729,390 @@ 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_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 f30148d..3eba934 100644
--- a/src/test/regress/expected/jsonb_1.out
+++ b/src/test/regress/expected/jsonb_1.out
@@ -2252,7 +2252,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  
 ----
  {}
@@ -2729,3 +2729,390 @@ 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_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 53cc239..d642e09 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -551,7 +551,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;
@@ -678,3 +678,84 @@ 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_indent('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}');
+
+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[];
#5Petr Jelinek
petr@2ndquadrant.com
In reply to: Andrew Dunstan (#4)
Re: mogrify and indent features for jsonb

Hi,

I looked at the patch and have several comments.

First let me say that modifying the individual paths of the json is the
feature I miss the most in the current implementation so I am happy that
this patch was submitted.

I would prefer slightly the patch split into two parts, one for the
indent printing and one with the manipulation functions, but it's not
too big patch so it's not too bad as it is.

There is one compiler warning that I see:
jsonfuncs.c:3533:1: warning: no previous prototype for
‘jsonb_delete_path’ [-Wmissing-prototypes]
jsonb_delete_path(PG_FUNCTION_ARGS)

I think it would be better if the ident printing didn't put the start of
array ([) and start of dictionary ({) on separate line since most
"pretty" printing implementations outside of Postgres (like the
JSON.stringify or python's json module) don't do that. This is purely
about consistency with the rest of the world.

The json_ident might be better named as json_pretty perhaps?

I don't really understand the point of h_atoi() function. What's wrong
with using strtol like pg_atoi does? Also there is no integer overflow
check anywhere in that function.

There is tons of end of line whitespace mess in jsonb_indent docs.

Otherwise everything I tried so far works as expected. The code looks ok
as well except maybe the replacePath could use couple of comments (for
example the line which uses the h_atoi) to make it easier to follow.

About the:

+	/* XXX : why do we need this assertion? The functions is declared to take text[] */
+	Assert(ARR_ELEMTYPE(path) == TEXTOID);

I wonder about this also, some functions do that, some don't, I am not
really sure what is the rule there myself.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Petr Jelinek (#5)
Re: mogrify and indent features for jsonb

Hi, Petr, thanks for the review.

I think it would be better if the ident printing didn't put the start

of array ([) and start of dictionary ({) on separate line
Did you mean this?

[
{
"a": 1,
"b": 2
}
]

I tried to verify this in several ways (http://jsonprettyprint.com/,
"JSON.stringify", "json.too" from python), and I always get this result
(new line after ([) and ({) ).

I don't really understand the point of h_atoi() function.

Initially, this function was to combine the convertion logic and specific
verifications. But I agree, "strtol" is more correct way, I should improve
this.

The code looks ok as well except maybe the replacePath could use couple

of comments
I already added several commentaries (and looks like I should add even more
in the nearest future) for this function in the jsonbx extension, and I
think we can update this patch one more time with that improvement.

About the Assert(ARR_ELEMTYPE(path) == TEXTOID);

I based my work on the hstore extension, which contains such kind of
assertions. But I suppose, it's not required anymore, so I removed this
from the extension. And, I think, we can also remove this from patch.

On 18 February 2015 at 08:32, Petr Jelinek <petr@2ndquadrant.com> wrote:

Show quoted text

Hi,

I looked at the patch and have several comments.

First let me say that modifying the individual paths of the json is the
feature I miss the most in the current implementation so I am happy that
this patch was submitted.

I would prefer slightly the patch split into two parts, one for the indent
printing and one with the manipulation functions, but it's not too big
patch so it's not too bad as it is.

There is one compiler warning that I see:
jsonfuncs.c:3533:1: warning: no previous prototype for ‘jsonb_delete_path’
[-Wmissing-prototypes]
jsonb_delete_path(PG_FUNCTION_ARGS)

I think it would be better if the ident printing didn't put the start of
array ([) and start of dictionary ({) on separate line since most "pretty"
printing implementations outside of Postgres (like the JSON.stringify or
python's json module) don't do that. This is purely about consistency with
the rest of the world.

The json_ident might be better named as json_pretty perhaps?

I don't really understand the point of h_atoi() function. What's wrong
with using strtol like pg_atoi does? Also there is no integer overflow
check anywhere in that function.

There is tons of end of line whitespace mess in jsonb_indent docs.

Otherwise everything I tried so far works as expected. The code looks ok
as well except maybe the replacePath could use couple of comments (for
example the line which uses the h_atoi) to make it easier to follow.

About the:

+       /* XXX : why do we need this assertion? The functions is declared
to take text[] */
+       Assert(ARR_ELEMTYPE(path) == TEXTOID);

I wonder about this also, some functions do that, some don't, I am not
really sure what is the rule there myself.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Thom Brown
thom@linux.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

On 15 February 2015 at 03:06, Andrew Dunstan <andrew@dunslane.net> wrote:

Attached is a patch to provide a number of very useful facilities to jsonb
that people have asked for. These are based on work by Dmitry Dolgov in his
jsonbx extension, but I take responsibility for any bugs.

The facilities are:

new operations:

concatenation: jsonb || jsonb -> jsonb
deletion: jsonb - text -> jsonb
deletion: jsonb - int -> text

new functions:

produce indented text: jsonb_indent(jsonb) -> text
change an element at a path: jsonb_replace(jsonb, text[], jsonb) -> jsonb.

It would be relatively trivial to add:

delete an element at a path: jsonb_delete(jsonb, text[]) -> json

Would this support deleting "type" and the value 'dd' from the following?:

{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}

and I think we should do that for the sake of completeness.

The docs might need a little extra work, and the indent code definitely
needs work, which I hope to complete in the next day or two, but I wanted
to put a stake in the ground.

This is high on my wanted list, so thanks for working on this.

Seems to work well for me with a few tests.

Is there a way to take the json:

'{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'

and add "ee" to "d" without replacing it? I can think of ways of currently
doing it, but it's very convoluted just for pushing a value to an array.

Also, are there any plans to support the following?:

jsonb - text[] # Provide list of keys to delete in array
jsonb - jsonb # Deduplicate key:value pairs
jsonb && jsonb # Return overlapping jsonb (opposite of jsonb - jsonb)

Thanks

Thom

#8Josh Berkus
josh@agliodbs.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

Is there a way to take the json:

'{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'

and add "ee" to "d" without replacing it? I can think of ways of
currently doing it, but it's very convoluted just for pushing a value to
an array.

Can you think of a reasonable syntax for doing that via operators? I
can imagine that as a json_path function, i.e.:

jsonb_add_to_path(jsonb, text[], jsonb)

or where the end of the path is an array:

jsonb_add_to_path(jsonb, text[], text|int|float|bool)

But I simply can't imagine an operator syntax which would make it clear
what the user intended.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Thom Brown
thom@linux.com
In reply to: Josh Berkus (#8)
Re: mogrify and indent features for jsonb

On 24 February 2015 at 19:16, Josh Berkus <josh@agliodbs.com> wrote:

Is there a way to take the json:

'{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'

and add "ee" to "d" without replacing it? I can think of ways of
currently doing it, but it's very convoluted just for pushing a value to
an array.

Can you think of a reasonable syntax for doing that via operators? I
can imagine that as a json_path function, i.e.:

jsonb_add_to_path(jsonb, text[], jsonb)

or where the end of the path is an array:

jsonb_add_to_path(jsonb, text[], text|int|float|bool)

But I simply can't imagine an operator syntax which would make it clear
what the user intended.

No, there probably isn't a sane operator syntax for such an operation. A
function would be nice. I'd just want to avoid hacking away at arrays by
exploding them, adding a value then re-arraying them and replacing the
value.

--
Thom

#10Josh Berkus
josh@agliodbs.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

On 02/25/2015 03:13 AM, Thom Brown wrote:

Can you think of a reasonable syntax for doing that via operators? I
can imagine that as a json_path function, i.e.:

jsonb_add_to_path(jsonb, text[], jsonb)

or where the end of the path is an array:

jsonb_add_to_path(jsonb, text[], text|int|float|bool)

But I simply can't imagine an operator syntax which would make it clear
what the user intended.

No, there probably isn't a sane operator syntax for such an operation.
A function would be nice. I'd just want to avoid hacking away at arrays
by exploding them, adding a value then re-arraying them and replacing
the value.

Well, anyway, that doesn't seem like a reason to block the patch.
Rather, it's a reason to create another one for 9.6 ...

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Josh Berkus (#10)
Re: mogrify and indent features for jsonb

Hi, Thom.

Would this support deleting "type" and the value 'dd'

With this patch you can delete them one by one:

select '{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'::jsonb - '{c, type}'::text[] - '{d, -1}'::text[];
?column?
-------------------------------------------------------------------
{"a": 1, "b": 2, "c": {"stuff": "test"}, "d": ["aa", "bb", "cc"]}
(1 row)

Is there a way to take the json:
'{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":

["aa","bb","cc","dd"]}'

and add "ee" to "d" without replacing it?

No, looks like there is no way to add a new element to array with help of
this patch. I suppose this feature can be implemented easy enough inside
the "jsonb_concat" function:

select '{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'::jsonb || '{"d": ["ee"]}'::jsonb

but I'm not sure, that it will be the best way.

On 26 February 2015 at 01:13, Josh Berkus <josh@agliodbs.com> wrote:

Show quoted text

On 02/25/2015 03:13 AM, Thom Brown wrote:

Can you think of a reasonable syntax for doing that via operators? I
can imagine that as a json_path function, i.e.:

jsonb_add_to_path(jsonb, text[], jsonb)

or where the end of the path is an array:

jsonb_add_to_path(jsonb, text[], text|int|float|bool)

But I simply can't imagine an operator syntax which would make it

clear

what the user intended.

No, there probably isn't a sane operator syntax for such an operation.
A function would be nice. I'd just want to avoid hacking away at arrays
by exploding them, adding a value then re-arraying them and replacing
the value.

Well, anyway, that doesn't seem like a reason to block the patch.
Rather, it's a reason to create another one for 9.6 ...

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Thom Brown
thom@linux.com
In reply to: Dmitry Dolgov (#11)
Re: mogrify and indent features for jsonb

On 26 February 2015 at 15:09, Dmitry Dolgov <9erthalion6@gmail.com> wrote:

Hi, Thom.

Would this support deleting "type" and the value 'dd'

With this patch you can delete them one by one:

select '{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'::jsonb - '{c, type}'::text[] - '{d, -1}'::text[];
?column?
-------------------------------------------------------------------
{"a": 1, "b": 2, "c": {"stuff": "test"}, "d": ["aa", "bb", "cc"]}
(1 row)

Doesn't work for me:

# select '{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'::jsonb - '{c, type}'::text[] - '{d, -1}'::text[];
ERROR: operator does not exist: jsonb - text[]
LINE 1: ...ff": "test"}, "d": ["aa","bb","cc","dd"]}'::jsonb - '{c, typ...
^
HINT: No operator matches the given name and argument type(s). You might
need to add explicit type casts.

Is there a way to take the json:
'{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":

["aa","bb","cc","dd"]}'

and add "ee" to "d" without replacing it?

No, looks like there is no way to add a new element to array with help of
this patch. I suppose this feature can be implemented easy enough inside
the "jsonb_concat" function:

select '{"a": 1, "b": 2, "c": {"type": "json", "stuff": "test"}, "d":
["aa","bb","cc","dd"]}'::jsonb || '{"d": ["ee"]}'::jsonb

but I'm not sure, that it will be the best way.

Yeah, I think that may be problematic. I agree with Josh that there's
probably no sane mix of operators for this, as I would expect your example
to replace "d": ["aa","bb","cc","dd"] with "d": ["ee"] rather than append
to it. Hmm... unless we used a + operator, but then I'm not sure what
should happen in the instance of '{"d": ["aa"]}' + '{"d": "bb"}'.

--
Thom

#13Josh Berkus
josh@agliodbs.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

On 02/26/2015 07:25 AM, Thom Brown wrote:

Yeah, I think that may be problematic. I agree with Josh that there's
probably no sane mix of operators for this, as I would expect your
example to replace "d": ["aa","bb","cc","dd"] with "d": ["ee"] rather
than append to it. Hmm... unless we used a + operator, but then I'm not
sure what should happen in the instance of '{"d": ["aa"]}' + '{"d": "bb"}'.

Yeah, that's what I would expect too. In fact, I could would count on
replacement.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Petr Jelinek
petr@2ndquadrant.com
In reply to: Dmitry Dolgov (#6)
Re: mogrify and indent features for jsonb

On 23/02/15 18:15, Dmitry Dolgov wrote:

Hi, Petr, thanks for the review.

I think it would be better if the ident printing didn't put the

start of array ([) and start of dictionary ({) on separate line
Did you mean this?

[
{
"a": 1,
"b": 2
}
]

I tried to verify this in several ways (http://jsonprettyprint.com/,
"JSON.stringify", "json.too" from python), and I always get this result
(new line after ([) and ({) ).

No, I mean new lines before the ([) and ({) - try pretty printing
something like '{"a":["b", "c"], "d": {"e":"f"}}' using that tool you
pasted and see what your patch outputs to see what I mean.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Andrew Dunstan
andrew@dunslane.net
In reply to: Petr Jelinek (#14)
1 attachment(s)
Re: mogrify and indent features for jsonb

On 03/01/2015 05:03 AM, Petr Jelinek wrote:

On 23/02/15 18:15, Dmitry Dolgov wrote:

Hi, Petr, thanks for the review.

I think it would be better if the ident printing didn't put the

start of array ([) and start of dictionary ({) on separate line
Did you mean this?

[
{
"a": 1,
"b": 2
}
]

I tried to verify this in several ways (http://jsonprettyprint.com/,
"JSON.stringify", "json.too" from python), and I always get this result
(new line after ([) and ({) ).

No, I mean new lines before the ([) and ({) - try pretty printing
something like '{"a":["b", "c"], "d": {"e":"f"}}' using that tool you
pasted and see what your patch outputs to see what I mean.

Please try this patch and see if it's doing what you want.

cheers

andrew

Attachments:

jsonbxcore3.patchtext/x-patch; name=jsonbxcore3.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index da2ed67..01e53e8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10256,6 +10256,30 @@ table2-mapping
         <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry>
         <entry><literal>'["a", "b"]'::jsonb ?&amp; array['a', 'b']</literal></entry>
        </row>
+       <row>
+        <entry><literal>||</literal></entry>
+        <entry><type>jsonb</type></entry>
+        <entry>Concatentate these two values to make a new 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 this 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 this 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 this path</entry>
+        <entry><literal>'["a", {"b":1}]'::jsonb - '{1,b}'::text[] </literal></entry>
+       </row>
       </tbody>
      </tgroup>
    </table>
@@ -10766,6 +10790,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_indent(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_indent('[{"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 5833401..6a92305 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
@@ -422,12 +424,41 @@ 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;
 	JsonbIteratorToken type = WJB_DONE;
 	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 +475,37 @@ 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 +530,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);
@@ -516,6 +565,22 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 }
 
 
+
+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 a8cdeaa..1b67732 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -124,6 +124,16 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container,
 							   char *key,
 							   uint32 keylen);
 
+/* functions supporting jsonb_delete, jsonb_replace and jsonb_concat */
+static bool h_atoi(char *c, int l, int *acc);
+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 addJsonbToParseState(JsonbParseState **jbps, Jsonb * jb);
+
 /* state for json_object_keys */
 typedef struct OkeysState
 {
@@ -3195,3 +3205,761 @@ 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_indent (jsonb)
+ *
+ * Pretty-printed text for the jsonb
+ */
+Datum
+jsonb_indent(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))
+		{
+			if (r == WJB_KEY)
+			{
+				/* skip corresponding value */
+				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;
+
+	/* XXX : why do we need this assertion? The functions is declared to take text[] */
+	Assert(ARR_ELEMTYPE(path) == TEXTOID);
+
+	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;
+
+	/* XXX : why do we need this assertion? The functions is declared to take text[] */
+	Assert(ARR_ELEMTYPE(path) == TEXTOID);
+
+	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
+	{
+		elog(ERROR, "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);
+
+	if (r == WJB_BEGIN_ARRAY)
+	{
+		int			idx,
+					i;
+		uint32		n = v.val.array.nElems;
+
+		idx = n;
+		if (level >= path_len || path_nulls[level] ||
+			h_atoi(VARDATA_ANY(path_elems[level]),
+				   VARSIZE_ANY_EXHDR(path_elems[level]), &idx) == false)
+		{
+			idx = n;
+		}
+		else if (idx < 0)
+		{
+			if (-idx > n)
+				idx = n;
+			else
+				idx = n + idx;
+		}
+
+		if (idx > n)
+			idx = n;
+
+		(void) pushJsonbValue(st, r, NULL);
+
+		for (i = 0; i < n; i++)
+		{
+			if (i == idx && level < path_len)
+			{
+				if (level == path_len - 1)
+				{
+					r = JsonbIteratorNext(it, &v, true);		/* skip */
+					if (newval != NULL)
+						addJsonbToParseState(st, newval);
+				}
+				else
+				{
+					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);
+					}
+				}
+
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, false);
+		Assert(r == WJB_END_ARRAY);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_BEGIN_OBJECT)
+	{
+		int			i;
+		uint32		n = v.val.object.nPairs;
+		JsonbValue	k;
+		bool		done = false;
+
+		(void) pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL);
+
+		if (level >= path_len || path_nulls[level])
+			done = true;
+
+		for (i = 0; i < n; i++)
+		{
+			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);
+					}
+				}
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, true);
+		Assert(r == WJB_END_OBJECT);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_ELEM || r == WJB_VALUE)
+	{
+		res = pushJsonbValue(st, r, &v);
+	}
+	else
+	{
+		elog(PANIC, "impossible state");
+	}
+
+	return res;
+}
+
+/*
+ * get the integer in the argument, if any,
+ * returning a success flag.
+ */
+static bool
+h_atoi(char *c, int l, int *acc)
+{
+	bool		negative = false;
+	char	   *p = c;
+
+	*acc = 0;
+
+	while (isspace(*p) && p - c < l)
+		p++;
+
+	if (p - c >= l)
+		return false;
+
+	if (*p == '-')
+	{
+		negative = true;
+		p++;
+	}
+	else if (*p == '+')
+	{
+		p++;
+	}
+
+	if (p - c >= l)
+		return false;
+
+
+	while (p - c < l)
+	{
+		if (!isdigit(*p))
+			return false;
+
+		*acc *= 10;
+		*acc += (*p - '0');
+		p++;
+	}
+
+	if (negative)
+		*acc = -*acc;
+
+	return true;
+}
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index af991d3..57e521b 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1808,6 +1808,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 = 3277 (  "||"	   PGNSP PGUID b f f 3802 3802 3802 0 0 jsonb_concat - - ));
+DESCR("concatenate");
+DATA(insert OID = 3278 (  "-"	   PGNSP PGUID b f f 3802 25 3802 0 0 3388 - - ));
+DESCR("delete");
+DATA(insert OID = 3279 (  "-"	   PGNSP PGUID b f f 3802 23 3802 0 0 3389 - - ));
+DESCR("delete");
+DATA(insert OID = 3280 (  "-"	   PGNSP PGUID b f f 3802 1009 3802 0 0 3390 - - ));
+DESCR("delete");
 
 /*
  * function prototypes
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 4268b99..b3b1bd3 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4762,7 +4762,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_ gin_triconsistent_jsonb_path _null_ _null_ _null_ ));
 DESCR("GIN support");
-
+DATA(insert OID = 3387 (  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_ jsonb_concat _null_ _null_ _null_ ));
+DATA(insert OID = 3388 (  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_ jsonb_delete _null_ _null_ _null_ ));
+DATA(insert OID = 3389 (  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_ jsonb_delete_idx _null_ _null_ _null_ ));
+DATA(insert OID = 3390 (  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_ jsonb_delete_path _null_ _null_ _null_ ));
+DATA(insert OID = 3391 (  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_ jsonb_replace _null_ _null_ _null_ ));
+DESCR("Replace part of a jsonb");
+DATA(insert OID = 3392 (  jsonb_indent	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_indent _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_ txid_snapshot_in _null_ _null_ _null_ ));
 DESCR("I/O");
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 9d1770e..1b9880c 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_indent(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 0d55890..a1d2b2b 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_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/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out
index 694b6ea..8c57594 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 676e1a7..fd3c1a3 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_indent('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}');
+select jsonb_indent('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]');
+select jsonb_indent('{"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[];
#16Ilya Ashchepkov
koctep@gmail.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

On Sat, 14 Feb 2015 22:06:07 -0500
Andrew Dunstan <andrew@dunslane.net> wrote:

Hello.

I have function with recursive merging objects:

# SELECT jsonb_deep_extend('{"a": {"b": 6}}'::jsonb, '{"a": {"c":
7}}'::jsonb) AS new_jsonb;
new_jsonb
-------------------------
{"a": {"b": 6, "c": 7}}

https://github.com/koctep/jsonb-extend

I think this function may be useful for people too.

Could you add it to your patch? I don't have enough time to prepare
patch.

Attached is a patch to provide a number of very useful facilities to
jsonb that people have asked for. These are based on work by Dmitry
Dolgov in his jsonbx extension, but I take responsibility for any
bugs.

The facilities are:

new operations:

concatenation: jsonb || jsonb -> jsonb
deletion: jsonb - text -> jsonb
deletion: jsonb - int -> text

new functions:

produce indented text: jsonb_indent(jsonb) -> text
change an element at a path: jsonb_replace(jsonb, text[], jsonb) ->
jsonb.

It would be relatively trivial to add:

delete an element at a path: jsonb_delete(jsonb, text[]) -> json

and I think we should do that for the sake of completeness.

The docs might need a little extra work, and the indent code
definitely needs work, which I hope to complete in the next day or
two, but I wanted to put a stake in the ground.

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Andrew Dunstan
andrew@dunslane.net
In reply to: Ilya Ashchepkov (#16)
Re: mogrify and indent features for jsonb

On 03/11/2015 04:05 AM, Ilya Ashchepkov wrote:

On Sat, 14 Feb 2015 22:06:07 -0500
Andrew Dunstan <andrew@dunslane.net> wrote:

Hello.

I have function with recursive merging objects:

# SELECT jsonb_deep_extend('{"a": {"b": 6}}'::jsonb, '{"a": {"c":
7}}'::jsonb) AS new_jsonb;
new_jsonb
-------------------------
{"a": {"b": 6, "c": 7}}

https://github.com/koctep/jsonb-extend

I think this function may be useful for people too.

Could you add it to your patch? I don't have enough time to prepare
patch.

It's far too late to be adding new material.

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Petr Jelinek
petr@2ndquadrant.com
In reply to: Andrew Dunstan (#15)
Re: mogrify and indent features for jsonb

On 01/03/15 16:49, Andrew Dunstan wrote:

On 03/01/2015 05:03 AM, Petr Jelinek wrote:

On 23/02/15 18:15, Dmitry Dolgov wrote:

Hi, Petr, thanks for the review.

I think it would be better if the ident printing didn't put the

start of array ([) and start of dictionary ({) on separate line
Did you mean this?

[
{
"a": 1,
"b": 2
}
]

I tried to verify this in several ways (http://jsonprettyprint.com/,
"JSON.stringify", "json.too" from python), and I always get this result
(new line after ([) and ({) ).

No, I mean new lines before the ([) and ({) - try pretty printing
something like '{"a":["b", "c"], "d": {"e":"f"}}' using that tool you
pasted and see what your patch outputs to see what I mean.

Please try this patch and see if it's doing what you want.

Yes, this produces output that looks like what javascript/python produce.

(the other stuff I commented about, mainly the h_atoi is still unfixed
though)

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Petr Jelinek (#18)
1 attachment(s)
Re: mogrify and indent features for jsonb

Sorry for late reply. Here is a slightly improved version of the patch with
the new `h_atoi` function, I hope this implementation will be more
appropriate.

On 13 March 2015 at 23:30, Petr Jelinek <petr@2ndquadrant.com> wrote:

Show quoted text

On 01/03/15 16:49, Andrew Dunstan wrote:

On 03/01/2015 05:03 AM, Petr Jelinek wrote:

On 23/02/15 18:15, Dmitry Dolgov wrote:

Hi, Petr, thanks for the review.

I think it would be better if the ident printing didn't put the

start of array ([) and start of dictionary ({) on separate line
Did you mean this?

[
{
"a": 1,
"b": 2
}
]

I tried to verify this in several ways (http://jsonprettyprint.com/,
"JSON.stringify", "json.too" from python), and I always get this result
(new line after ([) and ({) ).

No, I mean new lines before the ([) and ({) - try pretty printing
something like '{"a":["b", "c"], "d": {"e":"f"}}' using that tool you
pasted and see what your patch outputs to see what I mean.

Please try this patch and see if it's doing what you want.

Yes, this produces output that looks like what javascript/python produce.

(the other stuff I commented about, mainly the h_atoi is still unfixed
though)

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

jsonbxcore4.patchapplication/octet-stream; name=jsonbxcore4.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5f7bf6a..7051288 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10257,6 +10257,30 @@ table2-mapping
         <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry>
         <entry><literal>'["a", "b"]'::jsonb ?&amp; array['a', 'b']</literal></entry>
        </row>
+       <row>
+        <entry><literal>||</literal></entry>
+        <entry><type>jsonb</type></entry>
+        <entry>Concatentate these two values to make a new 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 this 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 this 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 this path</entry>
+        <entry><literal>'["a", {"b":1}]'::jsonb - '{1,b}'::text[] </literal></entry>
+       </row>
       </tbody>
      </tgroup>
    </table>
@@ -10767,6 +10791,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_indent(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_indent('[{"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 7e2c359..6d2705e 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
@@ -422,12 +424,41 @@ 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;
 	JsonbIteratorToken type = WJB_DONE;
 	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 +475,37 @@ 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 +530,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);
@@ -516,6 +565,22 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 }
 
 
+
+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 274f64c..b2cc3d3 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -124,6 +124,16 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container,
 							   char *key,
 							   uint32 keylen);
 
+/* functions supporting jsonb_delete, jsonb_replace and jsonb_concat */
+static bool h_atoi(char *c, int *idx);
+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 addJsonbToParseState(JsonbParseState **jbps, Jsonb * jb);
+
 /* state for json_object_keys */
 typedef struct OkeysState
 {
@@ -3195,3 +3205,728 @@ 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_indent (jsonb)
+ *
+ * Pretty-printed text for the jsonb
+ */
+Datum
+jsonb_indent(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))
+		{
+			if (r == WJB_KEY)
+			{
+				/* skip corresponding value */
+				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
+	{
+		elog(ERROR, "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);
+
+	if (r == WJB_BEGIN_ARRAY)
+	{
+		int			idx,
+					i;
+		uint32		n = v.val.array.nElems;
+
+		idx = n;
+		if (level >= path_len || path_nulls[level] ||
+			h_atoi(VARDATA_ANY(path_elems[level]), &idx) == false)
+		{
+			idx = n;
+		}
+		else if (idx < 0)
+		{
+			if (-idx > n)
+				idx = n;
+			else
+				idx = n + idx;
+		}
+
+		if (idx > n)
+			idx = n;
+
+		(void) pushJsonbValue(st, r, NULL);
+
+		for (i = 0; i < n; i++)
+		{
+			if (i == idx && level < path_len)
+			{
+				if (level == path_len - 1)
+				{
+					r = JsonbIteratorNext(it, &v, true);		/* skip */
+					if (newval != NULL)
+						addJsonbToParseState(st, newval);
+				}
+				else
+				{
+					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);
+					}
+				}
+
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, false);
+		Assert(r == WJB_END_ARRAY);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_BEGIN_OBJECT)
+	{
+		int			i;
+		uint32		n = v.val.object.nPairs;
+		JsonbValue	k;
+		bool		done = false;
+
+		(void) pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL);
+
+		if (level >= path_len || path_nulls[level])
+			done = true;
+
+		for (i = 0; i < n; i++)
+		{
+			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);
+					}
+				}
+			}
+		}
+
+		r = JsonbIteratorNext(it, &v, true);
+		Assert(r == WJB_END_OBJECT);
+		res = pushJsonbValue(st, r, NULL);
+	}
+	else if (r == WJB_ELEM || r == WJB_VALUE)
+	{
+		res = pushJsonbValue(st, r, &v);
+	}
+	else
+	{
+		elog(PANIC, "impossible state");
+	}
+
+	return res;
+}
+
+/*
+ * get the integer in the argument, if any,
+ * returning a success flag.
+ */
+static bool
+h_atoi(char *c, int *idx)
+{
+	char	   *badp;
+	errno = 0;
+
+	*idx = (int) strtol(c, &badp, 10);
+
+	if (errno != 0 || badp == c)
+	{
+		return false;
+	}
+	else
+	{
+		return true;
+	}
+}
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index e22eb27..8758aea 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 = 3277 (  "||"	   PGNSP PGUID b f f 3802 3802 3802 0 0 jsonb_concat - - ));
+DESCR("concatenate");
+DATA(insert OID = 3278 (  "-"	   PGNSP PGUID b f f 3802 25 3802 0 0 3388 - - ));
+DESCR("delete");
+DATA(insert OID = 3279 (  "-"	   PGNSP PGUID b f f 3802 23 3802 0 0 3389 - - ));
+DESCR("delete");
+DATA(insert OID = 3280 (  "-"	   PGNSP PGUID b f f 3802 1009 3802 0 0 3390 - - ));
+DESCR("delete");
 
 /*
  * function prototypes
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 619d996..0e3c54a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4797,7 +4797,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_ gin_triconsistent_jsonb_path _null_ _null_ _null_ ));
 DESCR("GIN support");
-
+DATA(insert OID = 3387 (  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_ jsonb_concat _null_ _null_ _null_ ));
+DATA(insert OID = 3388 (  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_ jsonb_delete _null_ _null_ _null_ ));
+DATA(insert OID = 3389 (  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_ jsonb_delete_idx _null_ _null_ _null_ ));
+DATA(insert OID = 3390 (  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_ jsonb_delete_path _null_ _null_ _null_ ));
+DATA(insert OID = 3391 (  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_ jsonb_replace _null_ _null_ _null_ ));
+DESCR("Replace part of a jsonb");
+DATA(insert OID = 3392 (  jsonb_indent	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_indent _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_ txid_snapshot_in _null_ _null_ _null_ ));
 DESCR("I/O");
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 9d1770e..1b9880c 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_indent(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 0d55890..a1d2b2b 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_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/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out
index 694b6ea..8c57594 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 676e1a7..fd3c1a3 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_indent('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}');
+select jsonb_indent('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]');
+select jsonb_indent('{"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[];
#20Petr Jelinek
petr@2ndquadrant.com
In reply to: Dmitry Dolgov (#19)
Re: mogrify and indent features for jsonb

On 18/04/15 20:35, Dmitry Dolgov wrote:

Sorry for late reply. Here is a slightly improved version of the patch
with the new `h_atoi` function, I hope this implementation will be more
appropriate.

It's better, but a) I don't like the name of the function b) I don't see
why we need the function at all given that it's called from only one
place and is effectively couple lines of code.

In general I wonder if it wouldn't be better to split the replacePath
into 3 functions, one replacePath, one replacePathObject,
replacePathArray and call those Object/Array ones from the replacePath
and inlining the h_atoi code into the Array one. If this was done then
it would make also sense to change the main if/else in replacePath into
a switch.

Another thing I noticed now when reading the code again - you should not
be using braces when you only have one command in the code-block.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Andrew Dunstan
andrew@dunslane.net
In reply to: Petr Jelinek (#20)
Re: mogrify and indent features for jsonb

On 04/27/2015 12:46 PM, Petr Jelinek wrote:

Another thing I noticed now when reading the code again - you should
not be using braces when you only have one command in the code-block.

There's one exception I, at least, have to this rule, namely when
there's a corresponding compound if or else. I personally find this
unaesthetic to put it mildly:

if (condition)
statement;
else
{
block of statements;
}

pgindent used to produce stuff like this, and you had to put a comment
in the block to get around it, but we stopped that years ago, IIRC.

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#21)
Re: mogrify and indent features for jsonb

On Mon, Apr 27, 2015 at 6:41 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

There's one exception I, at least, have to this rule, namely when there's a
corresponding compound if or else. I personally find this unaesthetic to put
it mildly:

if (condition)
statement;
else
{
block of statements;
}

Hmm, I don't dislike that style. If somebody submitted a patch with
braces around the lone statement, I would remove them before
committing.

<ducks>

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#22)
Re: mogrify and indent features for jsonb

Robert Haas wrote:

On Mon, Apr 27, 2015 at 6:41 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

There's one exception I, at least, have to this rule, namely when there's a
corresponding compound if or else. I personally find this unaesthetic to put
it mildly:

if (condition)
statement;
else
{
block of statements;
}

Hmm, I don't dislike that style. If somebody submitted a patch with
braces around the lone statement, I would remove them before
committing.

Same here.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#23)
Re: mogrify and indent features for jsonb

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Robert Haas wrote:

On Mon, Apr 27, 2015 at 6:41 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

There's one exception I, at least, have to this rule, namely when there's a
corresponding compound if or else. I personally find this unaesthetic to put
it mildly:

if (condition)
statement;
else
{
block of statements;
}

Hmm, I don't dislike that style. If somebody submitted a patch with
braces around the lone statement, I would remove them before
committing.

Same here.

Agreed.

Thanks!

Stephen

#25Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#22)
Re: mogrify and indent features for jsonb

On 04/29/2015 01:19 PM, Robert Haas wrote:

On Mon, Apr 27, 2015 at 6:41 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

There's one exception I, at least, have to this rule, namely when there's a
corresponding compound if or else. I personally find this unaesthetic to put
it mildly:

if (condition)
statement;
else
{
block of statements;
}

Hmm, I don't dislike that style. If somebody submitted a patch with
braces around the lone statement, I would remove them before
committing.

<ducks>

It's a matter of taste, but I find things a lot easier to understand
when they are symmetrical. Thus I like all the branches of an "if" to be
either in a block or not, and I like braces to line up either
horizontally or vertically. Perhaps this reflects my history, where I
wrote huge amounts of Ada and other non-C-like languages, well before I
ever wrote lots of C or C-ish languages.

Another case where I think putting a single statement in a block makes
sense is where the condition of the "if" spreads across more than one
line. This works particularly well with our BSD style brace placement.

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Kevin Grittner
kgrittn@ymail.com
In reply to: Andrew Dunstan (#25)
Re: mogrify and indent features for jsonb

Andrew Dunstan <andrew@dunslane.net> wrote:

It's a matter of taste, but I find things a lot easier to
understand when they are symmetrical. Thus I like all the
branches of an "if" to be either in a block or not, and I like
braces to line up either horizontally or vertically. Perhaps this
reflects my history, where I wrote huge amounts of Ada and other
non-C-like languages, well before I ever wrote lots of C or C-ish
languages.

Another case where I think putting a single statement in a block
makes sense is where the condition of the "if" spreads across
more than one line. This works particularly well with our BSD
style brace placement.

My personal preferences are the same on all of that, especially
that the closing paren, brace, or bracket should be either in the
same line or the same column as its mate. If we were going to open
a green-field discussion about what style to *choose* I would be
arguing for all of the above (plus a few other things which are not
current PostgreSQL style).

That said, I feel very strongly that it is important that everyone
use the *same* style. It is far more important to me that we stick
to a single style than that the style match my personal
preferences. The project style seems to me to be that a single
statement is not put into braces unless needed for correctness or
to prevent warnings about ambiguity from the compilers.

By the way, my preference for the above are not strong enough to
want to open up the style choices to re-evaluation. PLEASE, no!

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Jan de Visser
jan@de-visser.net
In reply to: Andrew Dunstan (#25)
Re: mogrify and indent features for jsonb

On April 29, 2015 03:09:51 PM Andrew Dunstan wrote:

On 04/29/2015 01:19 PM, Robert Haas wrote:

On Mon, Apr 27, 2015 at 6:41 PM, Andrew Dunstan <andrew@dunslane.net>

wrote:

There's one exception I, at least, have to this rule, namely when there's
a
corresponding compound if or else. I personally find this unaesthetic to
put>>
it mildly:
if (condition)

statement;

else
{

block of statements;

}

Hmm, I don't dislike that style. If somebody submitted a patch with
braces around the lone statement, I would remove them before
committing.

<ducks>

It's a matter of taste, but I find things a lot easier to understand
when they are symmetrical. Thus I like all the branches of an "if" to be
either in a block or not, and I like braces to line up either
horizontally or vertically. Perhaps this reflects my history, where I
wrote huge amounts of Ada and other non-C-like languages, well before I
ever wrote lots of C or C-ish languages.

Another case where I think putting a single statement in a block makes
sense is where the condition of the "if" spreads across more than one
line. This works particularly well with our BSD style brace placement.

I'm sure that many, many bits have been spilled over this, reaching way back
into the stone age of computing, sometimes almost reaching emacs-vs-vi levels
of intensity.

My position is the better-safe-than-sorry corner, which says to always add
braces, even if there's only one statement. Because one day somebody will be
in a rush, and will add a second statement without adding the braces, and
things will explode horribly.

But that's just me.

jan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Petr Jelinek
petr@2ndquadrant.com
In reply to: Petr Jelinek (#20)
1 attachment(s)
Re: mogrify and indent features for jsonb

On 27/04/15 18:46, Petr Jelinek wrote:

On 18/04/15 20:35, Dmitry Dolgov wrote:

Sorry for late reply. Here is a slightly improved version of the patch
with the new `h_atoi` function, I hope this implementation will be more
appropriate.

It's better, but a) I don't like the name of the function b) I don't see
why we need the function at all given that it's called from only one
place and is effectively couple lines of code.

In general I wonder if it wouldn't be better to split the replacePath
into 3 functions, one replacePath, one replacePathObject,
replacePathArray and call those Object/Array ones from the replacePath
and inlining the h_atoi code into the Array one. If this was done then
it would make also sense to change the main if/else in replacePath into
a switch.

Another thing I noticed now when reading the code again - you should not
be using braces when you only have one command in the code-block.

Hi,

I worked this over a bit (I hope Dmitry won't mind) and I am now more or
less happy with the patch. Here is list of changes I made:
- rebased to todays master (Oid conflicts, transforms patch conflicts)
- changed the main if/else if/else if/else to switch in replacePath
- split the replacePath into 3 functions (main one plus 2 helpers for
Object and Array)
- removed the h_atoi and use the strtol directly
- renamed jsonb_indent to jsonb_pretty because we use "pretty" for
similar use-case everywhere else
- fixed whitespace/brackets where needed
- added/reworded some comments and couple of lines in docs

I think it's ready for Andrew now.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-jsonbxcore5.patchapplication/x-patch; name=0001-jsonbxcore5.patchDownload
From b2da4958fc75e7c7009340e6b2e4ccb155632078 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 29 Apr 2015 22:54:01 +0200
Subject: [PATCH] jsonbxcore5

---
 doc/src/sgml/func.sgml                |  62 +++
 src/backend/utils/adt/jsonb.c         |  81 +++-
 src/backend/utils/adt/jsonfuncs.c     | 709 ++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_operator.h     |   8 +
 src/include/catalog/pg_proc.h         |   9 +-
 src/include/utils/jsonb.h             |  19 +-
 src/test/regress/expected/jsonb.out   | 424 +++++++++++++++++++-
 src/test/regress/expected/jsonb_1.out | 424 +++++++++++++++++++-
 src/test/regress/sql/jsonb.sql        |  85 +++-
 9 files changed, 1805 insertions(+), 16 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dcade93..f0d8d96 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10257,6 +10257,32 @@ table2-mapping
         <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry>
         <entry><literal>'["a", "b"]'::jsonb ?&amp; 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>
@@ -10767,6 +10793,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 7e2c359..bccc669 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
@@ -422,12 +424,39 @@ 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;
 	JsonbIteratorToken type = WJB_DONE;
 	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 274f64c..bd71135 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
 {
@@ -3195,3 +3210,697 @@ 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
+		elog(ERROR, "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(PANIC, "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 e22eb27..34ebb50 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 55c246e..6977036 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4799,7 +4799,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 9d1770e..7b56175 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 0d55890..83201fb 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 694b6ea..8c57594 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 676e1a7..808da9c 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[];
-- 
1.9.1

#29Eva7
billnoboyd@gmail.com
In reply to: Andrew Dunstan (#1)
Re: mogrify and indent features for jsonb

Yeaha didn't work either on http://jsonprettyprint.net
<http://jsonprettyprint.net&gt; for me.

--
View this message in context: http://postgresql.nabble.com/mogrify-and-indent-features-for-jsonb-tp5838008p5848933.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30jamesmalvi
james.malvi@gmail.com
In reply to: Petr Jelinek (#20)
Re: mogrify and indent features for jsonb

I would like to suggest https://jsonformatter.org/json-pretty-print for
formatting and beautifying JSON data.

Also for use this validationg JSON data, https://jsonformatter.org

--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#31Andrew Dunstan
andrew.dunstan@2ndquadrant.com
In reply to: jamesmalvi (#30)
Re: mogrify and indent features for jsonb

On 04/30/2018 05:33 AM, jamesmalvi wrote:

I would like to suggest https://jsonformatter.org/json-pretty-print for
formatting and beautifying JSON data.

Also for use this validationg JSON data, https://jsonformatter.org

We already have a pretty-print function built in, and we already use a
validating parser. So it's not clear to me what you want that we don't have.

cheers

andrew

--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services