diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile
index 43b7e5f9e71f83761101691f8d830788eb7fa51f..2b60fbed0e0cbd332e6a70c19562acffddbc8645 100644
--- a/contrib/hstore/Makefile
+++ b/contrib/hstore/Makefile
@@ -5,7 +5,8 @@ OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \
 	crc32.o
 
 EXTENSION = hstore
-DATA = hstore--1.2.sql hstore--1.1--1.2.sql hstore--1.0--1.1.sql \
+DATA = hstore--1.3.sql hstore--1.2--1.3.sql \
+	hstore--1.1--1.2.sql hstore--1.0--1.1.sql \
 	hstore--unpackaged--1.0.sql
 
 REGRESS = hstore
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 21141430229a4edcf410dc8f514d7521902ed0a0..9749e45c14392acafb89201dba55a8f3c0663b73 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -1453,7 +1453,7 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
      1
 (1 row)
 
--- json
+-- json and jsonb
 select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
                                          hstore_to_json                                          
 -------------------------------------------------------------------------------------------------
@@ -1472,6 +1472,24 @@ select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012
  {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}
 (1 row)
 
+select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
+                                         hstore_to_jsonb                                         
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
+select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+                                              jsonb                                              
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
+select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
+                                 hstore_to_jsonb_loose                                 
+---------------------------------------------------------------------------------------
+ {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "a key": 1}
+(1 row)
+
 create table test_json_agg (f1 text, f2 hstore);
 insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'),
        ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4');
diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0a7056015b7415a9d72801076e4be7e015bfb6a4
--- /dev/null
+++ b/contrib/hstore/hstore--1.2--1.3.sql
@@ -0,0 +1,17 @@
+/* contrib/hstore/hstore--1.2--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION hstore UPDATE TO '1.3'" to load this file. \quit
+
+CREATE FUNCTION hstore_to_jsonb(hstore)
+RETURNS jsonb
+AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE CAST (hstore AS jsonb)
+  WITH FUNCTION hstore_to_jsonb(hstore);
+
+CREATE FUNCTION hstore_to_jsonb_loose(hstore)
+RETURNS jsonb
+AS 'MODULE_PATHNAME', 'hstore_to_jsonb_loose'
+LANGUAGE C IMMUTABLE STRICT;
diff --git a/contrib/hstore/hstore--1.2.sql b/contrib/hstore/hstore--1.3.sql
similarity index 97%
rename from contrib/hstore/hstore--1.2.sql
rename to contrib/hstore/hstore--1.3.sql
index f415a7230b440d1dd70124a971dcf364cc0e926f..995ade1b3ce15bc8cbbdd8681ce8d0dc14e0a8ef 100644
--- a/contrib/hstore/hstore--1.2.sql
+++ b/contrib/hstore/hstore--1.3.sql
@@ -1,4 +1,4 @@
-/* contrib/hstore/hstore--1.1.sql */
+/* contrib/hstore/hstore--1.3.sql */
 
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION hstore" to load this file. \quit
@@ -247,6 +247,19 @@ RETURNS json
 AS 'MODULE_PATHNAME', 'hstore_to_json_loose'
 LANGUAGE C IMMUTABLE STRICT;
 
+CREATE FUNCTION hstore_to_jsonb(hstore)
+RETURNS jsonb
+AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE CAST (hstore AS jsonb)
+  WITH FUNCTION hstore_to_jsonb(hstore);
+
+CREATE FUNCTION hstore_to_jsonb_loose(hstore)
+RETURNS jsonb
+AS 'MODULE_PATHNAME', 'hstore_to_jsonb_loose'
+LANGUAGE C IMMUTABLE STRICT;
+
 CREATE FUNCTION hstore(record)
 RETURNS hstore
 AS 'MODULE_PATHNAME', 'hstore_from_record'
diff --git a/contrib/hstore/hstore.control b/contrib/hstore/hstore.control
index 9daf5e2e00a38e3597660f4e04ad60d2f39a28b2..dcc3b687cff46c75e524054419cb0702505d111e 100644
--- a/contrib/hstore/hstore.control
+++ b/contrib/hstore/hstore.control
@@ -1,5 +1,5 @@
 # hstore extension
 comment = 'data type for storing sets of (key, value) pairs'
-default_version = '1.2'
+default_version = '1.3'
 module_pathname = '$libdir/hstore'
 relocatable = true
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 65e4438d3225d49ab9eb61f7b0ec627824d08f67..6cfc93578876a19e42e52ad6463903f6545eef11 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -12,6 +12,7 @@
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
 #include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/typcache.h"
@@ -1374,3 +1375,167 @@ hstore_to_json(PG_FUNCTION_ARGS)
 
 	PG_RETURN_TEXT_P(cstring_to_text(dst.data));
 }
+
+PG_FUNCTION_INFO_V1(hstore_to_jsonb);
+Datum		hstore_to_jsonb(PG_FUNCTION_ARGS);
+Datum
+hstore_to_jsonb(PG_FUNCTION_ARGS)
+{
+	HStore	   *in = PG_GETARG_HS(0);
+	int			i;
+	int			count = HS_COUNT(in);
+	char	   *base = STRPTR(in);
+	HEntry	   *entries = ARRPTR(in);
+	JsonbParseState *state = NULL;
+	JsonbValue *res;
+
+	res = pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	for (i = 0; i < count; i++)
+	{
+		JsonbValue key, val;
+
+		key.estSize = sizeof(JEntry);
+		key.type = jbvString;
+		key.string.len = HS_KEYLEN(entries, i);
+		key.string.val = pnstrdup(HS_KEY(entries, base, i), key.string.len);
+		key.estSize += key.string.len;
+
+		res = pushJsonbValue(&state, WJB_KEY, &key);
+
+		if (HS_VALISNULL(entries, i))
+		{
+			val.estSize = sizeof(JEntry);
+			val.type = jbvNull;
+		}
+		else
+		{
+			val.estSize = sizeof(JEntry);
+			val.type = jbvString;
+			val.string.len = HS_VALLEN(entries, i);
+			val.string.val = pnstrdup(HS_VAL(entries, base, i), val.string.len);
+			val.estSize += val.string.len;
+		}
+		res = pushJsonbValue(&state, WJB_VALUE, &val);
+	}
+
+	res = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	PG_RETURN_POINTER(JsonbValueToJsonb(res));
+}
+
+PG_FUNCTION_INFO_V1(hstore_to_jsonb_loose);
+Datum		hstore_to_jsonb_loose(PG_FUNCTION_ARGS);
+Datum
+hstore_to_jsonb_loose(PG_FUNCTION_ARGS)
+{
+	HStore	   *in = PG_GETARG_HS(0);
+	int			i;
+	int			count = HS_COUNT(in);
+	char	   *base = STRPTR(in);
+	HEntry	   *entries = ARRPTR(in);
+	JsonbParseState *state = NULL;
+	JsonbValue *res;
+	StringInfoData tmp;
+	bool        is_number;
+
+	initStringInfo(&tmp);
+
+	res = pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	for (i = 0; i < count; i++)
+	{
+		JsonbValue key, val;
+
+		key.estSize = sizeof(JEntry);
+		key.type = jbvString;
+		key.string.len = HS_KEYLEN(entries, i);
+		key.string.val = pnstrdup(HS_KEY(entries, base, i), key.string.len);
+		key.estSize += key.string.len;
+
+		res = pushJsonbValue(&state, WJB_KEY, &key);
+
+		val.estSize = sizeof(JEntry);
+
+		if (HS_VALISNULL(entries, i))
+		{
+			val.type = jbvNull;
+		}
+		/* guess that values of 't' or 'f' are booleans */
+		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't')
+		{
+			val.type = jbvBool;
+			val.boolean = true;
+		}
+		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f')
+		{
+			val.type = jbvBool;
+			val.boolean = false;
+		}
+		else
+		{
+			is_number = false;
+			resetStringInfo(&tmp);
+
+			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
+
+			/*
+			 * don't treat something with a leading zero followed by another
+			 * digit as numeric - could be a zip code or similar
+			 */
+			if (tmp.len > 0 &&
+				!(tmp.data[0] == '0' &&
+				  isdigit((unsigned char) tmp.data[1])) &&
+				strspn(tmp.data, "+-0123456789Ee.") == tmp.len)
+			{
+				/*
+				 * might be a number. See if we can input it as a numeric
+				 * value. Ignore any actual parsed value.
+				 */
+				char	   *endptr = "junk";
+				long		lval;
+
+				lval = strtol(tmp.data, &endptr, 10);
+				(void) lval;
+				if (*endptr == '\0')
+				{
+					/*
+					 * strol man page says this means the whole string is
+					 * valid
+					 */
+					is_number = true;
+				}
+				else
+				{
+					/* not an int - try a double */
+					double		dval;
+
+					dval = strtod(tmp.data, &endptr);
+					(void) dval;
+					if (*endptr == '\0')
+						is_number = true;
+				}
+			}
+			if (is_number)
+			{
+				val.type = jbvNumeric;
+				val.numeric = DatumGetNumeric(
+					DirectFunctionCall3(numeric_in, CStringGetDatum(tmp.data), 0, -1));
+				val.estSize += VARSIZE_ANY(val.numeric) +sizeof(JEntry);
+			}
+			else
+			{
+				val.estSize = sizeof(JEntry);
+				val.type = jbvString;
+				val.string.len = HS_VALLEN(entries, i);
+				val.string.val = pnstrdup(HS_VAL(entries, base, i), val.string.len);
+				val.estSize += val.string.len;
+			}
+		}
+		res = pushJsonbValue(&state, WJB_VALUE, &val);
+	}
+
+	res = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	PG_RETURN_POINTER(JsonbValueToJsonb(res));
+}
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index 9518f569ecf865f0563991231a463220d5ac32e3..5a9e9ee5ae8b2c18b49f8a31929ed873c282af63 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -331,11 +331,15 @@ set enable_seqscan=off;
 select count(*) from testhstore where h #># 'p=>1';
 select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t';
 
--- json
+-- json and jsonb
 select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
 select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
 select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
 
+select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
+select cast( hstore  '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
+
 create table test_json_agg (f1 text, f2 hstore);
 insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'),
        ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4');
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index ac285ce01190e30455dd53022ab110b64e765ca5..cc458b4753f43554bfde8f50a406efb5582e45b2 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -139,7 +139,13 @@
       <row>
        <entry><type>json</type></entry>
        <entry></entry>
-       <entry>JSON data</entry>
+       <entry>textual JSON data</entry>
+      </row>
+
+      <row>
+       <entry><type>jsonb</type></entry>
+       <entry></entry>
+       <entry>binary JSON data, decomposed</entry>
       </row>
 
       <row>
@@ -4220,34 +4226,7 @@ SET xmloption TO { DOCUMENT | CONTENT };
    </sect2>
   </sect1>
 
-  <sect1 id="datatype-json">
-   <title><acronym>JSON</> Type</title>
-
-   <indexterm zone="datatype-json">
-    <primary>JSON</primary>
-   </indexterm>
-
-   <para>
-    The <type>json</type> data type can be used to store JSON (JavaScript
-    Object Notation) data, as specified in <ulink
-    url="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</ulink>.  Such
-    data can also be stored as <type>text</type>, but the
-    <type>json</type> data type has the advantage of checking that each
-    stored value is a valid JSON value.  There are also related support
-    functions available; see <xref linkend="functions-json">.
-   </para>
-
-   <para>
-    <productname>PostgreSQL</productname> allows only one server encoding
-    per database.  It is therefore not possible for JSON to conform rigidly
-    to the specification unless the server encoding is UTF-8.  Attempts to
-    directly include characters which cannot be represented in the server
-    encoding will fail; conversely, characters which can be represented in
-    the server encoding but not in UTF-8 will be allowed.
-    <literal>\uXXXX</literal> escapes are allowed regardless of the server
-    encoding, and are checked only for syntactic correctness.
-   </para>
-  </sect1>
+  &json;
 
   &array;
 
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 6c8e254a584460dbdb5fb26248635105c0190c56..ab6fcf7838cb5c55b15a73118b42d6e7c44d573b 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -22,6 +22,7 @@
 <!ENTITY dml        SYSTEM "dml.sgml">
 <!ENTITY func       SYSTEM "func.sgml">
 <!ENTITY indices    SYSTEM "indices.sgml">
+<!ENTITY json       SYSTEM "json.sgml">
 <!ENTITY mvcc       SYSTEM "mvcc.sgml">
 <!ENTITY perform    SYSTEM "perform.sgml">
 <!ENTITY queries    SYSTEM "queries.sgml">
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 71b9829d852b83bfb9557e4bdc3bc29aa9aadd3d..4e2fff7cd7481ef2a717957a4308fa3fccd1b254 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10079,12 +10079,13 @@ table2-mapping
   </indexterm>
 
    <para>
-   <xref linkend="functions-json-op-table"> shows the operators that are
-   available for use with JSON (see <xref linkend="datatype-json">) data.
+   <xref linkend="functions-json-op-table"> shows the operators that
+   are available for use with the two JSON datatypes (see <xref
+   linkend="datatype-json">).
   </para>
 
   <table id="functions-json-op-table">
-     <title>JSON Operators</title>
+     <title><type>json</> and <type>jsonb</> Operators</title>
      <tgroup cols="4">
       <thead>
        <row>
@@ -10121,13 +10122,13 @@ table2-mapping
        </row>
        <row>
         <entry><literal>#&gt;</literal></entry>
-        <entry>array of text</entry>
+        <entry>text[]</entry>
         <entry>Get JSON object at specified path</entry>
         <entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#&gt;'{a,2}'</literal></entry>
        </row>
        <row>
         <entry><literal>#&gt;&gt;</literal></entry>
-        <entry>array of text</entry>
+        <entry>text[]</entry>
         <entry>Get JSON object at specified path as text</entry>
         <entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#&gt;&gt;'{a,2}'</literal></entry>
        </row>
@@ -10135,13 +10136,107 @@ table2-mapping
      </tgroup>
    </table>
 
+  <note>
+   <para>
+    There are parallel variants of these operators for both the
+    <type>json</type> and <type>jsonb</type> types.  In addition to
+    those operators common to both types, a further set of operators
+    exists for <type>jsonb</type> (which comprise the default
+    <acronym>GIN</acronym> operator class).
+   </para>
+  </note>
   <para>
-   <xref linkend="functions-json-table"> shows the functions that are available
-   for creating and manipulating JSON (see <xref linkend="datatype-json">) data.
+   The following are <type>jsonb</>-only operators, used by
+   <type>jsonb</> operator classes.  For a full description of
+   <type>jsonb</> containment semantics and nesting, see <xref
+   linkend="json-containment">.  <xref linkend="json-indexing">
+   describes how these operators can be used to effectively index
+   <type>jsonb</>.
   </para>
+  <table id="functions-jsonb-op-table">
+     <title>Additonal JSONB Operators</title>
+     <tgroup cols="4">
+      <thead>
+       <row>
+        <entry>Operator</entry>
+        <entry>Right Operand Type</entry>
+        <entry>Description</entry>
+        <entry>Example</entry>
+       </row>
+      </thead>
+      <tbody>
+       <row>
+        <entry><literal>=</literal></entry>
+        <entry>jsonb</entry>
+        <entry>Is the jsonb equal to this jsonb?</entry>
+        <entry><literal>'[1,2,3]'::jsonb = '[1,2,3]'::jsonb</literal></entry>
+       </row>
+       <row>
+        <entry><literal>@&gt;</literal></entry>
+        <entry>jsonb</entry>
+        <entry>Does the jsonb contain within it this jsonb?</entry>
+        <entry><literal>'{"a":1, "b":2}'::jsonb &#64;&gt; '{"b":2}'::jsonb</literal></entry>
+       </row>
+       <row>
+        <entry><literal>&lt;@</literal></entry>
+        <entry>jsonb</entry>
+        <entry>Does the jsonb have contained within it this jsonb?</entry>
+        <entry><literal>'{"b":2}'::jsonb &lt;@ '{"a":1, "b":2}'::jsonb</literal></entry>
+       </row>
+       <row>
+        <entry><literal>?</literal></entry>
+        <entry>text</entry>
+        <entry>Does this key/element <emphasis>string</emphasis> exist?</entry>
+        <entry><literal>'{"a":1, "b":2}'::jsonb ? 'b'</literal></entry>
+       </row>
+       <row>
+        <entry><literal>?|</literal></entry>
+        <entry>text[]</entry>
+        <entry>Do any of these key/element <emphasis>strings</emphasis> exist?</entry>
+        <entry><literal>'{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c']</literal></entry>
+       </row>
+       <row>
+        <entry><literal>?&amp;</literal></entry>
+        <entry>text[]</entry>
+        <entry>Do all of these key/element <emphasis>strings</emphasis> exist?</entry>
+        <entry><literal>'["a", "b"]'::jsonb ?&amp; array['a', 'b']</literal></entry>
+       </row>
+      </tbody>
+     </tgroup>
+   </table>
+
+  <!--
+     The release notes contain a reference to "functions-json-table". Since
+     that table is now split in two, the id has been parked here so we don't
+     have to change the release notes.
+  -->
+  <para id="functions-json-table">
+   <xref linkend="functions-json-creation-table"> shows the functions that are
+   available for creating <type>json</type> values.
+   (see <xref linkend="datatype-json">)
+  </para>
+
+  <indexterm>
+   <primary>array_to_json</primary>
+  </indexterm>
+  <indexterm>
+   <primary>row_to_json</primary>
+  </indexterm>
+  <indexterm>
+   <primary>to_json</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_build_array</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_build_object</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_object</primary>
+  </indexterm>
 
-  <table id="functions-json-table">
-    <title>JSON Support Functions</title>
+  <table id="functions-json-creation-table">
+    <title>JSON Creation Functions</title>
     <tgroup cols="5">
      <thead>
       <row>
@@ -10155,9 +10250,6 @@ table2-mapping
      <tbody>
       <row>
        <entry>
-         <indexterm>
-          <primary>array_to_json</primary>
-         </indexterm>
          <literal>array_to_json(anyarray [, pretty_bool])</literal>
        </entry>
        <entry><type>json</type></entry>
@@ -10171,9 +10263,6 @@ table2-mapping
       </row>
       <row>
        <entry>
-         <indexterm>
-          <primary>row_to_json</primary>
-         </indexterm>
          <literal>row_to_json(record [, pretty_bool])</literal>
        </entry>
        <entry><type>json</type></entry>
@@ -10186,9 +10275,6 @@ table2-mapping
       </row>
       <row>
        <entry>
-         <indexterm>
-          <primary>to_json</primary>
-         </indexterm>
          <literal>to_json(anyelement)</literal>
        </entry>
        <entry><type>json</type></entry>
@@ -10204,11 +10290,180 @@ table2-mapping
       </row>
       <row>
        <entry>
-         <indexterm>
-          <primary>json_array_length</primary>
-         </indexterm>
-         <literal>json_array_length(json)</literal>
+         <literal>json_build_array(VARIADIC "any")</literal>
        </entry>
+       <entry><type>json</type></entry>
+       <entry>
+         Builds a heterogeneously-typed json array out of a variadic argument list.
+       </entry>
+       <entry><literal>SELECT json_build_array(1,2,'3',4,5);</literal></entry>
+       <entry>
+<programlisting>
+ json_build_array
+-------------------
+ [1, 2, "3", 4, 5]
+ </programlisting>
+       </entry>
+      </row>
+      <row>
+       <entry>
+         <literal>json_build_object(VARIADIC "any")</literal>
+       </entry>
+       <entry><type>json</type></entry>
+       <entry>
+         Builds a JSON array out of a variadic argument list.  By
+         convention, the object is constructed out of alternating
+         name/value arguments.
+       </entry>
+       <entry><literal>SELECT json_build_object('foo',1,'bar',2);</literal></entry>
+       <entry>
+<programlisting>
+   json_build_object
+------------------------
+ {"foo" : 1, "bar" : 2}
+ </programlisting>
+       </entry>
+      </row>
+      <row>
+       <entry>
+         <literal>json_object(text[])</literal>
+       </entry>
+       <entry><type>json</type></entry>
+       <entry>
+         Builds a JSON object out of a text array.  The array must have either
+         exactly one dimension with an even number of members, in which case
+         they are taken as alternating name/value pairs, or two dimensions
+         such that each inner array has exactly two elements, which
+         are taken as a name/value pair.
+       </entry>
+       <entry><literal>select * from json_object('{a, 1, b, "def", c, 3.5}')  or <literal>select json_object('{{a, 1},{b, "def"},{c, 3.5}}')</literal></literal></entry>
+       <entry>
+<programlisting>
+              json_object
+---------------------------------------
+ {"a" : "1", "b" : "def", "c" : "3.5"}
+ </programlisting>
+       </entry>
+      </row>
+      <row>
+       <entry>
+         <literal>json_object(keys text[], values text[])</literal>
+       </entry>
+       <entry><type>json</type></entry>
+       <entry>
+         The two-argument form of JSON object takes keys and values pairwise from two separate
+         arrays. In all other respects it is identical to the one-argument form.
+       </entry>
+       <entry><literal>select json_object('{a, b}', '{1,2}');</literal></entry>
+       <entry>
+<programlisting>
+      json_object
+------------------------
+ {"a" : "1", "b" : "2"}
+ </programlisting>
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+
+  <para>
+   <xref linkend="functions-json-processing-table"> shows the functions that
+   are available for processing <type>json</type> and <type>jsonb</type> values.
+   (see <xref linkend="datatype-json">)
+  </para>
+
+  <indexterm>
+   <primary>json_array_length</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_array_length</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_each</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_each</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_each_text</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_each_text</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_extract_path</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_extract_path</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_extract_path_text</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_extract_path_text</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_object_keys</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_object_keys</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_populate_record</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_populate_record</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_populate_recordset</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_populate_recordset</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_array_elements</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_array_elements</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_array_elements_text</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_array_elements_text</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_typeof</primary>
+  </indexterm>
+  <indexterm>
+   <primary>jsonb_typeof</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_to_record</primary>
+  </indexterm>
+  <indexterm>
+   <primary>json_to_recordset</primary>
+  </indexterm>
+
+  <table id="functions-json-processing-table">
+    <title>JSON Processing Functions</title>
+    <tgroup cols="5">
+     <thead>
+      <row>
+       <entry>Function</entry>
+       <entry>Return Type</entry>
+       <entry>Description</entry>
+       <entry>Example</entry>
+       <entry>Example Result</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><para><literal>json_array_length(json)</literal>
+         </para><para><literal>jsonb_array_length(jsonb)</literal>
+       </para></entry>
        <entry><type>int</type></entry>
        <entry>
          Returns the number of elements in the outermost JSON array.
@@ -10217,13 +10472,12 @@ table2-mapping
        <entry><literal>5</literal></entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_each</primary>
-         </indexterm>
-         <literal>json_each(json)</literal>
-       </entry>
-       <entry><type>SETOF key text, value json</type></entry>
+       <entry><para><literal>json_each(json)</literal>
+         </para><para><literal>jsonb_each(jsonb)</literal>
+       </para></entry>
+       <entry><para><literal>SETOF key text, value json</literal>
+         </para><para><literal>SETOF key text, value jsonb</literal>
+       </para></entry>
        <entry>
          Expands the outermost JSON object into a set of key/value pairs.
        </entry>
@@ -10238,12 +10492,9 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_each_text</primary>
-         </indexterm>
-         <literal>json_each_text(from_json json)</literal>
-       </entry>
+       <entry><para><literal>json_each_text(from_json json)</literal>
+         </para><para><literal>jsonb_each_text(from_json jsonb)</literal>
+       </para></entry>
        <entry><type>SETOF key text, value text</type></entry>
        <entry>
          Expands the outermost JSON object into a set of key/value pairs. The
@@ -10260,13 +10511,11 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_extract_path</primary>
-         </indexterm>
-         <literal>json_extract_path(from_json json, VARIADIC path_elems text[])</literal>
-       </entry>
-       <entry><type>json</type></entry>
+       <entry><para><literal>json_extract_path(from_json json, VARIADIC path_elems text[])</literal>
+        </para><para><literal>jsonb_extract_path(from_jsonb jsonb, VARIADIC path_elems text[])</literal>
+       </para></entry>
+       <entry><para><type>json</type></para><para><type>jsonb</type>
+       </para></entry>
        <entry>
          Returns JSON value pointed to by <parameter>path_elems</parameter>.
        </entry>
@@ -10274,12 +10523,9 @@ table2-mapping
        <entry><literal>{"f5":99,"f6":"foo"}</literal></entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_extract_path_text</primary>
-         </indexterm>
-         <literal>json_extract_path_text(from_json json, VARIADIC path_elems text[])</literal>
-       </entry>
+       <entry><para><literal>json_extract_path_text(from_json json, VARIADIC path_elems text[])</literal>
+         </para><para><literal>json_extract_path_text(from_json json, VARIADIC path_elems text[])</literal>
+       </para></entry>
        <entry><type>text</type></entry>
        <entry>
          Returns JSON value pointed to by <parameter>path_elems</parameter>.
@@ -10288,12 +10534,9 @@ table2-mapping
        <entry><literal>foo</literal></entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_object_keys</primary>
-         </indexterm>
-         <literal>json_object_keys(json)</literal>
-       </entry>
+       <entry><para><literal>json_object_keys(json)</literal>
+         </para><para><literal>jsonb_object_keys(jsonb)</literal>
+       </para></entry>
        <entry><type>SETOF text</type></entry>
        <entry>
           Returns set of keys in the JSON object.  Only the <quote>outer</quote> object will be displayed.
@@ -10309,18 +10552,16 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_populate_record</primary>
-         </indexterm>
-         <literal>json_populate_record(base anyelement, from_json json, [, use_json_as_text bool=false]</literal>
-       </entry>
+       <entry><para><literal>json_populate_record(base anyelement, from_json json, [, use_json_as_text bool=false])</literal>
+         </para><para><literal>jsonb_populate_record(base anyelement, from_json jsonb, [, use_json_as_text bool=false])</literal>
+       </para></entry>
        <entry><type>anyelement</type></entry>
        <entry>
          Expands the object in <replaceable>from_json</replaceable> to a row whose columns match
          the record type defined by base. Conversion will be best
          effort; columns in base with no corresponding key in <replaceable>from_json</replaceable>
-         will be left null. If a column is specified more than once, the last value is used.
+         will be left null. When processing <type>json</type>, if a
+         column is specified more than once, the last value is used.
        </entry>
        <entry><literal>select * from json_populate_record(null::x, '{"a":1,"b":2}')</literal></entry>
        <entry>
@@ -10332,19 +10573,17 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_populate_recordset</primary>
-         </indexterm>
-         <literal>json_populate_recordset(base anyelement, from_json json, [, use_json_as_text bool=false]</literal>
-       </entry>
+       <entry><para><literal>json_populate_recordset(base anyelement, from_json json, [, use_json_as_text bool=false])</literal>
+         </para><para><literal>jsonb_populate_recordset(base anyelement, from_json jsonb, [, use_json_as_text bool=false])</literal>
+       </para></entry>
        <entry><type>SETOF anyelement</type></entry>
        <entry>
          Expands the outermost set of objects in <replaceable>from_json</replaceable> to a set
          whose columns match the record type defined by base.
          Conversion will be best effort; columns in base with no
          corresponding key in <replaceable>from_json</replaceable> will be left null.
-         If a column is specified more than once, the last value is used.
+         When processing <type>json</type>, if a column is specified more
+         than once, the last value is used.
        </entry>
        <entry><literal>select * from json_populate_recordset(null::x, '[{"a":1,"b":2},{"a":3,"b":4}]')</literal></entry>
        <entry>
@@ -10357,13 +10596,12 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_array_elements</primary>
-         </indexterm>
-         <literal>json_array_elements(json)</literal>
-       </entry>
-       <entry><type>SETOF json</type></entry>
+       <entry><para><literal>json_array_elements(json)</literal>
+         </para><para><literal>jsonb_array_elements(jsonb)</literal>
+       </para></entry>
+       <entry><para><type>SETOF json</type>
+         </para><para><type>SETOF jsonb</type>
+       </para></entry>
        <entry>
          Expands a JSON array to a set of JSON values.
        </entry>
@@ -10379,12 +10617,9 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_array_elements_text</primary>
-         </indexterm>
-         <literal>json_array_elements_text(json)</literal>
-       </entry>
+       <entry><para><literal>json_array_elements_text(json)</literal>
+         </para><para><literal>jsonb_array_elements_text(jsonb)</literal>
+       </para></entry>
        <entry><type>SETOF text</type></entry>
        <entry>
          Expands a JSON array to a set of text values.
@@ -10400,12 +10635,9 @@ table2-mapping
        </entry>
       </row>
       <row>
-       <entry>
-         <indexterm>
-          <primary>json_typeof</primary>
-         </indexterm>
-         <literal>json_typeof(json)</literal>
-       </entry>
+       <entry><para><literal>json_typeof(json)</literal>
+         </para><para><literal>jsonb_typeof(jsonb)</literal>
+       </para></entry>
        <entry><type>text</type></entry>
        <entry>
          Returns the type of the outermost JSON value as a text string.  The types are
@@ -10418,98 +10650,11 @@ table2-mapping
       </row>
       <row>
        <entry>
-         <indexterm>
-          <primary>json_build_array</primary>
-         </indexterm>
-         <literal>json_build_array(VARIADIC "any")</literal>
-       </entry>
-       <entry><type>json</type></entry>
-       <entry>
-         Builds a heterogeneously-typed json array out of a variadic argument list.
-       </entry>
-       <entry><literal>SELECT json_build_array(1,2,'3',4,5);</literal></entry>
-       <entry>
-<programlisting>
- json_build_array
--------------------
- [1, 2, "3", 4, 5]
- </programlisting>
-       </entry>
-      </row>
-      <row>
-       <entry>
-         <indexterm>
-          <primary>json_build_object</primary>
-         </indexterm>
-         <literal>json_build_object(VARIADIC "any")</literal>
-       </entry>
-       <entry><type>json</type></entry>
-       <entry>
-         Builds a JSON array out of a variadic argument list.
-         By convention, the object is 
-         constructed out of alternating name/value arguments.
-       </entry>
-       <entry><literal>SELECT json_build_object('foo',1,'bar',2);</literal></entry>
-       <entry>
-<programlisting>
-   json_build_object
-------------------------
- {"foo" : 1, "bar" : 2}
- </programlisting>
-       </entry>
-      </row>
-      <row>
-       <entry>
-         <indexterm>
-          <primary>json_object</primary>
-         </indexterm>
-         <literal>json_object(text[])</literal>
-       </entry>
-       <entry><type>json</type></entry>
-       <entry>
-         Builds a JSON object out of a text array.  The array must have either
-         exactly one dimension with an even number of members, in which case
-         they are taken as alternating name/value pairs, or two dimensions
-         such that each inner array has exactly two elements, which
-         are taken as a name/value pair.
-       </entry>
-       <entry><literal>select * from json_object('{a, 1, b, "def", c, 3.5}')  or <literal>select * from json_object('{{a, 1},{b, "def"},{c, 3.5}}')</literal></literal></entry>
-       <entry>
-<programlisting>
-              json_object
----------------------------------------
- {"a" : "1", "b" : "def", "c" : "3.5"}
- </programlisting>
-       </entry>
-      </row>
-      <row>
-       <entry>
-         <literal>json_object(keys text[], values text[])</literal>
-       </entry>
-       <entry><type>json</type></entry>
-       <entry>
-         The two-argument form of JSON object takes keys and values pairwise from two separate
-         arrays. In all other respects it is identical to the one-argument form.
-       </entry>
-       <entry><literal>select * from json_object('{a, b}', '{1,2}');</literal></entry>
-       <entry>
-<programlisting>
-      json_object
-------------------------
- {"a" : "1", "b" : "2"}
- </programlisting>
-       </entry>
-      </row>
-      <row>
-       <entry>
-         <indexterm>
-          <primary>json_to_record</primary>
-         </indexterm>
          <literal>json_to_record(json, nested_as_text bool)</literal>
        </entry>
        <entry><type>record</type></entry>
        <entry>
-         json_to_record returns an arbitrary record from a JSON object.  As with all functions 
+         Returns an arbitrary record from a JSON object.  As with all functions 
          returning 'record', the caller must explicitly define the structure of the record 
          when making the call. The input JSON must be an object, not a scalar or an array.
          If nested_as_text is true, the function coerces nested complex elements to text.
@@ -10526,14 +10671,11 @@ table2-mapping
       </row>
       <row>
        <entry>
-         <indexterm>
-          <primary>json_to_recordset</primary>
-         </indexterm>
          <literal>json_to_recordset(json, nested_as_text bool)</literal>
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-         json_to_recordset returns an arbitrary set of records from a JSON object.  As with 
+         Returns an arbitrary set of records from a JSON object.  As with 
          json_to_record, the structure of the record must be explicitly defined when making the
          call.  However, with json_to_recordset the input JSON must be an array containing 
          objects.  nested_as_text works as with json_to_record.
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..d7d6de8d2877a1d5e70e67cb86d45a0d233eb3e8
--- /dev/null
+++ b/doc/src/sgml/json.sgml
@@ -0,0 +1,413 @@
+<!-- doc/src/sgml/json.sgml -->
+
+<sect1 id="datatype-json">
+ <title><acronym>JSON</> Types</title>
+
+ <indexterm zone="datatype-json">
+  <primary>JSON</primary>
+ </indexterm>
+
+ <indexterm zone="datatype-json">
+  <primary>JSONB</primary>
+ </indexterm>
+
+ <para>
+  JSON data types are for storing JSON (JavaScript Object Notation)
+  data, as specified in <ulink url="http://rfc7159.net/rfc7159">RFC
+  7159</ulink>. Such data can also be stored as <type>text</type>, but
+  both JSON data types have the advantage of enforcing that each
+  stored value is a valid JSON value.  There are also related support
+  functions available; see <xref linkend="functions-json">.
+ </para>
+
+ <para>
+  There are two JSON data types: <type>json</> and <type>jsonb</>.
+  Both accept <emphasis>almost</emphasis> identical sets of values as
+  input.  The major practical difference is one of efficiency.  The
+  <type>json</> data type stores an exact copy of the the input text,
+  which processing functions must continually reparse, while
+  <type>jsonb</> data is stored in a decomposed binary format that
+  makes it slightly less efficient to input due to added serialization
+  overhead, but significantly faster to process, since it never needs
+  reparsing.  <type>jsonb</> also supports advanced
+  <acronym>GIN</acronym> indexing, which is a further significant
+  advantage.
+ </para>
+
+ <para>
+  The other difference between the types is that the <type>json</>
+  type is guaranteed to contain an exact copy of the input, including
+  preservation of semantically insignificant white space, and the
+  order of keys within JSON objects (although <type>jsonb</> will
+  preserve trailing zeros within a JSON number). Also, because the
+  exact text is kept, if a JSON object within the value contains the
+  same key more than once, and has been stored using the <type>json</>
+  type, all the key/value pairs are kept.  In that case, the
+  processing functions consider the last value as the operative one.
+  By contrast, <type>jsonb</> does not preserve white space, does not
+  preserve the order of object keys, and does not keep duplicate
+  object keys.  Only the last value for a key specified in the input
+  is kept.
+ </para>
+
+ <para>
+  In general, most applications will prefer to store JSON data as
+  <type>jsonb</>, unless there are quite specialized needs.
+ </para>
+
+ <para>
+  <productname>PostgreSQL</productname> allows only one server
+  encoding per database.  It is therefore not possible for the JSON
+  types to conform rigidly to the specification unless the server
+  encoding is UTF-8. Attempts to directly include characters which
+  cannot be represented in the server encoding will fail; conversely,
+  characters which can be represented in the server encoding but not
+  in UTF-8 will be allowed.  <literal>\uXXXX</literal> escapes are
+  allowed regardless of the server encoding, and are checked only for
+  syntactic correctness.
+ </para>
+
+ <sect2 id="json-types">
+  <title>Mapping of RFC-7159/JSON Primitive Types to <productname>PostgreSQL</productname> Types</title>
+  <table id="json-type-mapping-table">
+     <title>Mapping of type correspondence, notes</title>
+     <tgroup cols="3">
+      <thead>
+       <row>
+        <entry><productname>PostgreSQL</productname> type</entry>
+        <entry>RFC-7159/JSON primitive type</entry>
+        <entry>Notes</entry>
+       </row>
+      </thead>
+      <tbody>
+       <row>
+        <entry><type>text</></entry>
+        <entry><type>string</></entry>
+        <entry>See general introductory notes on encoding and JSON</entry>
+       </row>
+       <row>
+        <entry><type>numeric</></entry>
+        <entry><type>number</></entry>
+        <entry><literal>NaN</literal> and <literal>infinity</literal> values are disallowed</entry>
+       </row>
+       <row>
+        <entry><type>boolean</></entry>
+        <entry><type>boolean</></entry>
+        <entry>Only lowercase <literal>true</literal> and <literal>false</literal> values are accepted</entry>
+       </row>
+       <row>
+        <entry><type>unknown</></entry>
+        <entry><type>null</></entry>
+        <entry>SQL <literal>NULL</literal> is orthogonal.  NULL semantics do not apply.</entry>
+       </row>
+      </tbody>
+     </tgroup>
+   </table>
+  <para>
+    Primitive types described by <acronym>RFC</> 7159 are effectively
+    internally mapped onto native
+    <productname>PostgreSQL</productname> types.  Therefore, there are
+    some very minor additional constraints on what constitutes valid
+    <type>jsonb</type> that do not apply to the <type>json</type>
+    type, or to JSON in the abstract, that pertain to limits on what
+    can be represented by the underlying type system.  These
+    implementation-defined restrictions are permitted by
+    <acronym>RFC</> 7159.  However, in practice problems are far more
+    likely to occur in other implementations which internally
+    represent the <type>number</> JSON primitive type as IEEE 754
+    double precision floating point values, which <acronym>RFC</> 7159
+    explicitly anticipates and allows for.  When using JSON as an
+    interchange format with such systems, the danger of losing numeric
+    precision in respect of data originally stored by
+    <productname>PostgreSQL</productname> should be considered.
+  </para>
+  <para>
+    Conversely, as noted above there are some minor restrictions on
+    the input format of JSON primitive types that do not apply to
+    corresponding <productname>PostgreSQL</productname> types.
+  </para>
+
+ </sect2>
+
+ <sect2 id="json-querying">
+  <title>Querying <type>jsonb</type> documents effectively</title>
+  <para>
+   Representing data as JSON can be considerably more flexible than
+   the traditional relational data model, which is compelling in
+   environments where requirements are fluid.  It is quite possible
+   for both approaches to co-exist and complement each other within
+   the same application.  However, even for applications where maximal
+   flexibility is desired, it is still recommended that JSON documents
+   have a somewhat fixed structure.  This structure is typically
+   unenforced (though enforcing some business rules declaratively is
+   possible), but makes it easier to write queries that usefully
+   summarize a set of <quote>documents</> (datums) in a table.
+  </para>
+  <para>
+   <type>jsonb</> data is subject to the same concurrency control
+   considerations as any other datatype when stored in a table.
+   Although storing large documents is practicable, in order to ensure
+   correct behavior row-level locks are, quite naturally, aquired as
+   rows are updated.  Consider keeping <type>jsonb</> documents at a
+   manageable size in order to decrease lock contention among updating
+   transactions.  Ideally, <type>jsonb</> documents should each
+   represent an atomic datum that business rules dictate cannot
+   reasonably be further subdivided into smaller atomic datums that
+   can be independently modified.
+  </para>
+ </sect2>
+ <sect2 id="json-keys-elements">
+  <title><type>jsonb</> Input and Output Syntax</title>
+  <para>
+   In effect, <type>jsonb</> has an internal type system whose
+   implementation is defined in terms of several particular ordinary
+   <productname>PostgreSQL</productname> types.  The SQL parser does
+   not have direct knowledge of the internal types that constitute a
+   <type>jsonb</>.
+  </para>
+  <para>
+   The following are all valid <type>jsonb</> expressions:
+  <programlisting>
+-- Simple scalar/primitive value (explicitly required by RFC-7159)
+SELECT '5'::jsonb;
+
+-- Array of heterogeneous, primitive-typed elements
+SELECT '[1, 2, "foo", null]'::jsonb;
+
+-- Object of heterogeneous key/value pairs of primitive types
+-- Note that key values are always strings
+SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
+  </programlisting>
+  </para>
+  <para>
+   Note the distinction between scalar/primitive values as elements,
+   keys and values.
+  </para>
+ </sect2>
+ <sect2 id="json-containment">
+  <title><type>jsonb</> containment</title>
+  <indexterm>
+    <primary>jsonb</primary>
+    <secondary>containment</secondary>
+  </indexterm>
+  <para>
+    Testing <quote>containment</> is an important capability of
+    <type>jsonb</>.  There is no parallel set of facilities for the
+    <type>json</> type.  Containment is the ability to determine if
+    one <type>jsonb</> document has contained within it another one.
+    <type>jsonb</> is nested, and so containment semantics are nested;
+    technically, top-down, unordered <emphasis>subtree isomorphism</>
+    may be tested.  Containment is conventionally tested using the
+    <literal>@&gt;</> operator, which is made indexable by various
+    operator classes discussed later in this section.
+  </para>
+  <programlisting>
+-- Simple scalar/primitive values may contain only each other:
+SELECT '"foo"'::jsonb @> '"foo"'::jsonb;
+
+-- The array on the right hand side is contained within the one on the
+-- left hand side:
+SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb;
+
+-- The object with a single pair on the right hand side is contained
+-- within the object on the left hand side:
+SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb":true}'::jsonb @> '{"version":9.4}'::jsonb;
+
+-- The array on the right hand side is not contained within the array
+-- containing a nested array on the left hand side:
+SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb;
+
+-- But with a layer of nesting, it is:
+SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb;
+  </programlisting>
+  <para>
+    It is both a sufficient and a necessary condition for nesting
+    levels to <quote>line up</> for one <type>jsonb</> to contain
+    within it another.  Under this definition, objects and arrays
+    cannot <quote>line up</>, not least because objects contain
+    key/value pairs, while arrays contain elements.
+  </para>
+  <para>
+    As a special exception to the general principle that nesting
+    levels should <quote>line up</>, an array may contain a raw scalar:
+  </para>
+  <programlisting>
+-- This array contains the raw scalar value:
+SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb;
+-- The special exception is not reciprocated -- non-containment is indicated here:
+SELECT '"bar"'::jsonb @> '["bar"]'::jsonb;
+  </programlisting>
+  <para>
+    Objects are better suited for testing containment when there is a
+    great deal of nesting involved, because unlike arrays they are
+    internally optimized for searching, and do not need to be searched
+    linearly within a single <type>jsonb</> document.
+  </para>
+  <programlisting>
+-- The right-hand side object is contained in this example:
+SELECT '{"p":1, "a":{"b":3, "q":11}, "i":77}'::jsonb @> '{"a":{"b":3}}'::jsonb;
+  </programlisting>
+  <para>
+    The various containment operators, along with all other JSON
+    operators and support functions are documented fully within <xref
+    linkend="functions-json">, <xref
+    linkend="functions-jsonb-op-table">.
+  </para>
+ </sect2>
+ <sect2 id="json-indexing">
+  <title><type>jsonb</> GIN Indexing</title>
+  <indexterm>
+    <primary>jsonb</primary>
+    <secondary>indexes on</secondary>
+  </indexterm>
+  <para>
+    <type>jsonb</> GIN indexes can be used to efficiently search among
+    more than one possible key/value pair within a single
+    <type>jsonb</> datum/document, among a large number of such
+    documents within a column in a table (i.e. among many rows).
+  </para>
+  <para>
+    <type>jsonb</> has GIN index support for the <literal>@&gt;</>,
+    <literal>?</>, <literal>?&amp;</> and <literal>?|</> operators.
+    The default GIN operator class makes all these operators
+    indexable:
+  </para>
+  <programlisting>
+-- GIN index (default opclass)
+CREATE INDEX idxgin ON api USING GIN (jdoc);
+
+-- GIN jsonb_hash_ops index
+CREATE INDEX idxginh ON api USING GIN (jdoc jsonb_hash_ops);
+  </programlisting>
+  <para>
+    The non-default GIN operator class <literal>jsonb_hash_ops</>
+    supports indexing the <literal>@&gt;</> operator only.
+  </para>
+  <para>
+    Consider the example of a table that stores JSON documents
+    retrieved from a third-party web service, with a documented schema
+    definition.  An example of a document retrieved from this web
+    service is as follows:
+    <programlisting>
+{
+    "guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a",
+    "name": "Angela Barton",
+    "is_active": true,
+    "company": "Magnafone",
+    "address": "178 Howard Place, Gulf, Washington, 702",
+    "registered": "2009-11-07T08:53:22 +08:00",
+    "latitude": 19.793713,
+    "longitude": 86.513373,
+    "tags": [
+        "enim",
+        "aliquip",
+        "qui"
+    ]
+}
+    </programlisting>
+    If a GIN index is created on the table that stores these
+    documents, <literal>api</literal>, on its <literal>jdoc</>
+    <type>jsonb</> column, we can expect that queries like the
+    following may make use of the index:
+    <programlisting>
+-- Note that both key and value have been specified
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @&gt; '{"company": "Magnafone"}';
+    </programlisting>
+    However, the index could not be used for queries like the
+    following, due to the aforementioned nesting restriction:
+    <programlisting>
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui';
+    </programlisting>
+    Still, with judicious use of expressional indexing, the above
+    query can use an index scan.  If there is a requirement to find
+    those records with a particular tag quickly, and the tags have a
+    high cardinality across all documents, defining an index as
+    follows is an effective approach to indexing:
+  <programlisting>
+-- Note that the "jsonb -> text" operator can only be called on an
+-- object, so as a consequence of creating this index the root "jdoc"
+-- datum must be an object.  This is enforced during insertion.
+CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags'));
+  </programlisting>
+  </para>
+  <para>
+    Expressional indexes are discussed in <xref
+    linkend="indexes-expressional">.
+  </para>
+  <para>
+    For the most flexible approach in terms of what may be indexed,
+    sophisticated querying on nested structures is possible by
+    exploiting containment.  At the cost of having to create an index
+    on the entire structure for each row, and not just a nested
+    subset, we may exploit containment semantics to get an equivalent
+    result with a non-expressional index on the entire <quote>jdoc</>
+    column, <emphasis>without</> ever having to create additional
+    expressional indexes against the document (provided only
+    containment will be tested).  While the index will be considerably
+    larger than our expression index, it will also be much more
+    flexible, allowing arbitrary structured searching.  Such an index
+    can generally be expected to help with a query like the following:
+  </para>
+  <programlisting>
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @&gt; '{"tags": ["qui"]}';
+  </programlisting>
+  <para>
+   For full details of the semantics that these indexable operators
+   implement, see <xref linkend="functions-json">, <xref
+   linkend="functions-jsonb-op-table">.
+  </para>
+ </sect2>
+ <sect2 id="json-opclass">
+  <title><type>jsonb</> non-default GIN operator class</title>
+  <indexterm>
+    <primary>jsonb</primary>
+    <secondary>indexes on</secondary>
+  </indexterm>
+  <para>
+    Although only the <literal>@&gt;</> operator is made indexable, a
+    <literal>jsonb_hash_ops</literal> operator class GIN index has
+    some notable advantages over an equivalent GIN index of the
+    default GIN operator class for <type>jsonb</type>.  Search
+    operations typically perform considerably better, and the on-disk
+    size of a <literal>jsonb_hash_ops</literal> operator class GIN
+    index can be much smaller.
+  </para>
+ </sect2>
+ <sect2 id="json-btree-indexing">
+  <title><type>jsonb</> B-Tree and hash indexing</title>
+  <para>
+    <type>jsonb</type> comparisons and related operations are
+    <emphasis>type-wise</>, in that the underlying
+    <productname>PostgreSQL</productname> datatype comparators are
+    invoked recursively, much like a traditional composite type.
+  </para>
+  <para>
+    <type>jsonb</> also supports <type>btree</> and <type>hash</>
+    indexes.  Ordering between <type>jsonb</> datums is:
+    <synopsis>
+      <replaceable>Object</replaceable> > <replaceable>Array</replaceable> > <replaceable>Boolean</replaceable> > <replaceable>Number</replaceable> > <replaceable>String</replaceable> > <replaceable>Null</replaceable>
+
+      <replaceable>Object with n pairs</replaceable> > <replaceable>object with n - 1 pairs</replaceable>
+
+      <replaceable>Array with n elements</replaceable> > <replaceable>array with n - 1 elements</replaceable>
+    </synopsis>
+      Subsequently, individual primitive type comparators are invoked.
+      All comparisons of JSON primitive types occurs using the same
+      comparison rules as the underlying
+      <productname>PostgreSQL</productname> types.  Strings are
+      compared lexically, using the default database collation.
+      Objects with equal numbers of pairs are compared:
+    <synopsis>
+      <replaceable>key-1</replaceable>, <replaceable>value-1</replaceable>, <replaceable>key-2</replaceable> ...
+    </synopsis>
+      Note however that object keys are compared in their storage order, and in particular,
+      since shorter keys are stored before longer keys, this can lead to results that might be
+      unintuitive, such as:
+      <programlisting>{ "aa": 1, "c": 1} > {"b": 1, "d": 1}</programlisting>
+      Similarly, arrays with equal numbers of elements are compared:
+    <synopsis>
+      <replaceable>element-1</replaceable>, <replaceable>element-2</replaceable> ...
+    </synopsis>
+  </para>
+ </sect2>
+</sect1>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 053d7585ca45fa128cd78cc8f5866b0086c86969..662040261e968dbc1846c12f7a9439cc792879cd 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -825,6 +825,14 @@ CREATE OR REPLACE FUNCTION
   json_populate_recordset(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
   RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100  AS 'json_populate_recordset';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_populate_record(base anyelement, from_json jsonb, use_json_as_text boolean DEFAULT false)
+  RETURNS anyelement LANGUAGE internal STABLE AS 'jsonb_populate_record';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_populate_recordset(base anyelement, from_json jsonb, use_json_as_text boolean DEFAULT false)
+  RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100  AS 'jsonb_populate_recordset';
+
 CREATE OR REPLACE FUNCTION pg_logical_slot_get_changes(
     IN slotname name, IN upto_lsn pg_lsn, IN upto_nchanges int, VARIADIC options text[] DEFAULT '{}',
     OUT location pg_lsn, OUT xid xid, OUT data text)
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 644687954b29913861bb707aa68e114c021d84ac..6b23069e26c36ce1303adcc828df9caeb88eb09a 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -21,11 +21,11 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
 	encode.o enum.o float.o format_type.o formatting.o genfile.o \
 	geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
-	int8.o json.o jsonfuncs.o like.o \
-	lockfuncs.o mac.o misc.o nabstime.o name.o network.o numeric.o \
-	numutils.o oid.o oracle_compat.o orderedsetaggs.o \
-	pg_lzcompress.o pg_locale.o pg_lsn.o pgstatfuncs.o \
-	pseudotypes.o quote.o rangetypes.o rangetypes_gist.o \
+	int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
+	jsonfuncs.o like.o lockfuncs.o mac.o misc.o nabstime.o name.o \
+	network.o numeric.o numutils.o oid.o oracle_compat.o \
+	orderedsetaggs.o pg_lzcompress.o pg_locale.o pg_lsn.o \
+	pgstatfuncs.o pseudotypes.o quote.o rangetypes.o rangetypes_gist.o \
 	rangetypes_selfuncs.o rangetypes_spgist.o rangetypes_typanalyze.o \
 	regexp.o regproc.o ri_triggers.o rowtypes.o ruleutils.o \
 	selfuncs.o tid.o timestamp.o trigfuncs.o \
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 97a0e9f211e52ca796c6ba6ebce5f88b3965e025..c34a1bb50be0b43ea5491027365b54c5802fb0fb 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -210,22 +210,17 @@ Datum
 json_recv(PG_FUNCTION_ARGS)
 {
 	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
-	text	   *result;
 	char	   *str;
 	int			nbytes;
 	JsonLexContext *lex;
 
 	str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
 
-	result = palloc(nbytes + VARHDRSZ);
-	SET_VARSIZE(result, nbytes + VARHDRSZ);
-	memcpy(VARDATA(result), str, nbytes);
-
 	/* Validate it. */
-	lex = makeJsonLexContext(result, false);
+	lex = makeJsonLexContextCstringLen(str, nbytes, false);
 	pg_parse_json(lex, &nullSemAction);
 
-	PG_RETURN_TEXT_P(result);
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes));
 }
 
 /*
@@ -236,15 +231,26 @@ json_recv(PG_FUNCTION_ARGS)
  *
  * Without is better as it makes the processing faster, so only make one
  * if really required.
+ *
+ * If you already have the json as a text* value, use the first of these
+ * functions, otherwise use  makeJsonLexContextCstringLen().
  */
 JsonLexContext *
 makeJsonLexContext(text *json, bool need_escapes)
+{
+	return makeJsonLexContextCstringLen(VARDATA(json),
+										VARSIZE(json) - VARHDRSZ,
+										need_escapes);
+}
+
+JsonLexContext *
+makeJsonLexContextCstringLen(char *json, int len, bool need_escapes)
 {
 	JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
 
-	lex->input = lex->token_terminator = lex->line_start = VARDATA(json);
+	lex->input = lex->token_terminator = lex->line_start = json;
 	lex->line_number = 1;
-	lex->input_length = VARSIZE(json) - VARHDRSZ;
+	lex->input_length = len;
 	if (need_escapes)
 		lex->strval = makeStringInfo();
 	return lex;
@@ -1274,7 +1280,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			pfree(outputstr);
 			break;
 		case TYPCATEGORY_JSON:
-			/* JSON will already be escaped */
+			/* JSON and JSONB will already be escaped */
 			outputstr = OidOutputFunctionCall(typoutputfunc, val);
 			appendStringInfoString(result, outputstr);
 			pfree(outputstr);
@@ -1406,7 +1412,7 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
 		tcategory = TYPCATEGORY_JSON_CAST;
 	else if (element_type == RECORDOID)
 		tcategory = TYPCATEGORY_COMPOSITE;
-	else if (element_type == JSONOID)
+	else if (element_type == JSONOID || element_type == JSONBOID)
 		tcategory = TYPCATEGORY_JSON;
 	else
 		tcategory = TypeCategory(element_type);
@@ -1501,7 +1507,8 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
 			tcategory = TYPCATEGORY_ARRAY;
 		else if (tupdesc->attrs[i]->atttypid == RECORDOID)
 			tcategory = TYPCATEGORY_COMPOSITE;
-		else if (tupdesc->attrs[i]->atttypid == JSONOID)
+		else if (tupdesc->attrs[i]->atttypid == JSONOID ||
+				 tupdesc->attrs[i]->atttypid == JSONBOID)
 			tcategory = TYPCATEGORY_JSON;
 		else
 			tcategory = TypeCategory(tupdesc->attrs[i]->atttypid);
@@ -1689,7 +1696,7 @@ to_json(PG_FUNCTION_ARGS)
 		tcategory = TYPCATEGORY_ARRAY;
 	else if (val_type == RECORDOID)
 		tcategory = TYPCATEGORY_COMPOSITE;
-	else if (val_type == JSONOID)
+	else if (val_type == JSONOID || val_type == JSONBOID)
 		tcategory = TYPCATEGORY_JSON;
 	else
 		tcategory = TypeCategory(val_type);
@@ -1783,7 +1790,7 @@ json_agg_transfn(PG_FUNCTION_ARGS)
 		tcategory = TYPCATEGORY_ARRAY;
 	else if (val_type == RECORDOID)
 		tcategory = TYPCATEGORY_COMPOSITE;
-	else if (val_type == JSONOID)
+	else if (val_type == JSONOID || val_type == JSONBOID)
 		tcategory = TYPCATEGORY_JSON;
 	else
 		tcategory = TypeCategory(val_type);
@@ -2346,12 +2353,15 @@ escape_json(StringInfo buf, const char *str)
 Datum
 json_typeof(PG_FUNCTION_ARGS)
 {
-	text	   *json = PG_GETARG_TEXT_P(0);
+	text	   *json;
 
-	JsonLexContext *lex = makeJsonLexContext(json, false);
+	JsonLexContext *lex;
 	JsonTokenType tok;
 	char	   *type;
 
+	json = PG_GETARG_TEXT_P(0);
+	lex = makeJsonLexContext(json, false);
+
 	/* Lex exactly one token from the input and check its type. */
 	json_lex(lex);
 	tok = lex_peek(lex);
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
new file mode 100644
index 0000000000000000000000000000000000000000..b30e79e425bedefce631fafe108f66ae7b45b010
--- /dev/null
+++ b/src/backend/utils/adt/jsonb.c
@@ -0,0 +1,468 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb.c
+ *		I/O routines for jsonb type
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/jsonb.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "libpq/pqformat.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/jsonapi.h"
+#include "utils/jsonb.h"
+
+typedef struct JsonbInState
+{
+	JsonbParseState *parseState;
+	JsonbValue *res;
+}	JsonbInState;
+
+static inline Datum jsonb_from_cstring(char *json, int len);
+static size_t checkStringLen(size_t len);
+static void jsonb_in_object_start(void *pstate);
+static void jsonb_in_object_end(void *pstate);
+static void jsonb_in_array_start(void *pstate);
+static void jsonb_in_array_end(void *pstate);
+static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull);
+static void jsonb_put_escaped_value(StringInfo out, JsonbValue * scalarVal);
+static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype);
+char *JsonbToCString(StringInfo out, char *in, int estimated_len);
+
+/*
+ * jsonb type input function
+ */
+Datum
+jsonb_in(PG_FUNCTION_ARGS)
+{
+	char	   *json = PG_GETARG_CSTRING(0);
+
+	return jsonb_from_cstring(json, strlen(json));
+}
+
+/*
+ * jsonb type recv function
+ *
+ * The type is sent as text in binary mode, so this is almost the same
+ * as the input function, but it's prefixed with a version number so we
+ * can change the binary format sent in future if necessary. For now,
+ * only version 1 is supported.
+ */
+Datum
+jsonb_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	int			version = pq_getmsgint(buf, 1);
+	char	   *str;
+	int			nbytes;
+
+	if (version == 1)
+		str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
+	else
+		elog(ERROR, "Unsupported jsonb version number %d", version);
+
+	return jsonb_from_cstring(str, nbytes);
+}
+
+/*
+ * jsonb type output function
+ */
+Datum
+jsonb_out(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	char	   *out;
+
+	out = JsonbToCString(NULL, VARDATA(jb), VARSIZE(jb));
+
+	PG_RETURN_CSTRING(out);
+}
+
+/*
+ * jsonb type send function
+ *
+ * Just send jsonb as a version number, then a string of text
+ */
+Datum
+jsonb_send(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	StringInfoData buf;
+	StringInfo	jtext = makeStringInfo();
+	int			version = 1;
+
+	(void) JsonbToCString(jtext, VARDATA(jb), VARSIZE(jb));
+
+	pq_begintypsend(&buf);
+	pq_sendint(&buf, version, 1);
+	pq_sendtext(&buf, jtext->data, jtext->len);
+	pfree(jtext->data);
+	pfree(jtext);
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+/*
+ * SQL function jsonb_typeof(jsonb) -> text
+ *
+ * This function is here because the analog json function is in json.c, since
+ * it uses the json parser internals not exposed elsewhere.
+ */
+Datum
+jsonb_typeof(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *in = PG_GETARG_JSONB(0);
+	JsonbIterator *it;
+	JsonbValue	v;
+	char	   *result;
+
+	if (JB_ROOT_IS_OBJECT(in))
+		result = "object";
+	else if (JB_ROOT_IS_ARRAY(in) && !JB_ROOT_IS_SCALAR(in))
+		result = "array";
+	else
+	{
+		Assert(JB_ROOT_IS_SCALAR(in));
+
+		it = JsonbIteratorInit(VARDATA_ANY(in));
+
+		/*
+		 * A root scalar is stored as an array of one element, so we get the
+		 * array and then its first (and only) member.
+		 */
+		(void) JsonbIteratorNext(&it, &v, true);
+		Assert(v.type == jbvArray);
+		(void) JsonbIteratorNext(&it, &v, true);
+		switch (v.type)
+		{
+			case jbvNull:
+				result = "null";
+				break;
+			case jbvString:
+				result = "string";
+				break;
+			case jbvNumeric:
+				result = "number";
+				break;
+			case jbvBool:
+				result = "boolean";
+				break;
+			default:
+				elog(ERROR, "unknown jsonb scalar type");
+		}
+	}
+
+	PG_RETURN_TEXT_P(cstring_to_text(result));
+}
+
+/*
+ * jsonb_from_cstring
+ *
+ * Turns json string into a jsonb Datum.
+ *
+ * Uses the json parser (with hooks) to construct a jsonb.
+ */
+static inline Datum
+jsonb_from_cstring(char *json, int len)
+{
+	JsonLexContext *lex;
+	JsonbInState state;
+	JsonSemAction sem;
+
+	memset(&state, 0, sizeof(state));
+	memset(&sem, 0, sizeof(sem));
+	lex = makeJsonLexContextCstringLen(json, len, true);
+
+	sem.semstate = (void *) &state;
+
+	sem.object_start = jsonb_in_object_start;
+	sem.array_start = jsonb_in_array_start;
+	sem.object_end = jsonb_in_object_end;
+	sem.array_end = jsonb_in_array_end;
+	sem.scalar = jsonb_in_scalar;
+	sem.object_field_start = jsonb_in_object_field_start;
+
+	pg_parse_json(lex, &sem);
+
+	/* after parsing, the item member has the composed jsonb structure */
+	PG_RETURN_POINTER(JsonbValueToJsonb(state.res));
+}
+
+static size_t
+checkStringLen(size_t len)
+{
+	if (len > JENTRY_POSMASK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("string too long to represent as jsonb string"),
+				 errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.",
+						   JENTRY_POSMASK)));
+
+	return len;
+}
+
+static void
+jsonb_in_object_start(void *pstate)
+{
+	JsonbInState *_state = (JsonbInState *) pstate;
+
+	_state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL);
+}
+
+static void
+jsonb_in_object_end(void *pstate)
+{
+	JsonbInState *_state = (JsonbInState *) pstate;
+
+	_state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL);
+}
+
+static void
+jsonb_in_array_start(void *pstate)
+{
+	JsonbInState *_state = (JsonbInState *) pstate;
+
+	_state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, NULL);
+}
+
+static void
+jsonb_in_array_end(void *pstate)
+{
+	JsonbInState *_state = (JsonbInState *) pstate;
+
+	_state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL);
+}
+
+static void
+jsonb_in_object_field_start(void *pstate, char *fname, bool isnull)
+{
+	JsonbInState *_state = (JsonbInState *) pstate;
+	JsonbValue	v;
+
+	Assert (fname != NULL);
+	v.type = jbvString;
+	v.string.len = checkStringLen(strlen(fname));
+	v.string.val = pnstrdup(fname, v.string.len);
+	v.estSize = sizeof(JEntry) + v.string.len;
+
+	_state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v);
+}
+
+static void
+jsonb_put_escaped_value(StringInfo out, JsonbValue * scalarVal)
+{
+	switch (scalarVal->type)
+	{
+		case jbvNull:
+			appendBinaryStringInfo(out, "null", 4);
+			break;
+		case jbvString:
+			escape_json(out, pnstrdup(scalarVal->string.val, scalarVal->string.len));
+			break;
+		case jbvNumeric:
+			appendStringInfoString(out,
+								   DatumGetCString(DirectFunctionCall1(numeric_out,
+																	   PointerGetDatum(scalarVal->numeric))));
+			break;
+		case jbvBool:
+			if (scalarVal->boolean)
+				appendBinaryStringInfo(out, "true", 4);
+			else
+				appendBinaryStringInfo(out, "false", 5);
+			break;
+		default:
+			elog(ERROR, "unknown jsonb scalar type");
+	}
+}
+
+/*
+ * For jsonb we always want the de-escaped value - that's what's in token
+ */
+static void
+jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype)
+{
+	JsonbInState *_state = (JsonbInState *) pstate;
+	JsonbValue	v;
+
+	v.estSize = sizeof(JEntry);
+
+	switch (tokentype)
+	{
+
+		case JSON_TOKEN_STRING:
+			Assert (token != NULL);
+			v.type = jbvString;
+			v.string.len = checkStringLen(strlen(token));
+			v.string.val = pnstrdup(token, v.string.len);
+			v.estSize += v.string.len;
+			break;
+		case JSON_TOKEN_NUMBER:
+			/*
+			 * No need to check size of numeric values, because maximum numeric
+			 * size is well below the JsonbValue restriction
+			 */
+			Assert (token != NULL);
+			v.type = jbvNumeric;
+			v.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(token), 0, -1));
+			v.estSize += VARSIZE_ANY(v.numeric) + sizeof(JEntry) /* alignment */ ;
+			break;
+		case JSON_TOKEN_TRUE:
+			v.type = jbvBool;
+			v.boolean = true;
+			break;
+		case JSON_TOKEN_FALSE:
+			v.type = jbvBool;
+			v.boolean = false;
+			break;
+		case JSON_TOKEN_NULL:
+			v.type = jbvNull;
+			break;
+		default:
+			/* should not be possible */
+			elog(ERROR, "invalid json token type");
+			break;
+	}
+
+	if (_state->parseState == NULL)
+	{
+		/* single scalar */
+		JsonbValue	va;
+
+		va.type = jbvArray;
+		va.array.rawScalar = true;
+		va.array.nElems = 1;
+
+		_state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, &va);
+		_state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v);
+		_state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL);
+	}
+	else
+	{
+		JsonbValue *o = &_state->parseState->contVal;
+
+		switch (o->type)
+		{
+			case jbvArray:
+				_state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v);
+				break;
+			case jbvObject:
+				_state->res = pushJsonbValue(&_state->parseState, WJB_VALUE, &v);
+				break;
+			default:
+				elog(ERROR, "unexpected parent of nested structure");
+		}
+	}
+}
+
+/*
+ * JsonbToCString
+ *	   Converts jsonb value to a C-string.
+ *
+ * If 'out' argument is non-null, the resulting C-string is stored inside the
+ * StringBuffer.  The resulting string is always returned.
+ *
+ * A typical case for passing the StringInfo in rather than NULL is where the
+ * caller wants access to the len attribute without having to call strlen, e.g.
+ * if they are converting it to a text* object.
+ */
+char *
+JsonbToCString(StringInfo out, JsonbSuperHeader in, int estimated_len)
+{
+	bool		first = true;
+	JsonbIterator *it;
+	int			type = 0;
+	JsonbValue	v;
+	int			level = 0;
+	bool		redo_switch = false;
+
+	if (out == NULL)
+		out = makeStringInfo();
+
+	enlargeStringInfo(out, (estimated_len >= 0) ? estimated_len : 64);
+
+	it = JsonbIteratorInit(in);
+
+	while (redo_switch ||
+		   ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE))
+	{
+		redo_switch = false;
+		switch (type)
+		{
+			case WJB_BEGIN_ARRAY:
+				if (!first)
+					appendBinaryStringInfo(out, ", ", 2);
+				first = true;
+
+				if (!v.array.rawScalar)
+					appendStringInfoChar(out, '[');
+				level++;
+				break;
+			case WJB_BEGIN_OBJECT:
+				if (!first)
+					appendBinaryStringInfo(out, ", ", 2);
+				first = true;
+				appendStringInfoCharMacro(out, '{');
+
+				level++;
+				break;
+			case WJB_KEY:
+				if (!first)
+					appendBinaryStringInfo(out, ", ", 2);
+				first = true;
+
+				/* json rules guarantee this is a string */
+				jsonb_put_escaped_value(out, &v);
+				appendBinaryStringInfo(out, ": ", 2);
+
+				type = JsonbIteratorNext(&it, &v, false);
+				if (type == WJB_VALUE)
+				{
+					first = false;
+					jsonb_put_escaped_value(out, &v);
+				}
+				else
+				{
+					Assert(type == WJB_BEGIN_OBJECT || type == WJB_BEGIN_ARRAY);
+
+					/*
+					 * We need to rerun the current switch() since we need to
+					 * output the object which we just got from the iterator
+					 * before calling the iterator again.
+					 */
+					redo_switch = true;
+				}
+				break;
+			case WJB_ELEM:
+				if (!first)
+					appendBinaryStringInfo(out, ", ", 2);
+				else
+					first = false;
+
+				jsonb_put_escaped_value(out, &v);
+				break;
+			case WJB_END_ARRAY:
+				level--;
+				if (!v.array.rawScalar)
+					appendStringInfoChar(out, ']');
+				first = false;
+				break;
+			case WJB_END_OBJECT:
+				level--;
+				appendStringInfoCharMacro(out, '}');
+				first = false;
+				break;
+			default:
+				elog(ERROR, "unknown flag of jsonb iterator");
+		}
+	}
+
+	Assert(level == 0);
+
+	return out->data;
+}
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
new file mode 100644
index 0000000000000000000000000000000000000000..4a6b8fd68887b8d42bdc9287434c020626c98514
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_gin.c
@@ -0,0 +1,646 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_gin.c
+ *	 GIN support functions for jsonb
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/jsonb_gin.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/gin.h"
+#include "access/skey.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+typedef struct PathHashStack
+{
+	uint32	hash;
+	struct PathHashStack *parent;
+}	PathHashStack;
+
+static text *make_text_key(const char *str, int len, char flag);
+static text *make_scalar_key(const JsonbValue * scalarVal, char flag);
+
+/*
+ *
+ * jsonb_ops GIN opclass support functions
+ *
+ */
+Datum
+gin_compare_jsonb(PG_FUNCTION_ARGS)
+{
+	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+	int32		result;
+	char	   *a1p,
+			   *a2p;
+	int			len1,
+				len2;
+
+	a1p = VARDATA_ANY(arg1);
+	a2p = VARDATA_ANY(arg2);
+
+	len1 = VARSIZE_ANY_EXHDR(arg1);
+	len2 = VARSIZE_ANY_EXHDR(arg2);
+
+	/* Compare text as bttextcmp does, but always using C collation */
+	result = varstr_cmp(a1p, len1, a2p, len2, C_COLLATION_OID);
+
+	PG_FREE_IF_COPY(arg1, 0);
+	PG_FREE_IF_COPY(arg2, 1);
+
+	PG_RETURN_INT32(result);
+}
+
+Datum
+gin_extract_jsonb(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = (Jsonb *) PG_GETARG_JSONB(0);
+	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+	Datum	   *entries = NULL;
+	int			total = 2 * JB_ROOT_COUNT(jb);
+	int			i = 0,
+				r;
+	JsonbIterator *it;
+	JsonbValue	v;
+
+	if (total == 0)
+	{
+		*nentries = 0;
+		PG_RETURN_POINTER(NULL);
+	}
+
+	entries = (Datum *) palloc(sizeof(Datum) * total);
+
+	it = JsonbIteratorInit(VARDATA(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+	{
+		if (i >= total)
+		{
+			total *= 2;
+			entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
+		}
+
+		/*
+		 * Serialize keys and elements equivalently,  but only when elements
+		 * are Jsonb strings.  Otherwise, serialize elements as values.  Array
+		 * elements are indexed as keys, for the benefit of
+		 * JsonbExistsStrategyNumber.  Our definition of existence does not
+		 * allow for checking the existence of a non-jbvString element (just
+		 * like the definition of the underlying operator), because the
+		 * operator takes a text rhs argument (which is taken as a proxy for an
+		 * equivalent Jsonb string).
+		 *
+		 * The way existence is represented does not preclude an alternative
+		 * existence operator, that takes as its rhs value an arbitrarily
+		 * internally-typed Jsonb.  The only reason that isn't the case here is
+		 * that the existence operator is only really intended to determine if
+		 * an object has a certain key (object pair keys are of course
+		 * invariably strings), which is extended to jsonb arrays.  You could
+		 * think of the default Jsonb definition of existence as being
+		 * equivalent to a definition where all types of scalar array elements
+		 * are keys that we can check the existence of, while just forbidding
+		 * non-string notation.  This inflexibility prevents the user from
+		 * having to qualify that the rhs string is a raw scalar string (that
+		 * is, naturally no internal string quoting in required for the text
+		 * argument), and allows us to not set the reset flag for
+		 * JsonbExistsStrategyNumber, since we know that keys are strings for
+		 * both objects and arrays, and don't have to further account for type
+		 * mismatch.  Not having to set the reset flag makes it less than
+		 * tempting to tighten up the definition of existence to preclude array
+		 * elements entirely, which would arguably be a simpler alternative.
+		 * In any case the infrastructure used to implement the existence
+		 * operator could trivially support this hypothetical, slightly
+		 * distinct definition of existence.
+		 */
+		switch (r)
+		{
+			case WJB_KEY:
+				/* Serialize key separately, for existence strategies */
+				entries[i++] = PointerGetDatum(make_scalar_key(&v, JKEYELEM));
+				break;
+			case WJB_ELEM:
+				if (v.type == jbvString)
+					entries[i++] = PointerGetDatum(make_scalar_key(&v, JKEYELEM));
+				else
+					entries[i++] = PointerGetDatum(make_scalar_key(&v, JVAL));
+				break;
+			case WJB_VALUE:
+				entries[i++] = PointerGetDatum(make_scalar_key(&v, JVAL));
+				break;
+			default:
+				continue;
+		}
+	}
+
+	*nentries = i;
+
+	PG_RETURN_POINTER(entries);
+}
+
+Datum
+gin_extract_jsonb_query(PG_FUNCTION_ARGS)
+{
+	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+	StrategyNumber strategy = PG_GETARG_UINT16(2);
+	int32	   *searchMode = (int32 *) PG_GETARG_POINTER(6);
+	Datum	   *entries;
+
+	if (strategy == JsonbContainsStrategyNumber)
+	{
+		/* Query is a jsonb, so just apply gin_extract_jsonb... */
+		entries = (Datum *)
+			DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb,
+												PG_GETARG_DATUM(0),
+												PointerGetDatum(nentries)));
+		/* ...although "contains {}" requires a full index scan */
+		if (entries == NULL)
+			*searchMode = GIN_SEARCH_MODE_ALL;
+	}
+	else if (strategy == JsonbExistsStrategyNumber)
+	{
+		text	   *query = PG_GETARG_TEXT_PP(0);
+		text	   *item;
+
+		*nentries = 1;
+		entries = (Datum *) palloc(sizeof(Datum));
+		item = make_text_key(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query),
+							 JKEYELEM);
+		entries[0] = PointerGetDatum(item);
+	}
+	else if (strategy == JsonbExistsAnyStrategyNumber ||
+			 strategy == JsonbExistsAllStrategyNumber)
+	{
+		ArrayType  *query = PG_GETARG_ARRAYTYPE_P(0);
+		Datum	   *key_datums;
+		bool	   *key_nulls;
+		int			key_count;
+		int			i,
+					j;
+		text	   *item;
+
+		deconstruct_array(query,
+						  TEXTOID, -1, false, 'i',
+						  &key_datums, &key_nulls, &key_count);
+
+		entries = (Datum *) palloc(sizeof(Datum) * key_count);
+
+		for (i = 0, j = 0; i < key_count; ++i)
+		{
+			/* Nulls in the array are ignored */
+			if (key_nulls[i])
+				continue;
+			item = make_text_key(VARDATA(key_datums[i]),
+								 VARSIZE(key_datums[i]) - VARHDRSZ,
+								 JKEYELEM);
+			entries[j++] = PointerGetDatum(item);
+		}
+
+		*nentries = j;
+		/* ExistsAll with no keys should match everything */
+		if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
+			*searchMode = GIN_SEARCH_MODE_ALL;
+	}
+	else
+	{
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+		entries = NULL;			/* keep compiler quiet */
+	}
+
+	PG_RETURN_POINTER(entries);
+}
+
+Datum
+gin_consistent_jsonb(PG_FUNCTION_ARGS)
+{
+	bool	   *check = (bool *) PG_GETARG_POINTER(0);
+	StrategyNumber strategy = PG_GETARG_UINT16(1);
+
+	/* Jsonb	   *query = PG_GETARG_JSONB(2); */
+	int32		nkeys = PG_GETARG_INT32(3);
+
+	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	bool	   *recheck = (bool *) PG_GETARG_POINTER(5);
+	bool		res = true;
+	int32		i;
+
+	if (strategy == JsonbContainsStrategyNumber)
+	{
+		/*
+		 * Index doesn't have information about correspondence of Jsonb keys
+		 * and values (as distinct from GIN keys, which a key/value pair is
+		 * stored as), so invariably we recheck.  Besides, there are some
+		 * special rules around the containment of raw scalar arrays and
+		 * regular arrays that are not represented here.  However, if all of
+		 * the keys are not present, that's sufficient reason to return false
+		 * and finish immediately.
+		 */
+		*recheck = true;
+		for (i = 0; i < nkeys; i++)
+		{
+			if (!check[i])
+			{
+				res = false;
+				break;
+			}
+		}
+	}
+	else if (strategy == JsonbExistsStrategyNumber)
+	{
+		/* Existence of key guaranteed in default search mode */
+		*recheck = false;
+		res = true;
+	}
+	else if (strategy == JsonbExistsAnyStrategyNumber)
+	{
+		/* Existence of key guaranteed in default search mode */
+		*recheck = false;
+		res = true;
+	}
+	else if (strategy == JsonbExistsAllStrategyNumber)
+	{
+		/* Testing for the presence of all keys gives an exact result */
+		*recheck = false;
+		for (i = 0; i < nkeys; i++)
+		{
+			if (!check[i])
+			{
+				res = false;
+				break;
+			}
+		}
+	}
+	else
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
+{
+	GinLogicValue   *check = (GinLogicValue *) PG_GETARG_POINTER(0);
+	StrategyNumber strategy = PG_GETARG_UINT16(1);
+	/* Jsonb	   *query = PG_GETARG_JSONB(2); */
+	int32		nkeys = PG_GETARG_INT32(3);
+	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	GinLogicValue	res = GIN_TRUE;
+
+	int32		i;
+
+	if (strategy == JsonbContainsStrategyNumber)
+	{
+		bool	has_maybe = false;
+
+		/*
+		 * All extracted keys must be present.  Combination of GIN_MAYBE and
+		 * GIN_TRUE gives GIN_MAYBE result because then all keys may be
+		 * present.
+		 */
+		for (i = 0; i < nkeys; i++)
+		{
+			if (check[i] == GIN_FALSE)
+			{
+				res = GIN_FALSE;
+				break;
+			}
+			if (check[i] == GIN_MAYBE)
+			{
+				res = GIN_MAYBE;
+				has_maybe = true;
+			}
+		}
+
+		/*
+		 * Index doesn't have information about correspondence of Jsonb keys
+		 * and values (as distinct from GIN keys, which a key/value pair is
+		 * stored as), so invariably we recheck.  This is also reflected in how
+		 * GIN_MAYBE is given in response to there being no GIN_MAYBE input.
+		 */
+		if (!has_maybe && res == GIN_TRUE)
+			res = GIN_MAYBE;
+	}
+	else if (strategy == JsonbExistsStrategyNumber ||
+			 strategy == JsonbExistsAnyStrategyNumber)
+	{
+		/* Existence of key guaranteed in default search mode */
+		res = GIN_FALSE;
+		for (i = 0; i < nkeys; i++)
+		{
+			if (check[i] == GIN_TRUE)
+			{
+				res = GIN_TRUE;
+				break;
+			}
+			if (check[i] == GIN_MAYBE)
+			{
+				res = GIN_MAYBE;
+			}
+		}
+	}
+	else if (strategy == JsonbExistsAllStrategyNumber)
+	{
+		/* Testing for the presence of all keys gives an exact result */
+		for (i = 0; i < nkeys; i++)
+		{
+			if (check[i] == GIN_FALSE)
+			{
+				res = GIN_FALSE;
+				break;
+			}
+			if (check[i] == GIN_MAYBE)
+			{
+				res = GIN_MAYBE;
+			}
+		}
+	}
+	else
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+	PG_RETURN_GIN_LOGIC_VALUE(res);
+}
+
+/*
+ *
+ * jsonb_hash_ops GIN opclass support functions
+ *
+ */
+Datum
+gin_consistent_jsonb_hash(PG_FUNCTION_ARGS)
+{
+	bool	   *check = (bool *) PG_GETARG_POINTER(0);
+	StrategyNumber strategy = PG_GETARG_UINT16(1);
+	/* Jsonb	   *query = PG_GETARG_JSONB(2); */
+	int32		nkeys = PG_GETARG_INT32(3);
+	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	bool	   *recheck = (bool *) PG_GETARG_POINTER(5);
+	bool		res = true;
+	int32		i;
+
+	if (strategy != JsonbContainsStrategyNumber)
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+	/*
+	 * jsonb_hash_ops index doesn't have information about correspondence
+	 * of Jsonb keys and values (as distinct from GIN keys, which a
+	 * key/value pair is stored as), so invariably we recheck.  Besides,
+	 * there are some special rules around the containment of raw scalar
+	 * arrays and regular arrays that are not represented here.  However,
+	 * if all of the keys are not present, that's sufficient reason to
+	 * return false and finish immediately.
+	 */
+	*recheck = true;
+	for (i = 0; i < nkeys; i++)
+	{
+		if (!check[i])
+		{
+			res = false;
+			break;
+		}
+	}
+
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+gin_triconsistent_jsonb_hash(PG_FUNCTION_ARGS)
+{
+	GinLogicValue   *check = (GinLogicValue *) PG_GETARG_POINTER(0);
+	StrategyNumber strategy = PG_GETARG_UINT16(1);
+	/* Jsonb	   *query = PG_GETARG_JSONB(2); */
+	int32		nkeys = PG_GETARG_INT32(3);
+	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	GinLogicValue	res = GIN_TRUE;
+	int32			i;
+	bool			has_maybe = false;
+
+	if (strategy != JsonbContainsStrategyNumber)
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+	/*
+	 * All extracted keys must be present.  A combination of GIN_MAYBE and
+	 * GIN_TRUE induces a GIN_MAYBE result, because then all keys may be
+	 * present.
+	 */
+	for (i = 0; i < nkeys; i++)
+	{
+		if (check[i] == GIN_FALSE)
+		{
+			res = GIN_FALSE;
+			break;
+		}
+		if (check[i] == GIN_MAYBE)
+		{
+			res = GIN_MAYBE;
+			has_maybe = true;
+		}
+	}
+
+	/*
+	 * jsonb_hash_ops index doesn't have information about correspondence of
+	 * Jsonb keys and values (as distinct from GIN keys, which for this opclass
+	 * are a hash of a pair, or a hash of just an element), so invariably we
+	 * recheck.  This is also reflected in how GIN_MAYBE is given in response
+	 * to there being no GIN_MAYBE input.
+	 */
+	if (!has_maybe && res == GIN_TRUE)
+		res = GIN_MAYBE;
+
+	PG_RETURN_GIN_LOGIC_VALUE(res);
+}
+
+Datum
+gin_extract_jsonb_hash(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+	int			total = 2 * JB_ROOT_COUNT(jb);
+	JsonbIterator *it;
+	JsonbValue	v;
+	PathHashStack tail;
+	PathHashStack *stack;
+	int			i = 0,
+				r;
+	Datum	   *entries = NULL;
+
+	if (total == 0)
+	{
+		*nentries = 0;
+		PG_RETURN_POINTER(NULL);
+	}
+
+	entries = (Datum *) palloc(sizeof(Datum) * total);
+
+	it = JsonbIteratorInit(VARDATA(jb));
+
+	tail.parent = NULL;
+	tail.hash = 0;
+	stack = &tail;
+
+	while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+	{
+		PathHashStack  *tmp;
+
+		if (i >= total)
+		{
+			total *= 2;
+			entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
+		}
+
+		switch (r)
+		{
+			case WJB_BEGIN_ARRAY:
+			case WJB_BEGIN_OBJECT:
+				tmp = stack;
+				stack = (PathHashStack *) palloc(sizeof(PathHashStack));
+
+				/*
+				 * Nesting an array within another array will not alter
+				 * innermost scalar element hash values, but that seems
+				 * inconsequential
+				 */
+				if (tmp->parent)
+				{
+					/*
+					 * We pass forward hashes from previous container nesting
+					 * levels so that nested arrays with an outermost nested
+					 * object will have element hashes mixed with the outermost
+					 * key.  It's also somewhat useful to have nested objects
+					 * innermost values have hashes that are a function of not
+					 * just their own key, but outer keys too.
+					 */
+					stack->hash = tmp->hash;
+				}
+				else
+				{
+					/*
+					 * At least nested level, initialize with stable container
+					 * type proxy value
+					 */
+					stack->hash = (r == WJB_BEGIN_ARRAY)? JB_FARRAY:JB_FOBJECT;
+				}
+				stack->parent = tmp;
+				break;
+			case WJB_KEY:
+				/* Initialize hash from parent */
+				stack->hash = stack->parent->hash;
+				JsonbHashScalarValue(&v, &stack->hash);
+				break;
+			case WJB_ELEM:
+				/* Elements have parent hash mixed in separately */
+				stack->hash = stack->parent->hash;
+			case WJB_VALUE:
+				/* Element/value case */
+				JsonbHashScalarValue(&v, &stack->hash);
+				entries[i++] = stack->hash;
+				break;
+			case WJB_END_ARRAY:
+			case WJB_END_OBJECT:
+				/* Pop the stack */
+				tmp = stack->parent;
+				pfree(stack);
+				stack = tmp;
+				break;
+			default:
+				elog(ERROR, "invalid JsonbIteratorNext rc: %d", r);
+		}
+	}
+
+	*nentries = i;
+
+	PG_RETURN_POINTER(entries);
+}
+
+Datum
+gin_extract_jsonb_query_hash(PG_FUNCTION_ARGS)
+{
+	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+	StrategyNumber strategy = PG_GETARG_UINT16(2);
+	int32	   *searchMode = (int32 *) PG_GETARG_POINTER(6);
+	Datum	   *entries;
+
+	if (strategy != JsonbContainsStrategyNumber)
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+	/* Query is a jsonb, so just apply gin_extract_jsonb... */
+	entries = (Datum *)
+		DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_hash,
+											PG_GETARG_DATUM(0),
+											PointerGetDatum(nentries)));
+
+	/* ...although "contains {}" requires a full index scan */
+	if (entries == NULL)
+		*searchMode = GIN_SEARCH_MODE_ALL;
+
+	PG_RETURN_POINTER(entries);
+}
+
+/*
+ * Build a text value from a cstring and flag suitable for storage as a key
+ * value
+ */
+static text *
+make_text_key(const char *str, int len, char flag)
+{
+	text	   *item;
+
+	item = (text *) palloc(VARHDRSZ + len + 1);
+	SET_VARSIZE(item, VARHDRSZ + len + 1);
+
+	*VARDATA(item) = flag;
+
+	memcpy(VARDATA(item) + 1, str, len);
+
+	return item;
+}
+
+/*
+ * Create a textual representation of a jsonbValue for GIN storage.
+ */
+static text *
+make_scalar_key(const JsonbValue * scalarVal, char flag)
+{
+	text	   *item;
+	char	   *cstr;
+
+	switch (scalarVal->type)
+	{
+		case jbvNull:
+			item = make_text_key("n", 1, flag);
+			break;
+		case jbvBool:
+			item = make_text_key(scalarVal->boolean ? "t" : "f", 1, flag);
+			break;
+		case jbvNumeric:
+			/*
+			 * A normalized textual representation, free of trailing zeroes is
+			 * is required.
+			 *
+			 * It isn't ideal that numerics are stored in a relatively bulky
+			 * textual format.  However, it's a notationally convenient way of
+			 * storing a "union" type in the GIN B-Tree, and indexing Jsonb
+			 * strings takes precedence.
+			 */
+			cstr = numeric_normalize(scalarVal->numeric);
+			item = make_text_key(cstr, strlen(cstr), flag);
+			pfree(cstr);
+			break;
+		case jbvString:
+			item = make_text_key(scalarVal->string.val, scalarVal->string.len,
+								 flag);
+			break;
+		default:
+			elog(ERROR, "invalid jsonb scalar type");
+	}
+
+	return item;
+}
diff --git a/src/backend/utils/adt/jsonb_op.c b/src/backend/utils/adt/jsonb_op.c
new file mode 100644
index 0000000000000000000000000000000000000000..d6b1855c195f8eedab0ac6afff2e9a142cf92ef1
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_op.c
@@ -0,0 +1,295 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_op.c
+ *	 Special operators for jsonb only, used by various index access methods
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/jsonb_op.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "utils/jsonb.h"
+
+Datum
+jsonb_exists(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	text	   *key = PG_GETARG_TEXT_PP(1);
+	JsonbValue	kval;
+	JsonbValue *v = NULL;
+
+	/*
+	 * We only match Object keys (which are naturally always Strings), or
+	 * string elements in arrays.  In particular, we do not match non-string
+	 * scalar elements.  Existence of a key/element is only considered at the
+	 * top level.  No recursion occurs.
+	 */
+	kval.type = jbvString;
+	kval.string.val = VARDATA_ANY(key);
+	kval.string.len = VARSIZE_ANY_EXHDR(key);
+
+	v = findJsonbValueFromSuperHeader(VARDATA(jb),
+									  JB_FOBJECT | JB_FARRAY,
+									  NULL,
+									  &kval);
+
+	PG_RETURN_BOOL(v != NULL);
+}
+
+Datum
+jsonb_exists_any(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	ArrayType  *keys = PG_GETARG_ARRAYTYPE_P(1);
+	JsonbValue *arrKey = arrayToJsonbSortedArray(keys);
+	uint32	   *plowbound = NULL,
+				lowbound = 0;
+	int			i;
+
+	if (arrKey == NULL || arrKey->object.nPairs == 0)
+		PG_RETURN_BOOL(false);
+
+	if (JB_ROOT_IS_OBJECT(jb))
+		plowbound = &lowbound;
+
+	/*
+	 * We exploit the fact that the pairs list is already sorted into strictly
+	 * increasing order to narrow the findJsonbValueFromSuperHeader search;
+	 * each search can start one entry past the previous "found" entry, or at
+	 * the lower bound of the last search.
+	 */
+	for (i = 0; i < arrKey->array.nElems; i++)
+	{
+		if (findJsonbValueFromSuperHeader(VARDATA(jb),
+										  JB_FOBJECT | JB_FARRAY,
+										  plowbound,
+										  arrKey->array.elems + i) != NULL)
+			PG_RETURN_BOOL(true);
+	}
+
+	PG_RETURN_BOOL(false);
+}
+
+Datum
+jsonb_exists_all(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	ArrayType  *keys = PG_GETARG_ARRAYTYPE_P(1);
+	JsonbValue *arrKey = arrayToJsonbSortedArray(keys);
+	uint32	   *plowbound = NULL;
+	uint32		lowbound = 0;
+	int			i;
+
+	if (arrKey == NULL || arrKey->array.nElems == 0)
+		PG_RETURN_BOOL(true);
+
+	if (JB_ROOT_IS_OBJECT(jb))
+		plowbound = &lowbound;
+
+	/*
+	 * We exploit the fact that the pairs list is already sorted into strictly
+	 * increasing order to narrow the findJsonbValueFromSuperHeader search;
+	 * each search can start one entry past the previous "found" entry, or at
+	 * the lower bound of the last search.
+	 */
+	for (i = 0; i < arrKey->array.nElems; i++)
+	{
+		if (findJsonbValueFromSuperHeader(VARDATA(jb),
+										  JB_FOBJECT | JB_FARRAY,
+										  plowbound,
+										  arrKey->array.elems + i) == NULL)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+jsonb_contains(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *val = PG_GETARG_JSONB(0);
+	Jsonb	   *tmpl = PG_GETARG_JSONB(1);
+
+	JsonbIterator *it1, *it2;
+
+	if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
+		JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
+		PG_RETURN_BOOL(false);
+
+	it1 = JsonbIteratorInit(VARDATA(val));
+	it2 = JsonbIteratorInit(VARDATA(tmpl));
+
+	PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2));
+}
+
+Datum
+jsonb_contained(PG_FUNCTION_ARGS)
+{
+	/* Commutator of "contains" */
+	Jsonb	   *tmpl = PG_GETARG_JSONB(0);
+	Jsonb	   *val = PG_GETARG_JSONB(1);
+
+	JsonbIterator *it1, *it2;
+
+	if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
+		JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
+		PG_RETURN_BOOL(false);
+
+	it1 = JsonbIteratorInit(VARDATA(val));
+	it2 = JsonbIteratorInit(VARDATA(tmpl));
+
+	PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2));
+}
+
+Datum
+jsonb_ne(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	bool		res;
+
+	res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) != 0);
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_BOOL(res);
+}
+
+/*
+ * B-Tree operator class operators, support function
+ */
+Datum
+jsonb_lt(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	bool		res;
+
+	res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) < 0);
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_gt(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	bool		res;
+
+	res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) > 0);
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_le(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	bool		res;
+
+	res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) <= 0);
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_ge(PG_FUNCTION_ARGS)
+{
+
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	bool		res;
+
+	res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) >= 0);
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_eq(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	bool		res;
+
+	res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) == 0);
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_cmp(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jba = PG_GETARG_JSONB(0);
+	Jsonb	   *jbb = PG_GETARG_JSONB(1);
+	int			res;
+
+	res = compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb));
+
+	PG_FREE_IF_COPY(jba, 0);
+	PG_FREE_IF_COPY(jbb, 1);
+	PG_RETURN_INT32(res);
+}
+
+/*
+ * Hash operator class jsonb hashing function
+ */
+Datum
+jsonb_hash(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	JsonbIterator *it;
+	int32		r;
+	JsonbValue	v;
+	uint32		hash = 0;
+
+	if (JB_ROOT_COUNT(jb) == 0)
+		PG_RETURN_INT32(0);
+
+	it = JsonbIteratorInit(VARDATA(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+	{
+		switch (r)
+		{
+			/* Rotation is left to JsonbHashScalarValue() */
+			case WJB_BEGIN_ARRAY:
+				hash ^= JB_FARRAY;
+				break;
+			case WJB_BEGIN_OBJECT:
+				hash ^= JB_FOBJECT;
+				break;
+			case WJB_KEY:
+			case WJB_VALUE:
+			case WJB_ELEM:
+				JsonbHashScalarValue(&v, &hash);
+				break;
+			case WJB_END_ARRAY:
+			case WJB_END_OBJECT:
+				break;
+			default:
+				elog(ERROR, "invalid JsonbIteratorNext rc: %d", r);
+		}
+	}
+
+	PG_FREE_IF_COPY(jb, 0);
+	PG_RETURN_INT32(hash);
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
new file mode 100644
index 0000000000000000000000000000000000000000..4a1d445130188632fba835ecfbe0416ca335bb70
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -0,0 +1,1872 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_util.c
+ *	  Utilities for jsonb datatype
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/jsonb_util.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+#include "utils/memutils.h"
+
+/*
+ * Twice as many values may be stored within pairs (for an Object) than within
+ * elements (for an Array), modulo the current MaxAllocSize limitation.  Note
+ * that JSONB_MAX_PAIRS is derived from the number of possible pairs, not
+ * values (as is the case for arrays and their elements), because we're
+ * concerned about limitations on the representation of the number of pairs.
+ * Over twice the memory is required to store n JsonbPairs as n JsonbValues.
+ * It only takes exactly twice as much disk space for storage, though.  The
+ * JsonbPair (not an actual pair of values) representation is used here because
+ * that is what is subject to the MaxAllocSize restriction when building an
+ * object.
+ */
+#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JENTRY_POSMASK))
+#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), \
+							 JENTRY_POSMASK))
+
+/*
+ * State used while converting an arbitrary JsonbValue into a Jsonb value
+ * (4-byte varlena uncompressed representation of a Jsonb)
+ *
+ * ConvertLevel:  Bookkeeping around particular level when converting.
+ */
+typedef struct convertLevel
+{
+	uint32		i;		/* Iterates once per element, or once per pair */
+	uint32	   *header; /* Pointer to current container header */
+	JEntry	   *meta;	/* This level's metadata */
+	char	   *begin;	/* Pointer into convertState.buffer */
+} convertLevel;
+
+/*
+ * convertState:  Overall bookkeeping state for conversion
+ */
+typedef struct convertState
+{
+	/* Preallocated buffer in which to form varlena/Jsonb value */
+	Jsonb			   *buffer;
+	/* Pointer into buffer */
+	char			   *ptr;
+
+	/* State for  */
+	convertLevel	   *allState,	/* Overall state array */
+					   *contPtr;	/* Cur container pointer (in allState) */
+
+	/* Current size of buffer containing allState array */
+	Size				levelSz;
+
+}	convertState;
+
+static int compareJsonbScalarValue(JsonbValue * a, JsonbValue * b);
+static int lexicalCompareJsonbStringValue(const void *a, const void *b);
+static Size convertJsonb(JsonbValue * val, Jsonb* buffer);
+static inline short addPaddingInt(convertState * cstate);
+static void walkJsonbValueConversion(JsonbValue * val, convertState * cstate,
+									 uint32 nestlevel);
+static void putJsonbValueConversion(convertState * cstate, JsonbValue * val,
+									uint32 flags, uint32 level);
+static void putScalarConversion(convertState * cstate, JsonbValue * scalarVal,
+								uint32 level, uint32 i);
+static void iteratorFromContainerBuf(JsonbIterator * it, char *buffer);
+static bool formIterIsContainer(JsonbIterator ** it, JsonbValue * val,
+								JEntry * ent, bool skipNested);
+static JsonbIterator *freeAndGetParent(JsonbIterator * it);
+static JsonbParseState *pushState(JsonbParseState ** pstate);
+static void appendKey(JsonbParseState * pstate, JsonbValue * scalarVal);
+static void appendValue(JsonbParseState * pstate, JsonbValue * scalarVal);
+static void appendElement(JsonbParseState * pstate, JsonbValue * scalarVal);
+static int lengthCompareJsonbStringValue(const void *a, const void *b, void *arg);
+static int lengthCompareJsonbPair(const void *a, const void *b, void *arg);
+static void uniqueifyJsonbObject(JsonbValue * object);
+static void uniqueifyJsonbArray(JsonbValue * array);
+
+/*
+ * Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
+ *
+ * There isn't a JsonbToJsonbValue(), because generally we find it more
+ * convenient to directly iterate through the Jsonb representation and only
+ * really convert nested scalar values.  formIterIsContainer() does this, so
+ * that clients of the iteration code don't have to directly deal with the
+ * binary representation (JsonbDeepContains() is a notable exception, although
+ * all exceptions are internal to this module).  In general, functions that
+ * accept a JsonbValue argument are concerned with the manipulation of scalar
+ * values, or simple containers of scalar values, where it would be
+ * inconvenient to deal with a great amount of other state.
+ */
+Jsonb *
+JsonbValueToJsonb(JsonbValue * val)
+{
+	Jsonb	   *out;
+	Size		sz;
+
+	if (IsAJsonbScalar(val))
+	{
+		/* Scalar value */
+		JsonbParseState *pstate = NULL;
+		JsonbValue *res;
+		JsonbValue	scalarArray;
+
+		scalarArray.type = jbvArray;
+		scalarArray.array.rawScalar = true;
+		scalarArray.array.nElems = 1;
+
+		pushJsonbValue(&pstate, WJB_BEGIN_ARRAY, &scalarArray);
+		pushJsonbValue(&pstate, WJB_ELEM, val);
+		res = pushJsonbValue(&pstate, WJB_END_ARRAY, NULL);
+
+		out = palloc(VARHDRSZ + res->estSize);
+		sz = convertJsonb(res, out);
+		Assert(sz <= res->estSize);
+		SET_VARSIZE(out, sz + VARHDRSZ);
+	}
+	else if (val->type == jbvObject || val->type == jbvArray)
+	{
+		out = palloc(VARHDRSZ + val->estSize);
+		sz = convertJsonb(val, out);
+		Assert(sz <= val->estSize);
+		SET_VARSIZE(out, VARHDRSZ + sz);
+	}
+	else
+	{
+		Assert(val->type == jbvBinary);
+		out = palloc(VARHDRSZ + val->binary.len);
+		SET_VARSIZE(out, VARHDRSZ + val->binary.len);
+		memcpy(VARDATA(out), val->binary.data, val->binary.len);
+	}
+
+	return out;
+}
+
+/*
+ * BT comparator worker function.  Returns an integer less than, equal to, or
+ * greater than zero, indicating whether a is less than, equal to, or greater
+ * than b.  Consistent with the requirements for a B-Tree operator class
+ *
+ * Strings are compared lexically, in contrast with other places where we use a
+ * much simpler comparator logic for searching through Strings.  Since this is
+ * called from B-Tree support function 1, we're careful about not leaking
+ * memory here.
+ */
+int
+compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
+{
+	JsonbIterator *ita,
+				  *itb;
+	int			res = 0;
+
+	ita = JsonbIteratorInit(a);
+	itb = JsonbIteratorInit(b);
+
+	do
+	{
+		JsonbValue	va,
+					vb;
+		int			ra,
+					rb;
+
+		ra = JsonbIteratorNext(&ita, &va, false);
+		rb = JsonbIteratorNext(&itb, &vb, false);
+
+		/*
+		 * To a limited extent we'll redundantly iterate over an array/object
+		 * while re-performing the same test without any reasonable expectation
+		 * of the same container types having differing lengths (as when we
+		 * process a WJB_BEGIN_OBJECT, and later the corresponding
+		 * WJB_END_OBJECT), but no matter.
+		 */
+		if (ra == rb)
+		{
+			if (ra == WJB_DONE)
+			{
+				/* Decisively equal */
+				break;
+			}
+
+			if (va.type == vb.type)
+			{
+				switch (va.type)
+				{
+					case jbvString:
+						res = lexicalCompareJsonbStringValue(&va, &vb);
+						break;
+					case jbvNull:
+					case jbvNumeric:
+					case jbvBool:
+						res = compareJsonbScalarValue(&va, &vb);
+						break;
+					case jbvArray:
+						/*
+						 * This could be a "raw scalar" pseudo array.  That's a
+						 * special case here though, since we still want the
+						 * general type-based comparisons to apply, and as far
+						 * as we're concerned a pseudo array is just a scalar.
+						 */
+						if (va.array.rawScalar != vb.array.rawScalar)
+							res = (va.array.rawScalar) ? -1 : 1;
+						if (va.array.nElems != vb.array.nElems)
+							res = (va.array.nElems > vb.array.nElems) ? 1 : -1;
+						break;
+					case jbvObject:
+						if (va.object.nPairs != vb.object.nPairs)
+							res = (va.object.nPairs > vb.object.nPairs) ? 1 : -1;
+						break;
+					case jbvBinary:
+						elog(ERROR, "unexpected jbvBinary value");
+				}
+			}
+			else
+			{
+				/* Type-defined order */
+				res = (va.type > vb.type) ? 1 : -1;
+			}
+		}
+		else
+		{
+			/*
+			 * It's safe to assume that the types differed.
+			 *
+			 * If the two values were the same container type, then there'd
+			 * have been a chance to observe the variation in the number of
+			 * elements/pairs (when processing WJB_BEGIN_OBJECT, say).  They
+			 * can't be scalar types either, because then they'd have to be
+			 * contained in containers already ruled unequal due to differing
+			 * numbers of pairs/elements, or already directly ruled unequal
+			 * with a call to the underlying type's comparator.
+			 */
+			Assert(va.type != vb.type);
+			Assert(va.type == jbvArray || va.type == jbvObject);
+			Assert(vb.type == jbvArray || vb.type == jbvObject);
+			/* Type-defined order */
+			res = (va.type > vb.type) ? 1 : -1;
+		}
+	}
+	while (res == 0);
+
+	while (ita != NULL)
+	{
+		JsonbIterator *i = ita->parent;
+		pfree(ita);
+		ita = i;
+	}
+	while (itb != NULL)
+	{
+		JsonbIterator *i = itb->parent;
+		pfree(itb);
+		itb = i;
+	}
+
+	return res;
+}
+
+/*
+ * Find value in object (i.e. the "value" part of some key/value pair in an
+ * object), or find a matching element if we're looking through an array.  Do
+ * so on the basis of equality of the object keys only, or alternatively
+ * element values only, with a caller-supplied value "key".  The "flags"
+ * argument allows the caller to specify which container types are of interest.
+ *
+ * This exported utility function exists to facilitate various cases concerned
+ * with "containment".  If asked to look through an object, the caller had
+ * better pass a Jsonb String, because their keys can only be strings.
+ * Otherwise, for an array, any type of JsonbValue will do.
+ *
+ * In order to proceed with the search, it is necessary for callers to have
+ * both specified an interest in exactly one particular container type with an
+ * appropriate flag, as well as having the pointed-to Jsonb superheader be of
+ * one of those same container types at the top level. (Actually, we just do
+ * whichever makes sense to save callers the trouble of figuring it out - at
+ * most one can make sense, because the super header either points to an array
+ * (possible a "raw scalar" pseudo array) or an object.)
+ *
+ * Note that we can return a jbvBinary JsonbValue if this is called on an
+ * object, but we never do so on an array.  If the caller asks to look through
+ * a container type that is not of the type pointed to by the superheader,
+ * immediately fall through and return NULL.  If we cannot find the value,
+ * return NULL.  Otherwise, return palloc()'d copy of value.
+ *
+ * lowbound can be NULL, but if not it's used to establish a point at which to
+ * start searching.  If the value searched for is found, then lowbound is then
+ * set to an offset into the array or object.  Typically, this is used to
+ * exploit the ordering of objects to avoid redundant work, by also sorting a
+ * list of items to be checked using the internal sort criteria for objects
+ * (object pair keys), and then, when searching for the second or subsequent
+ * item, picking it up where we left off knowing that the second or subsequent
+ * item can not be at a point below the low bound set when the first was found.
+ * This is only useful for objects, not arrays (which have a user-defined
+ * order), so array superheader Jsonbs should just pass NULL.  Moreover, it's
+ * only useful because we only match object pairs on the basis of their key, so
+ * presumably anyone exploiting this is only interested in matching Object keys
+ * with a String.  lowbound is given in units of pairs, not underlying values.
+ */
+JsonbValue *
+findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
+							  uint32 *lowbound, JsonbValue * key)
+{
+	uint32			superheader = *(uint32 *) sheader;
+	JEntry		   *array = (JEntry *) (sheader + sizeof(uint32));
+	int				count = (superheader & JB_CMASK);
+	JsonbValue	   *result = palloc(sizeof(JsonbValue));
+
+	Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
+
+	if (flags & JB_FARRAY & superheader)
+	{
+		char	   *data = (char *) (array + (superheader & JB_CMASK));
+		int			i;
+
+		for (i = 0; i < count; i++)
+		{
+			JEntry	   *e = array + i;
+
+			if (JBE_ISNULL(*e) && key->type == jbvNull)
+			{
+				result->type = jbvNull;
+				result->estSize = sizeof(JEntry);
+			}
+			else if (JBE_ISSTRING(*e) && key->type == jbvString)
+			{
+				result->type = jbvString;
+				result->string.val = data + JBE_OFF(*e);
+				result->string.len = JBE_LEN(*e);
+				result->estSize = sizeof(JEntry) + result->string.len;
+			}
+			else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric)
+			{
+				result->type = jbvNumeric;
+				result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+				result->estSize = 2 * sizeof(JEntry) +
+					VARSIZE_ANY(result->numeric);
+			}
+			else if (JBE_ISBOOL(*e) && key->type == jbvBool)
+			{
+				result->type = jbvBool;
+				result->boolean = JBE_ISBOOL_TRUE(*e) != 0;
+				result->estSize = sizeof(JEntry);
+			}
+			else
+				continue;
+
+			if (compareJsonbScalarValue(key, result) == 0)
+				return result;
+		}
+	}
+	else if (flags & JB_FOBJECT & superheader)
+	{
+		/* Since this is an object, account for *Pairs* of Jentrys */
+		char	   *data = (char *) (array + (superheader & JB_CMASK) * 2);
+		uint32		stopLow = lowbound ? *lowbound : 0,
+					stopMiddle;
+
+		/* Object key past by caller must be a string */
+		Assert(key->type == jbvString);
+
+		/* Binary search on object/pair keys *only* */
+		while (stopLow < count)
+		{
+			JEntry	   *entry;
+			int			difference;
+			JsonbValue	candidate;
+
+			/*
+			 * Note how we compensate for the fact that we're iterating through
+			 * pairs (not entries) throughout.
+			 */
+			stopMiddle = stopLow + (count - stopLow) / 2;
+
+			entry = array + stopMiddle * 2;
+
+			candidate.type = jbvString;
+			candidate.string.val = data + JBE_OFF(*entry);
+			candidate.string.len = JBE_LEN(*entry);
+			candidate.estSize = sizeof(JEntry) + candidate.string.len;
+
+			difference = lengthCompareJsonbStringValue(&candidate, key, NULL);
+
+			if (difference == 0)
+			{
+				/* Found our value (from key/value pair) */
+				JEntry	   *v = entry + 1;
+
+				if (lowbound)
+					*lowbound = stopMiddle + 1;
+
+				if (JBE_ISNULL(*v))
+				{
+					result->type = jbvNull;
+					result->estSize = sizeof(JEntry);
+				}
+				else if (JBE_ISSTRING(*v))
+				{
+					result->type = jbvString;
+					result->string.val = data + JBE_OFF(*v);
+					result->string.len = JBE_LEN(*v);
+					result->estSize = sizeof(JEntry) + result->string.len;
+				}
+				else if (JBE_ISNUMERIC(*v))
+				{
+					result->type = jbvNumeric;
+					result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v)));
+					result->estSize = 2 * sizeof(JEntry) +
+						VARSIZE_ANY(result->numeric);
+				}
+				else if (JBE_ISBOOL(*v))
+				{
+					result->type = jbvBool;
+					result->boolean = JBE_ISBOOL_TRUE(*v) != 0;
+					result->estSize = sizeof(JEntry);
+				}
+				else
+				{
+					/*
+					 * See header comments to understand why this never happens
+					 * with arrays
+					 */
+					result->type = jbvBinary;
+					result->binary.data = data + INTALIGN(JBE_OFF(*v));
+					result->binary.len = JBE_LEN(*v) -
+						(INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v));
+					result->estSize = 2 * sizeof(JEntry) + result->binary.len;
+				}
+
+				return result;
+			}
+			else
+			{
+				if (difference < 0)
+					stopLow = stopMiddle + 1;
+				else
+					count = stopMiddle;
+			}
+		}
+
+		if (lowbound)
+			*lowbound = stopLow;
+	}
+
+	/* Not found */
+	pfree(result);
+	return NULL;
+}
+
+/*
+ * Get i-th value of Jsonb array from superheader.
+ *
+ * Returns palloc()'d copy of value.
+ */
+JsonbValue *
+getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i)
+{
+	uint32		superheader = *(uint32 *) sheader;
+	JsonbValue *result;
+	JEntry	   *array,
+			   *e;
+	char	   *data;
+
+	result = palloc(sizeof(JsonbValue));
+
+	if (i >= (superheader & JB_CMASK))
+		return NULL;
+
+	array = (JEntry *) (sheader + sizeof(uint32));
+
+	if (superheader & JB_FARRAY)
+	{
+		e = array + i;
+		data = (char *) (array + (superheader & JB_CMASK));
+	}
+	else
+	{
+		elog(ERROR, "not a jsonb array");
+	}
+
+	if (JBE_ISNULL(*e))
+	{
+		result->type = jbvNull;
+		result->estSize = sizeof(JEntry);
+	}
+	else if (JBE_ISSTRING(*e))
+	{
+		result->type = jbvString;
+		result->string.val = data + JBE_OFF(*e);
+		result->string.len = JBE_LEN(*e);
+		result->estSize = sizeof(JEntry) + result->string.len;
+	}
+	else if (JBE_ISNUMERIC(*e))
+	{
+		result->type = jbvNumeric;
+		result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+		result->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(result->numeric);
+	}
+	else if (JBE_ISBOOL(*e))
+	{
+		result->type = jbvBool;
+		result->boolean = JBE_ISBOOL_TRUE(*e) != 0;
+		result->estSize = sizeof(JEntry);
+	}
+	else
+	{
+		result->type = jbvBinary;
+		result->binary.data = data + INTALIGN(JBE_OFF(*e));
+		result->binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e));
+		result->estSize = result->binary.len + 2 * sizeof(JEntry);
+	}
+
+	return result;
+}
+
+/*
+ * Push JsonbValue into JsonbParseState.
+ *
+ * Used when parsing JSON tokens to form Jsonb, or when converting an in-memory
+ * JsonbValue to a Jsonb.
+ *
+ * Initial state of *JsonbParseState is NULL, since it'll be allocated here
+ * originally (caller will get JsonbParseState back by reference).
+ *
+ * Only sequential tokens pertaining to non-container types should pass a
+ * JsonbValue.  There is one exception -- WJB_BEGIN_ARRAY callers may pass a
+ * "raw scalar" pseudo array to append that.
+ */
+JsonbValue *
+pushJsonbValue(JsonbParseState ** pstate, int seq, JsonbValue * scalarVal)
+{
+	JsonbValue *result = NULL;
+
+	switch (seq)
+	{
+		case WJB_BEGIN_ARRAY:
+			Assert(!scalarVal || scalarVal->array.rawScalar);
+			*pstate = pushState(pstate);
+			result = &(*pstate)->contVal;
+			(*pstate)->contVal.type = jbvArray;
+			(*pstate)->contVal.estSize = 3 * sizeof(JEntry);
+			(*pstate)->contVal.array.nElems = 0;
+			(*pstate)->contVal.array.rawScalar = (scalarVal &&
+												  scalarVal->array.rawScalar);
+			if (scalarVal && scalarVal->array.nElems > 0)
+			{
+				/* Assume that this array is still really a scalar */
+				Assert(scalarVal->type == jbvArray);
+				(*pstate)->size = scalarVal->array.nElems;
+			}
+			else
+			{
+				(*pstate)->size = 4;
+			}
+			(*pstate)->contVal.array.elems = palloc(sizeof(JsonbValue) *
+													(*pstate)->size);
+			break;
+		case WJB_BEGIN_OBJECT:
+			Assert(!scalarVal);
+			*pstate = pushState(pstate);
+			result = &(*pstate)->contVal;
+			(*pstate)->contVal.type = jbvObject;
+			(*pstate)->contVal.estSize = 3 * sizeof(JEntry);
+			(*pstate)->contVal.object.nPairs = 0;
+			(*pstate)->size = 4;
+			(*pstate)->contVal.object.pairs = palloc(sizeof(JsonbPair) *
+													 (*pstate)->size);
+			break;
+		case WJB_KEY:
+			Assert(scalarVal->type == jbvString);
+			appendKey(*pstate, scalarVal);
+			break;
+		case WJB_VALUE:
+			Assert(IsAJsonbScalar(scalarVal) ||
+				   scalarVal->type == jbvBinary);
+			appendValue(*pstate, scalarVal);
+			break;
+		case WJB_ELEM:
+			Assert(IsAJsonbScalar(scalarVal) ||
+				   scalarVal->type == jbvBinary);
+			appendElement(*pstate, scalarVal);
+			break;
+		case WJB_END_OBJECT:
+			uniqueifyJsonbObject(&(*pstate)->contVal);
+		case WJB_END_ARRAY:
+			/* Steps here common to WJB_END_OBJECT case */
+			Assert(!scalarVal);
+			result = &(*pstate)->contVal;
+
+			/*
+			 * Pop stack and push current array/object as value in parent
+			 * array/object
+			 */
+			*pstate = (*pstate)->next;
+			if (*pstate)
+			{
+				switch ((*pstate)->contVal.type)
+				{
+					case jbvArray:
+						appendElement(*pstate, result);
+						break;
+					case jbvObject:
+						appendValue(*pstate, result);
+						break;
+					default:
+						elog(ERROR, "invalid jsonb container type");
+				}
+			}
+			break;
+		default:
+			elog(ERROR, "unrecognized jsonb sequential processing token");
+	}
+
+	return result;
+}
+
+/*
+ * Given a Jsonb superheader, expand to JsonbIterator to iterate over items
+ * fully expanded to in-memory representation for manipulation.
+ *
+ * See JsonbIteratorNext() for notes on memory management.
+ */
+JsonbIterator *
+JsonbIteratorInit(JsonbSuperHeader sheader)
+{
+	JsonbIterator *it = palloc(sizeof(JsonbIterator));
+
+	iteratorFromContainerBuf(it, sheader);
+	it->parent = NULL;
+
+	return it;
+}
+
+/*
+ * Get next JsonbValue while iterating
+ *
+ * Caller should initially pass their own, original iterator.  They may get
+ * back a child iterator palloc()'d here instead.  The function can be relied
+ * on to free those child iterators, lest the memory allocated for highly
+ * nested objects become unreasonable, but only if callers don't end iteration
+ * early (by breaking upon having found something in a search, for example).
+ *
+ * Callers in such a scenario, that are particularly sensitive to leaking
+ * memory in a long-lived context may walk the ancestral tree from the final
+ * iterator we left them with to its oldest ancestor, pfree()ing as they go.
+ * They do not have to free any other memory previously allocated for iterators
+ * but not accessible as direct ancestors of the iterator they're last passed
+ * back.
+ *
+ * Returns "Jsonb sequential processing" token value.  Iterator "state"
+ * reflects the current stage of the process in a less granular fashion, and is
+ * mostly used here to track things internally with respect to particular
+ * iterators.
+ *
+ * Clients of this function should not have to handle any jbvBinary values
+ * (since recursive calls will deal with this), provided skipNested is false.
+ * It is our job to expand the jbvBinary representation without bothering them
+ * with it.  However, clients should not take it upon themselves to touch array
+ * or Object element/pair buffers, since their element/pair pointers are
+ * garbage.
+ */
+int
+JsonbIteratorNext(JsonbIterator ** it, JsonbValue * val, bool skipNested)
+{
+	JsonbIterState	state;
+
+	/* Guard against stack overflow due to overly complex Jsonb */
+	check_stack_depth();
+
+	/* Recursive caller may have original caller's iterator */
+	if (*it == NULL)
+		return WJB_DONE;
+
+	state = (*it)->state;
+
+	if ((*it)->containerType == JB_FARRAY)
+	{
+		if (state == jbi_start)
+		{
+			/* Set v to array on first array call */
+			val->type = jbvArray;
+			val->array.nElems = (*it)->nElems;
+			/*
+			 * v->array.elems is not actually set, because we aren't doing a
+			 * full conversion
+			 */
+			val->array.rawScalar = (*it)->isScalar;
+			(*it)->i = 0;
+			/* Set state for next call */
+			(*it)->state = jbi_elem;
+			return WJB_BEGIN_ARRAY;
+		}
+		else if (state == jbi_elem)
+		{
+			if ((*it)->i >= (*it)->nElems)
+			{
+				/*
+				 * All elements within array already processed.  Report this to
+				 * caller, and give it back original parent iterator (which
+				 * independently tracks iteration progress at its level of
+				 * nesting).
+				 */
+				*it = freeAndGetParent(*it);
+				return WJB_END_ARRAY;
+			}
+			else if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i++],
+										 skipNested))
+			{
+				/*
+				 * New child iterator acquired within formIterIsContainer.
+				 * Recurse into container.  Don't directly return jbvBinary
+				 * value to top-level client.
+				 */
+				return JsonbIteratorNext(it, val, skipNested);
+			}
+			else
+			{
+				/* Scalar item in array */
+				return WJB_ELEM;
+			}
+		}
+	}
+	else if ((*it)->containerType == JB_FOBJECT)
+	{
+		if (state == jbi_start)
+		{
+			/* Set v to object on first object call */
+			val->type = jbvObject;
+			val->object.nPairs = (*it)->nElems;
+			/*
+			 * v->object.pairs is not actually set, because we aren't doing a
+			 * full conversion
+			 */
+			(*it)->i = 0;
+			/* Set state for next call */
+			(*it)->state = jbi_key;
+			return WJB_BEGIN_OBJECT;
+		}
+		else if (state == jbi_key)
+		{
+			if ((*it)->i >= (*it)->nElems)
+			{
+				/*
+				 * All pairs within object already processed.  Report this to
+				 * caller, and give it back original containing iterator (which
+				 * independently tracks iteration progress at its level of
+				 * nesting).
+				 */
+				*it = freeAndGetParent(*it);
+				return WJB_END_OBJECT;
+			}
+			else
+			{
+				/*
+				 * Return binary item key (ensured by setting skipNested to
+				 * false directly).  No child iterator, no further recursion.
+				 * When control reaches here, it's probably from a recursive
+				 * call.
+				 */
+				if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i * 2], false))
+					elog(ERROR, "unexpected container as object key");
+
+				Assert(val->type == jbvString);
+				/* Set state for next call */
+				(*it)->state = jbi_value;
+				return WJB_KEY;
+			}
+		}
+		else if (state == jbi_value)
+		{
+			/* Set state for next call */
+			(*it)->state = jbi_key;
+
+			/*
+			 * Value may be a container, in which case we recurse with new,
+			 * child iterator.  If it is, don't bother !skipNested callers with
+			 * dealing with the jbvBinary representation.
+			 */
+			if (formIterIsContainer(it, val, &(*it)->meta[((*it)->i++) * 2 + 1],
+									skipNested))
+				return JsonbIteratorNext(it, val, skipNested);
+			else
+				return WJB_VALUE;
+		}
+	}
+
+	elog(ERROR, "invalid iterator state");
+}
+
+/*
+ * Worker for "contains" operator's function
+ *
+ * Formally speaking, containment is top-down, unordered subtree isomorphism.
+ *
+ * Takes iterators that belong to some container type.  These iterators
+ * "belong" to those values in the sense that they've just been initialized in
+ * respect of them by the caller (perhaps in a nested fashion).
+ *
+ * "val" is lhs Jsonb, and mContained is rhs Jsonb when called from top level.
+ * We determine if mContained is contained within val.
+ */
+bool
+JsonbDeepContains(JsonbIterator ** val, JsonbIterator ** mContained)
+{
+	uint32		rval,
+				rcont;
+	JsonbValue	vval,
+				vcontained;
+	/*
+	 * Guard against stack overflow due to overly complex Jsonb.
+	 *
+	 * Functions called here independently take this precaution, but that might
+	 * not be sufficient since this is also a recursive function.
+	 */
+	check_stack_depth();
+
+	rval = JsonbIteratorNext(val, &vval, false);
+	rcont = JsonbIteratorNext(mContained, &vcontained, false);
+
+	if (rval != rcont)
+	{
+		/*
+		 * The differing return values can immediately be taken as indicating
+		 * two differing container types at this nesting level, which is
+		 * sufficient reason to give up entirely (but it should be the case
+		 * that they're both some container type).
+		 */
+		Assert(rval == WJB_BEGIN_OBJECT || rval == WJB_BEGIN_ARRAY);
+		Assert(rcont == WJB_BEGIN_OBJECT || rcont == WJB_BEGIN_ARRAY);
+		return false;
+	}
+	else if (rcont == WJB_BEGIN_OBJECT)
+	{
+		JsonbValue *lhsVal;		/* lhsVal is from pair in lhs object */
+
+		Assert(vcontained.type == jbvObject);
+
+		/* Work through rhs "is it contained within?" object */
+		for (;;)
+		{
+			rcont = JsonbIteratorNext(mContained, &vcontained, false);
+
+			/*
+			 * When we get through caller's rhs "is it contained within?"
+			 * object without failing to find one of its values, it's
+			 * contained.
+			 */
+			if (rcont == WJB_END_OBJECT)
+				return true;
+
+			Assert(rcont == WJB_KEY);
+
+			/* First, find value by key... */
+			lhsVal = findJsonbValueFromSuperHeader((*val)->buffer,
+												   JB_FOBJECT,
+												   NULL,
+												   &vcontained);
+
+			if (!lhsVal)
+				return false;
+
+			/*
+			 * ...at this stage it is apparent that there is at least a key
+			 * match for this rhs pair.
+			 */
+			rcont = JsonbIteratorNext(mContained, &vcontained, true);
+
+			Assert(rcont == WJB_VALUE);
+
+			/*
+			 * Compare rhs pair's value with lhs pair's value just found using
+			 * key
+			 */
+			if (lhsVal->type != vcontained.type)
+			{
+				return false;
+			}
+			else if (IsAJsonbScalar(lhsVal))
+			{
+				if (compareJsonbScalarValue(lhsVal, &vcontained) != 0)
+					return false;
+			}
+			else
+			{
+				/* Nested container value (object or array) */
+				JsonbIterator *nestval, *nestContained;
+
+				Assert(lhsVal->type == jbvBinary);
+				Assert(vcontained.type == jbvBinary);
+
+				nestval = JsonbIteratorInit(lhsVal->binary.data);
+				nestContained = JsonbIteratorInit(vcontained.binary.data);
+
+				/*
+				 * Match "value" side of rhs datum object's pair recursively.
+				 * It's a nested structure.
+				 *
+				 * Note that nesting still has to "match up" at the right
+				 * nesting sub-levels.  However, there need only be zero or
+				 * more matching pairs (or elements) at each nesting level
+				 * (provided the *rhs* pairs/elements *all* match on each
+				 * level), which enables searching nested structures for a
+				 * single String or other primitive type sub-datum quite
+				 * effectively (provided the user constructed the rhs nested
+				 * structure such that we "know where to look").
+				 *
+				 * In other words, the mapping of container nodes in the rhs
+				 * "vcontained" Jsonb to internal nodes on the lhs is
+				 * injective, and parent-child edges on the rhs must be mapped
+				 * to parent-child edges on the lhs to satisfy the condition of
+				 * containment (plus of course the mapped nodes must be equal).
+				 */
+				if (!JsonbDeepContains(&nestval, &nestContained))
+					return false;
+			}
+		}
+	}
+	else if (rcont == WJB_BEGIN_ARRAY)
+	{
+		JsonbValue *lhsConts = NULL;
+		uint32		nLhsElems = vval.array.nElems;
+
+		Assert(vcontained.type == jbvArray);
+
+		/*
+		 * Handle distinction between "raw scalar" pseudo arrays, and real
+		 * arrays.
+		 *
+		 * A raw scalar may contain another raw scalar, and an array may
+		 * contain a raw scalar, but a raw scalar may not contain an array.  We
+		 * don't do something like this for the object case, since objects can
+		 * only contain pairs, never raw scalars (a pair is represented by an
+		 * rhs object argument with a single contained pair).
+		 */
+		if (vval.array.rawScalar && !vcontained.array.rawScalar)
+			return false;
+
+		/* Work through rhs "is it contained within?" array */
+		for (;;)
+		{
+			rcont = JsonbIteratorNext(mContained, &vcontained, true);
+
+			/*
+			 * When we get through caller's rhs "is it contained within?" array
+			 * without failing to find one of its values, it's contained.
+			 */
+			if (rcont == WJB_END_ARRAY)
+				return true;
+
+			Assert(rcont == WJB_ELEM);
+
+			if (IsAJsonbScalar(&vcontained))
+			{
+				if (!findJsonbValueFromSuperHeader((*val)->buffer,
+												   JB_FARRAY,
+												   NULL,
+												   &vcontained))
+					return false;
+			}
+			else
+			{
+				uint32		i;
+
+				/*
+				 * If this is first container found in rhs array (at this
+				 * depth), initialize temp lhs array of containers
+				 */
+				if (lhsConts == NULL)
+				{
+					uint32		j = 0;
+
+					/* Make room for all possible values */
+					lhsConts = palloc(sizeof(JsonbValue) * nLhsElems);
+
+					for (i = 0; i < nLhsElems; i++)
+					{
+						/* Store all lhs elements in temp array*/
+						rcont = JsonbIteratorNext(val, &vval, true);
+						Assert(rcont == WJB_ELEM);
+
+						if (vval.type == jbvBinary)
+							lhsConts[j++] = vval;
+					}
+
+					/* No container elements in temp array, so give up now */
+					if (j == 0)
+						return false;
+
+					/* We may have only partially filled array */
+					nLhsElems = j;
+				}
+
+				/* XXX: Nested array containment is O(N^2) */
+				for (i = 0; i < nLhsElems; i++)
+				{
+					/* Nested container value (object or array) */
+					JsonbIterator  *nestval, *nestContained;
+					bool			contains;
+
+					nestval = JsonbIteratorInit(lhsConts[i].binary.data);
+					nestContained = JsonbIteratorInit(vcontained.binary.data);
+
+					contains = JsonbDeepContains(&nestval, &nestContained);
+
+					if (nestval)
+						pfree(nestval);
+					if (nestContained)
+						pfree(nestContained);
+					if (contains)
+						break;
+				}
+
+				/*
+				 * Report rhs container value is not contained if couldn't
+				 * match rhs container to *some* lhs cont
+				 */
+				if (i == nLhsElems)
+					return false;
+			}
+		}
+	}
+	else
+	{
+		elog(ERROR, "invalid jsonb container type");
+	}
+
+	elog(ERROR, "unexpectedly fell off end of jsonb container");
+}
+
+/*
+ * Convert a Postgres text array to a Jsonb array, sorted and with
+ * de-duplicated key elements.  This is used for searching an object for items
+ * in the array, so we enforce that the number of strings cannot exceed
+ * JSONB_MAX_PAIRS.
+ */
+JsonbValue *
+arrayToJsonbSortedArray(ArrayType *array)
+{
+	Datum	   *key_datums;
+	bool	   *key_nulls;
+	int			elem_count;
+	JsonbValue *result;
+	int			i,
+				j;
+
+	/* Extract data for sorting */
+	deconstruct_array(array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls,
+					  &elem_count);
+
+	if (elem_count == 0)
+		return NULL;
+
+	/*
+	 * A text array uses at least eight bytes per element, so any overflow in
+	 * "key_count * sizeof(JsonbPair)" is small enough for palloc() to catch.
+	 * However, credible improvements to the array format could invalidate that
+	 * assumption.  Therefore, use an explicit check rather than relying on
+	 * palloc() to complain.
+	 */
+	if (elem_count > JSONB_MAX_PAIRS)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of array elements (%d) exceeds maximum allowed Jsonb pairs (%zu)",
+						elem_count, JSONB_MAX_PAIRS)));
+
+	result = palloc(sizeof(JsonbValue));
+	result->type = jbvArray;
+	result->array.rawScalar = false;
+	result->array.elems = palloc(sizeof(JsonbPair) * elem_count);
+
+	for (i = 0, j = 0; i < elem_count; i++)
+	{
+		if (!key_nulls[i])
+		{
+			result->array.elems[j].type = jbvString;
+			result->array.elems[j].string.val = VARDATA(key_datums[i]);
+			result->array.elems[j].string.len = VARSIZE(key_datums[i]) - VARHDRSZ;
+			j++;
+		}
+	}
+	result->array.nElems = j;
+
+	uniqueifyJsonbArray(result);
+	return result;
+}
+
+/*
+ * Hash a JsonbValue scalar value, mixing in the hash value with an existing
+ * hash provided by the caller.
+ *
+ * Some callers may wish to independently XOR in JB_FOBJECT and JB_FARRAY
+ * flags.
+ */
+void
+JsonbHashScalarValue(const JsonbValue * scalarVal, uint32 * hash)
+{
+	int tmp;
+
+	/*
+	 * Combine hash values of successive keys, values and elements by rotating
+	 * the previous value left 1 bit, then XOR'ing in the new
+	 * key/value/element's hash value.
+	 */
+	*hash = (*hash << 1) | (*hash >> 31);
+	switch (scalarVal->type)
+	{
+		case jbvNull:
+			*hash ^= 0x01;
+			return;
+		case jbvString:
+			tmp = hash_any((unsigned char *) scalarVal->string.val,
+						   scalarVal->string.len);
+			*hash ^= tmp;
+			return;
+		case jbvNumeric:
+			/* Must be unaffected by trailing zeroes */
+			tmp = DatumGetInt32(DirectFunctionCall1(hash_numeric,
+													NumericGetDatum(scalarVal->numeric)));
+			*hash ^= tmp;
+			return;
+		case jbvBool:
+			*hash ^= scalarVal->boolean? 0x02:0x04;
+			return;
+		default:
+			elog(ERROR, "invalid jsonb scalar type");
+	}
+}
+
+/*
+ * Are two scalar JsonbValues of the same type a and b equal?
+ *
+ * Does not use lexical comparisons.  Therefore, it is essentially that this
+ * never be used against Strings for anything other than searching for values
+ * within a single jsonb.
+ */
+static int
+compareJsonbScalarValue(JsonbValue * aScalar, JsonbValue * bScalar)
+{
+	if (aScalar->type == bScalar->type)
+	{
+		switch (aScalar->type)
+		{
+			case jbvNull:
+				return 0;
+			case jbvString:
+				return lengthCompareJsonbStringValue(aScalar, bScalar, NULL);
+			case jbvNumeric:
+				return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+														 PointerGetDatum(aScalar->numeric),
+														 PointerGetDatum(bScalar->numeric)));
+			case jbvBool:
+				if (aScalar->boolean != bScalar->boolean)
+					return (aScalar->boolean > bScalar->boolean) ? 1 : -1;
+				else
+					return 0;
+			default:
+				elog(ERROR, "invalid jsonb scalar type");
+		}
+	}
+	elog(ERROR, "jsonb scalar type mismatch");
+}
+
+/*
+ * Standard lexical qsort() comparator of jsonb strings.
+ *
+ * Sorts strings lexically, using the default database collation.  Used by
+ * B-Tree operators, where a lexical sort order is generally expected.
+ */
+static int
+lexicalCompareJsonbStringValue(const void *a, const void *b)
+{
+	const JsonbValue *va = (const JsonbValue *) a;
+	const JsonbValue *vb = (const JsonbValue *) b;
+
+	Assert(va->type == jbvString);
+	Assert(vb->type == jbvString);
+
+	return varstr_cmp(va->string.val, va->string.len, vb->string.val,
+					  vb->string.len, DEFAULT_COLLATION_OID);
+}
+
+/*
+ * Given a JsonbValue, convert to Jsonb and store in preallocated Jsonb buffer
+ * sufficiently large to fit the value
+ */
+static Size
+convertJsonb(JsonbValue * val, Jsonb *buffer)
+{
+	convertState	state;
+	Size			len;
+
+	/* Should not already have binary representation */
+	Assert(val->type != jbvBinary);
+
+	state.buffer = buffer;
+	/* Start from superheader */
+	state.ptr = VARDATA(state.buffer);
+	state.levelSz = 8;
+	state.allState = palloc(sizeof(convertLevel) * state.levelSz);
+
+	walkJsonbValueConversion(val, &state, 0);
+
+	len = state.ptr - VARDATA(state.buffer);
+
+	Assert(len <= val->estSize);
+	return len;
+}
+
+/*
+ * Walk the tree representation of Jsonb, as part of the process of converting
+ * a JsonbValue to a Jsonb.
+ *
+ * This high-level function takes care of recursion into sub-containers, but at
+ * the top level calls putJsonbValueConversion once per sequential processing
+ * token (in a manner similar to generic iteration).
+ */
+static void
+walkJsonbValueConversion(JsonbValue * val, convertState * cstate,
+						 uint32 nestlevel)
+{
+	int			i;
+
+	check_stack_depth();
+
+	if (!val)
+		return;
+
+	switch (val->type)
+	{
+		case jbvArray:
+
+			putJsonbValueConversion(cstate, val, WJB_BEGIN_ARRAY, nestlevel);
+			for (i = 0; i < val->array.nElems; i++)
+			{
+				if (IsAJsonbScalar(&val->array.elems[i]) ||
+					val->array.elems[i].type == jbvBinary)
+					putJsonbValueConversion(cstate, val->array.elems + i,
+											WJB_ELEM, nestlevel);
+				else
+					walkJsonbValueConversion(val->array.elems + i, cstate,
+											 nestlevel + 1);
+			}
+			putJsonbValueConversion(cstate, val, WJB_END_ARRAY, nestlevel);
+
+			break;
+		case jbvObject:
+
+			putJsonbValueConversion(cstate, val, WJB_BEGIN_OBJECT, nestlevel);
+			for (i = 0; i < val->object.nPairs; i++)
+			{
+				putJsonbValueConversion(cstate, &val->object.pairs[i].key,
+										WJB_KEY, nestlevel);
+
+				if (IsAJsonbScalar(&val->object.pairs[i].value) ||
+					val->object.pairs[i].value.type == jbvBinary)
+					putJsonbValueConversion(cstate,
+											&val->object.pairs[i].value,
+											WJB_VALUE, nestlevel);
+				else
+					walkJsonbValueConversion(&val->object.pairs[i].value,
+											 cstate, nestlevel + 1);
+			}
+			putJsonbValueConversion(cstate, val, WJB_END_OBJECT, nestlevel);
+
+			break;
+		default:
+			elog(ERROR, "unknown type of jsonb container");
+	}
+}
+
+/*
+ * walkJsonbValueConversion() worker.  Add padding sufficient to int-align our
+ * access to conversion buffer.
+ */
+static inline
+short addPaddingInt(convertState * cstate)
+{
+	short		padlen, p;
+
+	padlen = INTALIGN(cstate->ptr - VARDATA(cstate->buffer)) -
+		(cstate->ptr - VARDATA(cstate->buffer));
+
+	for (p = padlen; p > 0; p--)
+	{
+		*cstate->ptr = '\0';
+		cstate->ptr++;
+	}
+
+	return padlen;
+}
+
+/*
+ * walkJsonbValueConversion() worker.
+ *
+ * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
+ * copy over an arbitrary individual JsonbValue.  This function may copy any
+ * type of value, even containers (Objects/arrays).  However, it is not
+ * responsible for recursive aspects of walking the tree (so only top-level
+ * Object/array details are handled).
+ *
+ * No details about their keys/values/elements are handled recursively -
+ * rather, the function is called as required for the start of an Object/Array,
+ * and the end (i.e.  there is one call per sequential processing WJB_* token).
+ */
+static void
+putJsonbValueConversion(convertState * cstate, JsonbValue * val, uint32 flags,
+						uint32 level)
+{
+	if (level == cstate->levelSz)
+	{
+		cstate->levelSz *= 2;
+		cstate->allState = repalloc(cstate->allState,
+									 sizeof(convertLevel) * cstate->levelSz);
+	}
+
+	cstate->contPtr = cstate->allState + level;
+
+	if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT))
+	{
+		Assert(((flags & WJB_BEGIN_ARRAY) && val->type == jbvArray) ||
+			   ((flags & WJB_BEGIN_OBJECT) && val->type == jbvObject));
+
+		/* Initialize pointer into conversion buffer at this level */
+		cstate->contPtr->begin = cstate->ptr;
+
+		addPaddingInt(cstate);
+
+		/* Initialize everything else at this level */
+		cstate->contPtr->header = (uint32 *) cstate->ptr;
+		/* Advance past header */
+		cstate->ptr += sizeof(uint32);
+		cstate->contPtr->meta = (JEntry *) cstate->ptr;
+		cstate->contPtr->i = 0;
+
+		if (val->type == jbvArray)
+		{
+			*cstate->contPtr->header = val->array.nElems | JB_FARRAY;
+			cstate->ptr += sizeof(JEntry) * val->array.nElems;
+
+			if (val->array.rawScalar)
+			{
+				Assert(val->array.nElems == 1);
+				Assert(level == 0);
+				*cstate->contPtr->header |= JB_FSCALAR;
+			}
+		}
+		else
+		{
+			*cstate->contPtr->header = val->object.nPairs | JB_FOBJECT;
+			cstate->ptr += sizeof(JEntry) * val->object.nPairs * 2;
+		}
+	}
+	else if (flags & WJB_ELEM)
+	{
+		putScalarConversion(cstate, val, level, cstate->contPtr->i);
+		cstate->contPtr->i++;
+	}
+	else if (flags & WJB_KEY)
+	{
+		Assert(val->type == jbvString);
+
+		putScalarConversion(cstate, val, level, cstate->contPtr->i * 2);
+	}
+	else if (flags & WJB_VALUE)
+	{
+		putScalarConversion(cstate, val, level, cstate->contPtr->i * 2 + 1);
+		cstate->contPtr->i++;
+	}
+	else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT))
+	{
+		convertLevel   *prevPtr;	/* Prev container pointer */
+		uint32			len,
+						i;
+
+		Assert(((flags & WJB_END_ARRAY) && val->type == jbvArray) ||
+			   ((flags & WJB_END_OBJECT) && val->type == jbvObject));
+
+		if (level == 0)
+			return;
+
+		len = cstate->ptr - (char *) cstate->contPtr->begin;
+
+		prevPtr = cstate->contPtr - 1;
+
+		if (*prevPtr->header & JB_FARRAY)
+		{
+			i = prevPtr->i;
+
+			prevPtr->meta[i].header = JENTRY_ISNEST;
+
+			if (i == 0)
+				prevPtr->meta[0].header |= JENTRY_ISFIRST | len;
+			else
+				prevPtr->meta[i].header |=
+					(prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
+		}
+		else if (*prevPtr->header & JB_FOBJECT)
+		{
+			i = 2 * prevPtr->i + 1;		/* Value, not key */
+
+			prevPtr->meta[i].header = JENTRY_ISNEST;
+
+			prevPtr->meta[i].header |=
+				(prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
+		}
+		else
+		{
+			elog(ERROR, "invalid jsonb container type");
+		}
+
+		Assert(cstate->ptr - cstate->contPtr->begin <= val->estSize);
+		prevPtr->i++;
+	}
+	else
+	{
+		elog(ERROR, "unknown flag encountered during jsonb tree walk");
+	}
+}
+
+/*
+ * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
+ * serialize and copy a scalar value into buffer.
+ *
+ * This is a worker function for putJsonbValueConversion() (itself a worker for
+ * walkJsonbValueConversion()).  It handles the details with regard to Jentry
+ * metadata peculiar to each scalar type.
+ */
+static void
+putScalarConversion(convertState * cstate, JsonbValue * scalarVal, uint32 level,
+					uint32 i)
+{
+	int 		numlen;
+	short		padlen;
+
+	cstate->contPtr = cstate->allState + level;
+
+	if (i == 0)
+		cstate->contPtr->meta[0].header = JENTRY_ISFIRST;
+	else
+		cstate->contPtr->meta[i].header = 0;
+
+	switch (scalarVal->type)
+	{
+		case jbvNull:
+			cstate->contPtr->meta[i].header |= JENTRY_ISNULL;
+
+			if (i > 0)
+				cstate->contPtr->meta[i].header |=
+					cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
+			break;
+		case jbvString:
+			memcpy(cstate->ptr, scalarVal->string.val, scalarVal->string.len);
+			cstate->ptr += scalarVal->string.len;
+
+			if (i == 0)
+				cstate->contPtr->meta[0].header |= scalarVal->string.len;
+			else
+				cstate->contPtr->meta[i].header |=
+					(cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK) +
+					scalarVal->string.len;
+			break;
+		case jbvNumeric:
+			numlen = VARSIZE_ANY(scalarVal->numeric);
+			padlen = addPaddingInt(cstate);
+
+			memcpy(cstate->ptr, scalarVal->numeric, numlen);
+			cstate->ptr += numlen;
+
+			cstate->contPtr->meta[i].header |= JENTRY_ISNUMERIC;
+			if (i == 0)
+				cstate->contPtr->meta[0].header |= padlen + numlen;
+			else
+				cstate->contPtr->meta[i].header |=
+					(cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK)
+					+ padlen + numlen;
+			break;
+		case jbvBool:
+			cstate->contPtr->meta[i].header |= (scalarVal->boolean) ?
+				JENTRY_ISTRUE : JENTRY_ISFALSE;
+
+			if (i > 0)
+				cstate->contPtr->meta[i].header |=
+					cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
+			break;
+		default:
+			elog(ERROR, "invalid jsonb scalar type");
+	}
+}
+
+/*
+ * Given superheader pointer into buffer, initialize iterator.  Must be a
+ * container type.
+ */
+static void
+iteratorFromContainerBuf(JsonbIterator * it, JsonbSuperHeader sheader)
+{
+	uint32		superheader = *(uint32 *) sheader;
+
+	it->containerType = superheader & (JB_FARRAY | JB_FOBJECT);
+	it->nElems = superheader & JB_CMASK;
+	it->buffer = sheader;
+
+	/* Array starts just after header */
+	it->meta = (JEntry *) (sheader + sizeof(uint32));
+	it->state = jbi_start;
+
+	switch (it->containerType)
+	{
+		case JB_FARRAY:
+			it->dataProper =
+				(char *) it->meta + it->nElems * sizeof(JEntry);
+			it->isScalar = (superheader & JB_FSCALAR) != 0;
+			/* This is either a "raw scalar", or an array */
+			Assert(!it->isScalar || it->nElems == 1);
+			break;
+		case JB_FOBJECT:
+			/*
+			 * Offset reflects that nElems indicates JsonbPairs in an object.
+			 * Each key and each value contain Jentry metadata just the same.
+			 */
+			it->dataProper =
+				(char *) it->meta + it->nElems * sizeof(JEntry) * 2;
+			break;
+		default:
+			elog(ERROR, "unknown type of jsonb container");
+	}
+}
+
+/*
+ * JsonbIteratorNext() worker
+ *
+ * Returns bool indicating if v was a non-jbvBinary container, and thus if
+ * further recursion is required by caller (according to its skipNested
+ * preference).  If it is required, we set the caller's iterator for further
+ * recursion into the nested value.  If we're going to skip nested items, just
+ * set v to a jbvBinary value, but don't set caller's iterator.
+ *
+ * Unlike with containers (either in this function or in any
+ * JsonbIteratorNext() infrastructure), we fully convert from what is
+ * ultimately a Jsonb on-disk representation, to a JsonbValue in-memory
+ * representation (for scalar values only).  JsonbIteratorNext() initializes
+ * container Jsonbvalues, but without a sane private buffer.  For scalar values
+ * it has to be done for real (even if we don't actually allocate more memory
+ * to do this.  The point is that our JsonbValues scalars can be passed around
+ * anywhere).
+ */
+static bool
+formIterIsContainer(JsonbIterator ** it, JsonbValue * val, JEntry * ent,
+					bool skipNested)
+{
+	if (JBE_ISNULL(*ent))
+	{
+		val->type = jbvNull;
+		val->estSize = sizeof(JEntry);
+
+		return false;
+	}
+	else if (JBE_ISSTRING(*ent))
+	{
+		val->type = jbvString;
+		val->string.val = (*it)->dataProper + JBE_OFF(*ent);
+		val->string.len = JBE_LEN(*ent);
+		val->estSize = sizeof(JEntry) + val->string.len;
+
+		return false;
+	}
+	else if (JBE_ISNUMERIC(*ent))
+	{
+		val->type = jbvNumeric;
+		val->numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+		val->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val->numeric);
+
+		return false;
+	}
+	else if (JBE_ISBOOL(*ent))
+	{
+		val->type = jbvBool;
+		val->boolean = JBE_ISBOOL_TRUE(*ent) != 0;
+		val->estSize = sizeof(JEntry);
+
+		return false;
+	}
+	else if (skipNested)
+	{
+		val->type = jbvBinary;
+		val->binary.data = (*it)->dataProper + INTALIGN(JBE_OFF(*ent));
+		val->binary.len = JBE_LEN(*ent) - (INTALIGN(JBE_OFF(*ent)) - JBE_OFF(*ent));
+		val->estSize = val->binary.len + 2 * sizeof(JEntry);
+
+		return false;
+	}
+	else
+	{
+		/*
+		 * Must be container type, so setup caller's iterator to point to that,
+		 * and return indication of that.
+		 *
+		 * Get child iterator.
+		 */
+		JsonbIterator *child = palloc(sizeof(JsonbIterator));
+
+		iteratorFromContainerBuf(child,
+								 (*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+
+		child->parent = *it;
+		*it = child;
+
+		return true;
+	}
+}
+
+/*
+ * JsonbIteratorNext() worker:  Return parent, while freeing memory for current
+ * iterator
+ */
+static JsonbIterator *
+freeAndGetParent(JsonbIterator * it)
+{
+	JsonbIterator *v = it->parent;
+
+	pfree(it);
+	return v;
+}
+
+/*
+ * pushJsonbValue() worker:  Iteration-like forming of Jsonb
+ */
+static JsonbParseState *
+pushState(JsonbParseState ** pstate)
+{
+	JsonbParseState *ns = palloc(sizeof(JsonbParseState));
+
+	ns->next = *pstate;
+	return ns;
+}
+
+/*
+ * pushJsonbValue() worker:  Append a pair key to state when generating a Jsonb
+ */
+static void
+appendKey(JsonbParseState * pstate, JsonbValue * string)
+{
+	JsonbValue *object = &pstate->contVal;
+
+	Assert(object->type == jbvObject);
+	Assert(string->type == jbvString);
+
+	if (object->object.nPairs >= JSONB_MAX_PAIRS)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of jsonb object pairs exceeds the maximum allowed (%zu)",
+						JSONB_MAX_PAIRS)));
+
+	if (object->object.nPairs >= pstate->size)
+	{
+		pstate->size *= 2;
+		object->object.pairs = repalloc(object->object.pairs,
+										sizeof(JsonbPair) * pstate->size);
+	}
+
+	object->object.pairs[object->object.nPairs].key = *string;
+	object->object.pairs[object->object.nPairs].order = object->object.nPairs;
+
+	object->estSize += string->estSize;
+}
+
+/*
+ * pushJsonbValue() worker:  Append a pair value to state when generating a
+ * Jsonb
+ */
+static void
+appendValue(JsonbParseState * pstate, JsonbValue * scalarVal)
+{
+	JsonbValue *object = &pstate->contVal;
+
+	Assert(object->type == jbvObject);
+
+	object->object.pairs[object->object.nPairs++].value = *scalarVal;
+	object->estSize += scalarVal->estSize;
+}
+
+/*
+ * pushJsonbValue() worker:  Append an element to state when generating a Jsonb
+ */
+static void
+appendElement(JsonbParseState * pstate, JsonbValue * scalarVal)
+{
+	JsonbValue *array = &pstate->contVal;
+
+	Assert(array->type == jbvArray);
+
+	if (array->array.nElems >= JSONB_MAX_ELEMS)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of jsonb array elements exceeds the maximum allowed (%zu)",
+						JSONB_MAX_ELEMS)));
+
+	if (array->array.nElems >= pstate->size)
+	{
+		pstate->size *= 2;
+		array->array.elems = repalloc(array->array.elems,
+									  sizeof(JsonbValue) * pstate->size);
+	}
+
+	array->array.elems[array->array.nElems++] = *scalarVal;
+	array->estSize += scalarVal->estSize;
+}
+
+/*
+ * Compare two jbvString JsonbValue values, a and b.
+ *
+ * This is a special qsort_arg() comparator used to sort strings in certain
+ * internal contexts where it is sufficient to have a well-defined sort order.
+ * In particular, object pair keys are sorted according to this criteria to
+ * facilitate cheap binary searches where we don't care about lexical sort
+ * order.
+ *
+ * a and b are first sorted based on their length.  If a tie-breaker is
+ * required, only then do we consider string binary equality.
+ *
+ * Third argument 'binequal' may point to a bool. If it's set, *binequal is set
+ * to true iff a and b have full binary equality, since some callers have an
+ * interest in whether the two values are equal or merely equivalent.
+ */
+static int
+lengthCompareJsonbStringValue(const void *a, const void *b, void *binequal)
+{
+	const JsonbValue *va = (const JsonbValue *) a;
+	const JsonbValue *vb = (const JsonbValue *) b;
+	int			res;
+
+	Assert(va->type == jbvString);
+	Assert(vb->type == jbvString);
+
+	if (va->string.len == vb->string.len)
+	{
+		res = memcmp(va->string.val, vb->string.val, va->string.len);
+		if (res == 0 && binequal)
+			*((bool *) binequal) = true;
+	}
+	else
+	{
+		res = (va->string.len > vb->string.len) ? 1 : -1;
+	}
+
+	return res;
+}
+
+/*
+ * qsort_arg() comparator to compare JsonbPair values.
+ *
+ * Function implemented in terms of lengthCompareJsonbStringValue(), and thus the
+ * same "arg setting" hack will be applied here in respect of the pair's key
+ * values.
+ *
+ * N.B: String comparisons here are "length-wise"
+ *
+ * Pairs with equals keys are ordered such that the order field is respected.
+ */
+static int
+lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
+{
+	const JsonbPair *pa = (const JsonbPair *) a;
+	const JsonbPair *pb = (const JsonbPair *) b;
+	int			res;
+
+	res = lengthCompareJsonbStringValue(&pa->key, &pb->key, binequal);
+
+	/*
+	 * Guarantee keeping order of equal pair.  Unique algorithm will prefer
+	 * first element as value.
+	 */
+	if (res == 0)
+		res = (pa->order > pb->order) ? -1 : 1;
+
+	return res;
+}
+
+/*
+ * Sort and unique-ify pairs in JsonbValue object
+ */
+static void
+uniqueifyJsonbObject(JsonbValue * object)
+{
+	bool		hasNonUniq = false;
+
+	Assert(object->type == jbvObject);
+
+	if (object->object.nPairs > 1)
+		qsort_arg(object->object.pairs, object->object.nPairs, sizeof(JsonbPair),
+				  lengthCompareJsonbPair, &hasNonUniq);
+
+	if (hasNonUniq)
+	{
+		JsonbPair  *ptr = object->object.pairs + 1,
+				   *res = object->object.pairs;
+
+		while (ptr - object->object.pairs < object->object.nPairs)
+		{
+			/* Avoid copying over duplicate */
+			if (lengthCompareJsonbStringValue(ptr, res, NULL) == 0)
+			{
+				object->estSize -= ptr->key.estSize + ptr->value.estSize;
+			}
+			else
+			{
+				res++;
+				if (ptr != res)
+					memcpy(res, ptr, sizeof(JsonbPair));
+			}
+			ptr++;
+		}
+
+		object->object.nPairs = res + 1 - object->object.pairs;
+	}
+}
+
+/*
+ * Sort and unique-ify JsonbArray.
+ *
+ * Sorting uses internal ordering.
+ */
+static void
+uniqueifyJsonbArray(JsonbValue * array)
+{
+	bool hasNonUniq = false;
+
+	Assert(array->type == jbvArray);
+
+	/*
+	 * Actually sort values, determining if any were equal on the basis of full
+	 * binary equality (rather than just having the same string length).
+	 */
+	if (array->array.nElems > 1)
+		qsort_arg(array->array.elems, array->array.nElems,
+				  sizeof(JsonbValue), lengthCompareJsonbStringValue,
+				  &hasNonUniq);
+
+	if (hasNonUniq)
+	{
+		JsonbValue *ptr = array->array.elems + 1,
+				   *res = array->array.elems;
+
+		while (ptr - array->array.elems < array->array.nElems)
+		{
+			/* Avoid copying over duplicate */
+			if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0)
+			{
+				res++;
+				*res = *ptr;
+			}
+
+			ptr++;
+		}
+
+		array->array.nElems = res + 1 - array->array.elems;
+	}
+}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 232030560858f23b4403acb90e43c93e49576aba..f80eaeb1c6d2d515449d90ceabe75bfb5a79f3ef 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * jsonfuncs.c
- *		Functions to process JSON data type.
+ *		Functions to process JSON data types.
  *
  * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -27,6 +27,7 @@
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/jsonapi.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -47,18 +48,20 @@ static void get_array_element_end(void *state, bool isnull);
 static void get_scalar(void *state, char *token, JsonTokenType tokentype);
 
 /* common worker function for json getter functions */
-static inline Datum get_path_all(PG_FUNCTION_ARGS, bool as_text);
+static inline Datum get_path_all(FunctionCallInfo fcinfo, bool as_text);
 static inline text *get_worker(text *json, char *field, int elem_index,
 		   char **tpath, int *ipath, int npath,
 		   bool normalize_results);
+static inline Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text);
 
 /* semantic action functions for json_array_length */
 static void alen_object_start(void *state);
 static void alen_scalar(void *state, char *token, JsonTokenType tokentype);
 static void alen_array_element_start(void *state, bool isnull);
 
-/* common worker for json_each* functions */
-static inline Datum each_worker(PG_FUNCTION_ARGS, bool as_text);
+/* common workers for json{b}_each* functions */
+static inline Datum each_worker(FunctionCallInfo fcinfo, bool as_text);
+static inline Datum each_worker_jsonb(FunctionCallInfo fcinfo, bool as_text);
 
 /* semantic action functions for json_each */
 static void each_object_field_start(void *state, char *fname, bool isnull);
@@ -66,8 +69,9 @@ static void each_object_field_end(void *state, char *fname, bool isnull);
 static void each_array_start(void *state);
 static void each_scalar(void *state, char *token, JsonTokenType tokentype);
 
-/* common worker for json_each* functions */
-static inline Datum elements_worker(PG_FUNCTION_ARGS, bool as_text);
+/* common workers for json{b}_array_elements_* functions */
+static inline Datum elements_worker(FunctionCallInfo fcinfo, bool as_text);
+static inline Datum elements_worker_jsonb(FunctionCallInfo fcinfo, bool as_text);
 
 /* semantic action functions for json_array_elements */
 static void elements_object_start(void *state);
@@ -79,7 +83,7 @@ static void elements_scalar(void *state, char *token, JsonTokenType tokentype);
 static HTAB *get_json_object_as_hash(text *json, char *funcname, bool use_json_as_text);
 
 /* common worker for populate_record and to_record */
-static inline Datum populate_record_worker(PG_FUNCTION_ARGS,
+static inline Datum populate_record_worker(FunctionCallInfo fcinfo,
 					   bool have_record_arg);
 
 /* semantic action functions for get_json_object_as_hash */
@@ -98,8 +102,13 @@ static void populate_recordset_array_start(void *state);
 static void populate_recordset_array_element_start(void *state, bool isnull);
 
 /* worker function for populate_recordset and to_recordset */
-static inline Datum populate_recordset_worker(PG_FUNCTION_ARGS,
+static inline Datum populate_recordset_worker(FunctionCallInfo fcinfo,
 						  bool have_record_arg);
+/* Worker that takes care of common setup for us */
+static JsonbValue *findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader,
+													uint32 flags,
+													char *key,
+													uint32 keylen);
 
 /* search type classification for json_get* functions */
 typedef enum
@@ -225,18 +234,98 @@ typedef struct PopulateRecordsetState
 	MemoryContext fn_mcxt;		/* used to stash IO funcs */
 } PopulateRecordsetState;
 
+/* Turn a jsonb object into a record */
+static void make_row_from_rec_and_jsonb(Jsonb * element,
+										PopulateRecordsetState *state);
+
 /*
- * SQL function json_object-keys
+ * SQL function json_object_keys
  *
  * Returns the set of keys for the object argument.
  *
  * This SRF operates in value-per-call mode. It processes the
  * object during the first call, and the keys are simply stashed
- * in an array, whise size is expanded as necessary. This is probably
+ * in an array, whose size is expanded as necessary. This is probably
  * safe enough for a list of keys of a single object, since they are
  * limited in size to NAMEDATALEN and the number of keys is unlikely to
  * be so huge that it has major memory implications.
  */
+Datum
+jsonb_object_keys(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+	OkeysState *state;
+	int			i;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		MemoryContext oldcontext;
+		Jsonb	   *jb = PG_GETARG_JSONB(0);
+		bool		skipNested = false;
+		JsonbIterator *it;
+		JsonbValue	v;
+		int			r;
+
+		if (JB_ROOT_IS_SCALAR(jb))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("cannot call jsonb_object_keys on a scalar")));
+		else if (JB_ROOT_IS_ARRAY(jb))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("cannot call jsonb_object_keys on an array")));
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		state = palloc(sizeof(OkeysState));
+
+		state->result_size = JB_ROOT_COUNT(jb);
+		state->result_count = 0;
+		state->sent_count = 0;
+		state->result = palloc(state->result_size * sizeof(char *));
+
+		it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+		while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+		{
+			skipNested = true;
+
+			if (r == WJB_KEY)
+			{
+				char	   *cstr;
+
+				cstr = palloc(v.string.len + 1 * sizeof(char));
+				memcpy(cstr, v.string.val, v.string.len);
+				cstr[v.string.len] = '\0';
+				state->result[state->result_count++] = cstr;
+			}
+		}
+
+
+		MemoryContextSwitchTo(oldcontext);
+		funcctx->user_fctx = (void *) state;
+
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+	state = (OkeysState *) funcctx->user_fctx;
+
+	if (state->sent_count < state->result_count)
+	{
+		char	   *nxt = state->result[state->sent_count++];
+
+		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt));
+	}
+
+	/* cleanup to reduce or eliminate memory leaks */
+	for (i = 0; i < state->result_count; i++)
+		pfree(state->result[i]);
+	pfree(state->result);
+	pfree(state);
+
+	SRF_RETURN_DONE(funcctx);
+}
 
 
 Datum
@@ -350,9 +439,9 @@ okeys_scalar(void *state, char *token, JsonTokenType tokentype)
 }
 
 /*
- * json getter functions
+ * json and jsonb getter functions
  * these implement the -> ->> #> and #>> operators
- * and the json_extract_path*(json, text, ...) functions
+ * and the json{b?}_extract_path*(json, text, ...) functions
  */
 
 
@@ -372,6 +461,51 @@ json_object_field(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_object_field(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	char	   *key = text_to_cstring(PG_GETARG_TEXT_P(1));
+	int			klen = strlen(key);
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			r;
+	bool		skipNested = false;
+
+	if (JB_ROOT_IS_SCALAR(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_object_field (jsonb -> text operator) on a scalar")));
+	else if (JB_ROOT_IS_ARRAY(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_object_field (jsonb -> text operator) on an array")));
+
+	Assert(JB_ROOT_IS_OBJECT(jb));
+
+	it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+	{
+		skipNested = true;
+
+		if (r == WJB_KEY)
+		{
+			if (klen == v.string.len && strncmp(key, v.string.val, klen) == 0)
+			{
+				/*
+				 * The next thing the iterator fetches should be the value, no
+				 * matter what shape it is.
+				 */
+				(void) JsonbIteratorNext(&it, &v, skipNested);
+				PG_RETURN_JSONB(JsonbValueToJsonb(&v));
+			}
+		}
+	}
+
+	PG_RETURN_NULL();
+}
+
 Datum
 json_object_field_text(PG_FUNCTION_ARGS)
 {
@@ -388,6 +522,74 @@ json_object_field_text(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_object_field_text(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	char	   *key = text_to_cstring(PG_GETARG_TEXT_P(1));
+	int			klen = strlen(key);
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			r;
+	bool		skipNested = false;
+
+	if (JB_ROOT_IS_SCALAR(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_object_field_text (jsonb ->> text operator) on a scalar")));
+	else if (JB_ROOT_IS_ARRAY(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_object_field_text (jsonb ->> text operator) on an array")));
+
+	Assert(JB_ROOT_IS_OBJECT(jb));
+
+	it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+	{
+		skipNested = true;
+
+		if (r == WJB_KEY)
+		{
+			if (klen == v.string.len && strncmp(key, v.string.val, klen) == 0)
+			{
+				text	   *result;
+
+				/*
+				 * The next thing the iterator fetches should be the value, no
+				 * matter what shape it is.
+				 */
+				r = JsonbIteratorNext(&it, &v, skipNested);
+
+				/*
+				 * if it's a scalar string it needs to be de-escaped,
+				 * otherwise just return the text
+				 */
+				if (v.type == jbvString)
+				{
+					result = cstring_to_text_with_len(v.string.val, v.string.len);
+				}
+				else if (v.type == jbvNull)
+				{
+					PG_RETURN_NULL();
+				}
+				else
+				{
+					StringInfo	jtext = makeStringInfo();
+					Jsonb	   *tjb = JsonbValueToJsonb(&v);
+
+					(void) JsonbToCString(jtext, VARDATA(tjb), -1);
+					result = cstring_to_text_with_len(jtext->data, jtext->len);
+				}
+				PG_RETURN_TEXT_P(result);
+			}
+		}
+	}
+
+	PG_RETURN_NULL();
+}
+
 Datum
 json_array_element(PG_FUNCTION_ARGS)
 {
@@ -403,6 +605,44 @@ json_array_element(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_array_element(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	int			element = PG_GETARG_INT32(1);
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			r;
+	bool		skipNested = false;
+	int			element_number = 0;
+
+	if (JB_ROOT_IS_SCALAR(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_array_element (jsonb -> int operator) on a scalar")));
+	else if (JB_ROOT_IS_OBJECT(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_array_element (jsonb -> int operator) on an object")));
+
+	Assert(JB_ROOT_IS_ARRAY(jb));
+
+	it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+	{
+		skipNested = true;
+
+		if (r == WJB_ELEM)
+		{
+			if (element_number++ == element)
+				PG_RETURN_JSONB(JsonbValueToJsonb(&v));
+		}
+	}
+
+	PG_RETURN_NULL();
+}
+
 Datum
 json_array_element_text(PG_FUNCTION_ARGS)
 {
@@ -418,6 +658,69 @@ json_array_element_text(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_array_element_text(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	int			element = PG_GETARG_INT32(1);
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			r;
+	bool		skipNested = false;
+	int			element_number = 0;
+
+
+	if (JB_ROOT_IS_SCALAR(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_array_element_text on a scalar")));
+	else if (JB_ROOT_IS_OBJECT(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			   errmsg("cannot call jsonb_array_element_text on an object")));
+
+	Assert(JB_ROOT_IS_ARRAY(jb));
+
+	it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+	{
+		skipNested = true;
+
+		if (r == WJB_ELEM)
+		{
+			if (element_number++ == element)
+			{
+				/*
+				 * if it's a scalar string it needs to be de-escaped,
+				 * otherwise just return the text
+				 */
+				text	   *result;
+
+				if (v.type == jbvString)
+				{
+					result = cstring_to_text_with_len(v.string.val, v.string.len);
+				}
+				else if (v.type == jbvNull)
+				{
+					PG_RETURN_NULL();
+				}
+				else
+				{
+					StringInfo	jtext = makeStringInfo();
+					Jsonb	   *tjb = JsonbValueToJsonb(&v);
+
+					(void) JsonbToCString(jtext, VARDATA(tjb), -1);
+					result = cstring_to_text_with_len(jtext->data, jtext->len);
+				}
+				PG_RETURN_TEXT_P(result);
+			}
+		}
+	}
+
+	PG_RETURN_NULL();
+}
+
 Datum
 json_extract_path(PG_FUNCTION_ARGS)
 {
@@ -434,9 +737,9 @@ json_extract_path_text(PG_FUNCTION_ARGS)
  * common routine for extract_path functions
  */
 static inline Datum
-get_path_all(PG_FUNCTION_ARGS, bool as_text)
+get_path_all(FunctionCallInfo fcinfo, bool as_text)
 {
-	text	   *json = PG_GETARG_TEXT_P(0);
+	text	   *json;
 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
 	text	   *result;
 	Datum	   *pathtext;
@@ -448,6 +751,8 @@ get_path_all(PG_FUNCTION_ARGS, bool as_text)
 	long		ind;
 	char	   *endptr;
 
+	json = PG_GETARG_TEXT_P(0);
+
 	if (array_contains_nulls(path))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -486,8 +791,9 @@ get_path_all(PG_FUNCTION_ARGS, bool as_text)
 	result = get_worker(json, NULL, -1, tpath, ipath, npath, as_text);
 
 	if (result != NULL)
-		PG_RETURN_TEXT_P(result);
+			PG_RETURN_TEXT_P(result);
 	else
+		/* null is NULL, regardless */
 		PG_RETURN_NULL();
 }
 
@@ -668,7 +974,7 @@ get_object_field_end(void *state, char *fname, bool isnull)
 		/*
 		 * make a text object from the string from the prevously noted json
 		 * start up to the end of the previous token (the lexer is by now
-		 * ahead of us on whatevere came after what we're interested in).
+		 * ahead of us on whatever came after what we're interested in).
 		 */
 		int			len = _state->lex->prev_token_terminator - _state->result_start;
 
@@ -822,18 +1128,139 @@ get_scalar(void *state, char *token, JsonTokenType tokentype)
 
 }
 
+Datum
+jsonb_extract_path(PG_FUNCTION_ARGS)
+{
+	return get_jsonb_path_all(fcinfo, false);
+}
+
+Datum
+jsonb_extract_path_text(PG_FUNCTION_ARGS)
+{
+	return get_jsonb_path_all(fcinfo, true);
+}
+
+static inline Datum
+get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+	Datum	   *pathtext;
+	bool	   *pathnulls;
+	int			npath;
+	int			i;
+	Jsonb	   *res;
+	bool		have_object = false,
+				have_array = false;
+	JsonbValue *jbvp = NULL;
+	JsonbValue	tv;
+	JsonbSuperHeader superHeader;
+
+	if (array_contains_nulls(path))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call function with null path elements")));
+
+	deconstruct_array(path, TEXTOID, -1, false, 'i',
+					  &pathtext, &pathnulls, &npath);
+
+	if (JB_ROOT_IS_OBJECT(jb))
+		have_object = true;
+	else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb))
+		have_array = true;
+
+	superHeader = (JsonbSuperHeader) VARDATA(jb);
+
+	for (i = 0; i < npath; i++)
+	{
+		if (have_object)
+		{
+			jbvp = findJsonbValueFromSuperHeaderLen(superHeader,
+													JB_FOBJECT,
+													VARDATA_ANY(pathtext[i]),
+													VARSIZE_ANY_EXHDR(pathtext[i]));
+		}
+		else if (have_array)
+		{
+			long		lindex;
+			uint32		index;
+			char	   *indextext = TextDatumGetCString(pathtext[i]);
+			char	   *endptr;
+
+			lindex = strtol(indextext, &endptr, 10);
+			if (*endptr != '\0' || lindex > INT_MAX || lindex < 0)
+				PG_RETURN_NULL();
+			index = (uint32) lindex;
+			jbvp = getIthJsonbValueFromSuperHeader(superHeader, index);
+		}
+		else
+		{
+			if (i == 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot call extract path from a scalar")));
+			PG_RETURN_NULL();
+		}
+
+		if (jbvp == NULL)
+			PG_RETURN_NULL();
+		else if (i == npath - 1)
+			break;
+
+		if (jbvp->type == jbvBinary)
+		{
+			JsonbIterator  *it = JsonbIteratorInit(jbvp->binary.data);
+			int				r;
+
+			r = JsonbIteratorNext(&it, &tv, true);
+			superHeader = (JsonbSuperHeader) jbvp->binary.data;
+			have_object = r == WJB_BEGIN_OBJECT;
+			have_array = r == WJB_BEGIN_ARRAY;
+		}
+		else
+		{
+			have_object = jbvp->type == jbvObject;
+			have_array = jbvp->type == jbvArray;
+		}
+	}
+
+	if (as_text)
+	{
+		if (jbvp->type == jbvString)
+			PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->string.val, jbvp->string.len));
+		else if (jbvp->type == jbvNull)
+			PG_RETURN_NULL();
+	}
+
+	res = JsonbValueToJsonb(jbvp);
+
+	if (as_text)
+	{
+		PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL,
+														VARDATA(res),
+														VARSIZE(res))));
+	}
+	else
+	{
+		/* not text mode - just hand back the jsonb */
+		PG_RETURN_JSONB(res);
+	}
+}
+
 /*
  * SQL function json_array_length(json) -> int
  */
 Datum
 json_array_length(PG_FUNCTION_ARGS)
 {
-	text	   *json = PG_GETARG_TEXT_P(0);
+	text	   *json;
 
 	AlenState  *state;
-	JsonLexContext *lex = makeJsonLexContext(json, false);
+	JsonLexContext *lex;
 	JsonSemAction *sem;
 
+	json = PG_GETARG_TEXT_P(0);
+	lex = makeJsonLexContext(json, false);
 	state = palloc0(sizeof(AlenState));
 	sem = palloc0(sizeof(JsonSemAction));
 
@@ -853,6 +1280,23 @@ json_array_length(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(state->count);
 }
 
+Datum
+jsonb_array_length(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+
+	if (JB_ROOT_IS_SCALAR(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot get array length of a scalar")));
+	else if (!JB_ROOT_IS_ARRAY(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot get array length of a non-array")));
+
+	PG_RETURN_INT32(JB_ROOT_COUNT(jb));
+}
+
 /*
  * These next two check ensure that the json is an array (since it can't be
  * a scalar or an object).
@@ -908,23 +1352,178 @@ json_each(PG_FUNCTION_ARGS)
 	return each_worker(fcinfo, false);
 }
 
+Datum
+jsonb_each(PG_FUNCTION_ARGS)
+{
+	return each_worker_jsonb(fcinfo, false);
+}
+
 Datum
 json_each_text(PG_FUNCTION_ARGS)
 {
 	return each_worker(fcinfo, true);
 }
 
+Datum
+jsonb_each_text(PG_FUNCTION_ARGS)
+{
+	return each_worker_jsonb(fcinfo, true);
+}
+
 static inline Datum
-each_worker(PG_FUNCTION_ARGS, bool as_text)
+each_worker_jsonb(FunctionCallInfo fcinfo, bool as_text)
 {
-	text	   *json = PG_GETARG_TEXT_P(0);
-	JsonLexContext *lex = makeJsonLexContext(json, true);
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	ReturnSetInfo *rsi;
+	Tuplestorestate *tuple_store;
+	TupleDesc	tupdesc;
+	TupleDesc	ret_tdesc;
+	MemoryContext old_cxt,
+				tmp_cxt;
+	bool		skipNested = false;
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			r;
+
+	if (!JB_ROOT_IS_OBJECT(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call jsonb_each%s on a non-object",
+						as_text ? "_text" : "")));
+
+	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+		rsi->expectedDesc == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that "
+						"cannot accept a set")));
+
+
+	rsi->returnMode = SFRM_Materialize;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("function returning record called in context "
+						"that cannot accept type record")));
+
+	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+	ret_tdesc = CreateTupleDescCopy(tupdesc);
+	BlessTupleDesc(ret_tdesc);
+	tuple_store =
+		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+							  false, work_mem);
+
+	MemoryContextSwitchTo(old_cxt);
+
+	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+									"jsonb_each temporary cxt",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+
+
+	it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+	{
+		skipNested = true;
+
+		if (r == WJB_KEY)
+		{
+			text	   *key;
+			HeapTuple	tuple;
+			Datum		values[2];
+			bool		nulls[2] = {false, false};
+
+			/* Use the tmp context so we can clean up after each tuple is done */
+			old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
+			key = cstring_to_text_with_len(v.string.val, v.string.len);
+
+			/*
+			 * The next thing the iterator fetches should be the value, no
+			 * matter what shape it is.
+			 */
+			r = JsonbIteratorNext(&it, &v, skipNested);
+
+			values[0] = PointerGetDatum(key);
+
+			if (as_text)
+			{
+				if (v.type == jbvNull)
+				{
+					/* a json null is an sql null in text mode */
+					nulls[1] = true;
+					values[1] = (Datum) NULL;
+				}
+				else
+				{
+					text	   *sv;
+
+					if (v.type == jbvString)
+					{
+						/* In text mode, scalar strings should be dequoted */
+						sv = cstring_to_text_with_len(v.string.val, v.string.len);
+					}
+					else
+					{
+						/* Turn anything else into a json string */
+						StringInfo	jtext = makeStringInfo();
+						Jsonb	   *jb = JsonbValueToJsonb(&v);
+
+						(void) JsonbToCString(jtext, VARDATA(jb), 2 * v.estSize);
+						sv = cstring_to_text_with_len(jtext->data, jtext->len);
+					}
+
+					values[1] = PointerGetDatum(sv);
+				}
+			}
+			else
+			{
+				/* Not in text mode, just return the Jsonb */
+				Jsonb	   *val = JsonbValueToJsonb(&v);
+
+				values[1] = PointerGetDatum(val);
+			}
+
+			tuple = heap_form_tuple(ret_tdesc, values, nulls);
+
+			tuplestore_puttuple(tuple_store, tuple);
+
+			/* clean up and switch back */
+			MemoryContextSwitchTo(old_cxt);
+			MemoryContextReset(tmp_cxt);
+		}
+	}
+
+	MemoryContextDelete(tmp_cxt);
+
+	rsi->setResult = tuple_store;
+	rsi->setDesc = ret_tdesc;
+
+	PG_RETURN_NULL();
+}
+
+
+static inline Datum
+each_worker(FunctionCallInfo fcinfo, bool as_text)
+{
+	text	   *json;
+	JsonLexContext *lex;
 	JsonSemAction *sem;
 	ReturnSetInfo *rsi;
 	MemoryContext old_cxt;
 	TupleDesc	tupdesc;
 	EachState  *state;
 
+	json = PG_GETARG_TEXT_P(0);
+
+	lex = makeJsonLexContext(json, true);
 	state = palloc0(sizeof(EachState));
 	sem = palloc0(sizeof(JsonSemAction));
 
@@ -941,11 +1540,7 @@ each_worker(PG_FUNCTION_ARGS, bool as_text)
 
 	rsi->returnMode = SFRM_Materialize;
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("function returning record called in context "
-						"that cannot accept type record")));
+	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
 
 	/* make these in a sufficiently long-lived memory context */
 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
@@ -1037,56 +1632,196 @@ each_object_field_end(void *state, char *fname, bool isnull)
 	}
 	else
 	{
-		len = _state->lex->prev_token_terminator - _state->result_start;
-		val = cstring_to_text_with_len(_state->result_start, len);
-		values[1] = PointerGetDatum(val);
-	}
+		len = _state->lex->prev_token_terminator - _state->result_start;
+		val = cstring_to_text_with_len(_state->result_start, len);
+		values[1] = PointerGetDatum(val);
+	}
+
+
+	tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
+
+	tuplestore_puttuple(_state->tuple_store, tuple);
+
+	/* clean up and switch back */
+	MemoryContextSwitchTo(old_cxt);
+	MemoryContextReset(_state->tmp_cxt);
+}
+
+static void
+each_array_start(void *state)
+{
+	EachState  *_state = (EachState *) state;
+
+	/* json structure check */
+	if (_state->lex->lex_level == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot deconstruct an array as an object")));
+}
+
+static void
+each_scalar(void *state, char *token, JsonTokenType tokentype)
+{
+	EachState  *_state = (EachState *) state;
+
+	/* json structure check */
+	if (_state->lex->lex_level == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot deconstruct a scalar")));
+
+	/* supply de-escaped value if required */
+	if (_state->next_scalar)
+		_state->normalized_scalar = token;
+}
+
+/*
+ * SQL functions json_array_elements and json_array_elements_text
+ *
+ * get the elements from a json array
+ *
+ * a lot of this processing is similar to the json_each* functions
+ */
+
+Datum
+jsonb_array_elements(PG_FUNCTION_ARGS)
+{
+	return elements_worker_jsonb(fcinfo, false);
+}
+
+Datum
+jsonb_array_elements_text(PG_FUNCTION_ARGS)
+{
+	return elements_worker_jsonb(fcinfo, true);
+}
+
+static inline Datum
+elements_worker_jsonb(FunctionCallInfo fcinfo, bool as_text)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	ReturnSetInfo *rsi;
+	Tuplestorestate *tuple_store;
+	TupleDesc	tupdesc;
+	TupleDesc	ret_tdesc;
+	MemoryContext old_cxt,
+				tmp_cxt;
+	bool		skipNested = false;
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			r;
+
+	if (JB_ROOT_IS_SCALAR(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot extract elements from a scalar")));
+	else if (!JB_ROOT_IS_ARRAY(jb))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot extract elements from an object")));
+
+	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+		rsi->expectedDesc == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that "
+						"cannot accept a set")));
+
+
+	rsi->returnMode = SFRM_Materialize;
+
+	/* it's a simple type, so don't use get_call_result_type() */
+	tupdesc = rsi->expectedDesc;
+
+	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+	ret_tdesc = CreateTupleDescCopy(tupdesc);
+	BlessTupleDesc(ret_tdesc);
+	tuple_store =
+		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+							  false, work_mem);
+
+	MemoryContextSwitchTo(old_cxt);
+
+	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+									"jsonb_each temporary cxt",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+
+
+	it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+	while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+	{
+		skipNested = true;
 
+		if (r == WJB_ELEM)
+		{
+			HeapTuple	tuple;
+			Datum		values[1];
+			bool		nulls[1] = {false};
 
-	tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
+			/* use the tmp context so we can clean up after each tuple is done */
+			old_cxt = MemoryContextSwitchTo(tmp_cxt);
 
-	tuplestore_puttuple(_state->tuple_store, tuple);
+			if (!as_text)
+			{
+				Jsonb	   *val = JsonbValueToJsonb(&v);
 
-	/* clean up and switch back */
-	MemoryContextSwitchTo(old_cxt);
-	MemoryContextReset(_state->tmp_cxt);
-}
+				values[0] = PointerGetDatum(val);
+			}
+			else
+			{
+				if (v.type == jbvNull)
+				{
+					/* a json null is an sql null in text mode */
+					nulls[0] = true;
+					values[0] = (Datum) NULL;
+				}
+				else
+				{
+					text	   *sv;
+
+					if (v.type == jbvString)
+					{
+						/* in text mode scalar strings should be dequoted */
+						sv = cstring_to_text_with_len(v.string.val, v.string.len);
+					}
+					else
+					{
+						/* turn anything else into a json string */
+						StringInfo	jtext = makeStringInfo();
+						Jsonb	   *jb = JsonbValueToJsonb(&v);
+
+						(void) JsonbToCString(jtext, VARDATA(jb), 2 * v.estSize);
+						sv = cstring_to_text_with_len(jtext->data, jtext->len);
+					}
+
+					values[0] = PointerGetDatum(sv);
+				}
+			}
 
-static void
-each_array_start(void *state)
-{
-	EachState  *_state = (EachState *) state;
+			tuple = heap_form_tuple(ret_tdesc, values, nulls);
 
-	/* json structure check */
-	if (_state->lex->lex_level == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("cannot deconstruct an array as an object")));
-}
+			tuplestore_puttuple(tuple_store, tuple);
 
-static void
-each_scalar(void *state, char *token, JsonTokenType tokentype)
-{
-	EachState  *_state = (EachState *) state;
+			/* clean up and switch back */
+			MemoryContextSwitchTo(old_cxt);
+			MemoryContextReset(tmp_cxt);
+		}
+	}
 
-	/* json structure check */
-	if (_state->lex->lex_level == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("cannot deconstruct a scalar")));
+	MemoryContextDelete(tmp_cxt);
 
-	/* supply de-escaped value if required */
-	if (_state->next_scalar)
-		_state->normalized_scalar = token;
+	rsi->setResult = tuple_store;
+	rsi->setDesc = ret_tdesc;
+
+	PG_RETURN_NULL();
 }
 
-/*
- * SQL functions json_array_elements and json_array_elements_text
- *
- * get the elements from a json array
- *
- * a lot of this processing is similar to the json_each* functions
- */
 Datum
 json_array_elements(PG_FUNCTION_ARGS)
 {
@@ -1100,7 +1835,7 @@ json_array_elements_text(PG_FUNCTION_ARGS)
 }
 
 static inline Datum
-elements_worker(PG_FUNCTION_ARGS, bool as_text)
+elements_worker(FunctionCallInfo fcinfo, bool as_text)
 {
 	text	   *json = PG_GETARG_TEXT_P(0);
 
@@ -1270,8 +2005,15 @@ elements_scalar(void *state, char *token, JsonTokenType tokentype)
  * which is in turn partly adapted from record_out.
  *
  * The json is decomposed into a hash table, in which each
- * field in the record is then looked up by name.
+ * field in the record is then looked up by name. For jsonb
+ * we fetch the values direct from the object.
  */
+Datum
+jsonb_populate_record(PG_FUNCTION_ARGS)
+{
+	return populate_record_worker(fcinfo, true);
+}
+
 Datum
 json_populate_record(PG_FUNCTION_ARGS)
 {
@@ -1285,11 +2027,14 @@ json_to_record(PG_FUNCTION_ARGS)
 }
 
 static inline Datum
-populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
+populate_record_worker(FunctionCallInfo fcinfo, bool have_record_arg)
 {
+	Oid			argtype;
+	Oid			jtype = get_fn_expr_argtype(fcinfo->flinfo, have_record_arg ? 1 : 0);
 	text	   *json;
+	Jsonb	   *jb = NULL;
 	bool		use_json_as_text;
-	HTAB	   *json_hash;
+	HTAB	   *json_hash = NULL;
 	HeapTupleHeader rec = NULL;
 	Oid			tupType = InvalidOid;
 	int32		tupTypmod = -1;
@@ -1301,19 +2046,20 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 	int			i;
 	Datum	   *values;
 	bool	   *nulls;
-	char		fname[NAMEDATALEN];
-	JsonHashEntry *hashentry;
+
+	Assert(jtype == JSONOID || jtype == JSONBOID);
+
+	use_json_as_text = PG_ARGISNULL(have_record_arg ? 2 : 1) ? false :
+		PG_GETARG_BOOL(have_record_arg ? 2 : 1);
 
 	if (have_record_arg)
 	{
-		Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
-
-		use_json_as_text = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);
+		argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
 
 		if (!type_is_rowtype(argtype))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("first argument of json_populate_record must be a row type")));
+					 errmsg("first argument of json%s_populate_record must be a row type", jtype == JSONBOID ? "b" : "")));
 
 		if (PG_ARGISNULL(0))
 		{
@@ -1340,19 +2086,16 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 			tupTypmod = HeapTupleHeaderGetTypMod(rec);
 		}
 
-		json = PG_GETARG_TEXT_P(1);
+		tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 	}
 	else
-	{
-		/* json_to_record case */
+	{							/* json{b}_to_record case */
 
 		use_json_as_text = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1);
 
 		if (PG_ARGISNULL(0))
 			PG_RETURN_NULL();
 
-		json = PG_GETARG_TEXT_P(0);
-
 		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1362,11 +2105,13 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 							 "using a column definition list.")));
 	}
 
-	json_hash = get_json_object_as_hash(json, "json_populate_record",
-										use_json_as_text);
-
-	if (have_record_arg)
+	if (jtype == JSONOID)
 	{
+		/* just get the text */
+		json = PG_GETARG_TEXT_P(have_record_arg ? 1 : 0);
+
+		json_hash = get_json_object_as_hash(json, "json_populate_record", use_json_as_text);
+
 		/*
 		 * if the input json is empty, we can only skip the rest if we were
 		 * passed in a non-null record, since otherwise there may be issues
@@ -1375,8 +2120,14 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 		if (hash_get_num_entries(json_hash) == 0 && rec)
 			PG_RETURN_POINTER(rec);
 
+	}
+	else
+	{
+		jb = PG_GETARG_JSONB(have_record_arg ? 1 : 0);
 
-		tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+		/* same logic as for json */
+		if (!have_record_arg && rec)
+			PG_RETURN_POINTER(rec);
 	}
 
 	ncolumns = tupdesc->natts;
@@ -1439,7 +2190,9 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 	{
 		ColumnIOData *column_info = &my_extra->columns[i];
 		Oid			column_type = tupdesc->attrs[i]->atttypid;
-		char	   *value;
+		JsonbValue *v = NULL;
+		char		fname[NAMEDATALEN];
+		JsonHashEntry *hashentry = NULL;
 
 		/* Ignore dropped columns in datatype */
 		if (tupdesc->attrs[i]->attisdropped)
@@ -1448,9 +2201,20 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 			continue;
 		}
 
-		memset(fname, 0, NAMEDATALEN);
-		strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
-		hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+		if (jtype == JSONOID)
+		{
+
+			memset(fname, 0, NAMEDATALEN);
+			strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
+			hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+		}
+		else
+		{
+			char	   *key = NameStr(tupdesc->attrs[i]->attname);
+
+			v = findJsonbValueFromSuperHeaderLen(VARDATA(jb), JB_FOBJECT, key,
+												 strlen(key));
+		}
 
 		/*
 		 * we can't just skip here if the key wasn't found since we might have
@@ -1460,7 +2224,8 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 		 * then every field which we don't populate needs to be run through
 		 * the input function just in case it's a domain type.
 		 */
-		if (hashentry == NULL && rec)
+		if (((jtype == JSONOID && hashentry == NULL) ||
+			 (jtype == JSONBOID && v == NULL)) && rec)
 			continue;
 
 		/*
@@ -1475,7 +2240,8 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 						  fcinfo->flinfo->fn_mcxt);
 			column_info->column_type = column_type;
 		}
-		if (hashentry == NULL || hashentry->isnull)
+		if ((jtype == JSONOID && (hashentry == NULL || hashentry->isnull)) ||
+			(jtype == JSONBOID && (v == NULL || v->type == jbvNull)))
 		{
 			/*
 			 * need InputFunctionCall to happen even for nulls, so that domain
@@ -1488,9 +2254,33 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 		}
 		else
 		{
-			value = hashentry->val;
+			char	   *s = NULL;
 
-			values[i] = InputFunctionCall(&column_info->proc, value,
+			if (jtype == JSONOID)
+			{
+				/* already done the hard work in the json case */
+				s = hashentry->val;
+			}
+			else
+			{
+				if (v->type == jbvString)
+					s = pnstrdup(v->string.val, v->string.len);
+				else if (v->type == jbvBool)
+					s = pnstrdup((v->boolean) ? "t" : "f", 1);
+				else if (v->type == jbvNumeric)
+					s = DatumGetCString(DirectFunctionCall1(numeric_out,
+											   PointerGetDatum(v->numeric)));
+				else if (!use_json_as_text)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("cannot populate with a nested object unless use_json_as_text is true")));
+				else if (v->type == jbvBinary)
+					s = JsonbToCString(NULL, v->binary.data, v->binary.len);
+				else
+					elog(ERROR, "invalid jsonb type");
+			}
+
+			values[i] = InputFunctionCall(&column_info->proc, s,
 										  column_info->typioparam,
 										  tupdesc->attrs[i]->atttypmod);
 			nulls[i] = false;
@@ -1655,6 +2445,134 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype)
  * is pushed down into the semantic action handlers so it's done
  * per object in the array.
  */
+Datum
+jsonb_populate_recordset(PG_FUNCTION_ARGS)
+{
+	return populate_recordset_worker(fcinfo, true);
+}
+
+static void
+make_row_from_rec_and_jsonb(Jsonb * element, PopulateRecordsetState *state)
+{
+	Datum	   *values;
+	bool	   *nulls;
+	int			i;
+	RecordIOData *my_extra = state->my_extra;
+	int			ncolumns = my_extra->ncolumns;
+	TupleDesc	tupdesc = state->ret_tdesc;
+	HeapTupleHeader rec = state->rec;
+	HeapTuple	rettuple;
+
+	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+	nulls = (bool *) palloc(ncolumns * sizeof(bool));
+
+	if (state->rec)
+	{
+		HeapTupleData tuple;
+
+		/* Build a temporary HeapTuple control structure */
+		tuple.t_len = HeapTupleHeaderGetDatumLength(state->rec);
+		ItemPointerSetInvalid(&(tuple.t_self));
+		tuple.t_tableOid = InvalidOid;
+		tuple.t_data = state->rec;
+
+		/* Break down the tuple into fields */
+		heap_deform_tuple(&tuple, tupdesc, values, nulls);
+	}
+	else
+	{
+		for (i = 0; i < ncolumns; ++i)
+		{
+			values[i] = (Datum) 0;
+			nulls[i] = true;
+		}
+	}
+
+	for (i = 0; i < ncolumns; ++i)
+	{
+		ColumnIOData *column_info = &my_extra->columns[i];
+		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		JsonbValue *v = NULL;
+		char	   *key;
+
+		/* Ignore dropped columns in datatype */
+		if (tupdesc->attrs[i]->attisdropped)
+		{
+			nulls[i] = true;
+			continue;
+		}
+
+		key = NameStr(tupdesc->attrs[i]->attname);
+
+		v = findJsonbValueFromSuperHeaderLen(VARDATA(element), JB_FOBJECT,
+											 key, strlen(key));
+
+		/*
+		 * We can't just skip here if the key wasn't found since we might have
+		 * a domain to deal with. If we were passed in a non-null record
+		 * datum, we assume that the existing values are valid (if they're
+		 * not, then it's not our fault), but if we were passed in a null,
+		 * then every field which we don't populate needs to be run through
+		 * the input function just in case it's a domain type.
+		 */
+		if (v == NULL && rec)
+			continue;
+
+		/*
+		 * Prepare to convert the column value from text
+		 */
+		if (column_info->column_type != column_type)
+		{
+			getTypeInputInfo(column_type,
+							 &column_info->typiofunc,
+							 &column_info->typioparam);
+			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+						  state->fn_mcxt);
+			column_info->column_type = column_type;
+		}
+		if (v == NULL || v->type == jbvNull)
+		{
+			/*
+			 * Need InputFunctionCall to happen even for nulls, so that domain
+			 * checks are done
+			 */
+			values[i] = InputFunctionCall(&column_info->proc, NULL,
+										  column_info->typioparam,
+										  tupdesc->attrs[i]->atttypmod);
+			nulls[i] = true;
+		}
+		else
+		{
+			char	   *s = NULL;
+
+			if (v->type == jbvString)
+				s = pnstrdup(v->string.val, v->string.len);
+			else if (v->type == jbvBool)
+				s = pnstrdup((v->boolean) ? "t" : "f", 1);
+			else if (v->type == jbvNumeric)
+				s = DatumGetCString(DirectFunctionCall1(numeric_out,
+											   PointerGetDatum(v->numeric)));
+			else if (!state->use_json_as_text)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot populate with a nested object unless use_json_as_text is true")));
+			else if (v->type == jbvBinary)
+				s = JsonbToCString(NULL, v->binary.data, v->binary.len);
+			else
+				elog(ERROR, "invalid jsonb type");
+
+			values[i] = InputFunctionCall(&column_info->proc, s,
+										  column_info->typioparam,
+										  tupdesc->attrs[i]->atttypmod);
+			nulls[i] = false;
+		}
+	}
+
+	rettuple = heap_form_tuple(tupdesc, values, nulls);
+
+	tuplestore_puttuple(state->tuple_store, rettuple);
+}
+
 Datum
 json_populate_recordset(PG_FUNCTION_ARGS)
 {
@@ -1671,10 +2589,10 @@ json_to_recordset(PG_FUNCTION_ARGS)
  * common worker for json_populate_recordset() and json_to_recordset()
  */
 static inline Datum
-populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
+populate_recordset_worker(FunctionCallInfo fcinfo, bool have_record_arg)
 {
 	Oid			argtype;
-	text	   *json;
+	Oid			jtype = get_fn_expr_argtype(fcinfo->flinfo, have_record_arg ? 1 : 0);
 	bool		use_json_as_text;
 	ReturnSetInfo *rsi;
 	MemoryContext old_cxt;
@@ -1684,8 +2602,6 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 	TupleDesc	tupdesc;
 	RecordIOData *my_extra;
 	int			ncolumns;
-	JsonLexContext *lex;
-	JsonSemAction *sem;
 	PopulateRecordsetState *state;
 
 	if (have_record_arg)
@@ -1721,7 +2637,8 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 
 	/*
 	 * get the tupdesc from the result set info - it must be a record type
-	 * because we already checked that arg1 is a record type.
+	 * because we already checked that arg1 is a record type, or we're in a
+	 * to_record function which returns a setof record.
 	 */
 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 		ereport(ERROR,
@@ -1729,29 +2646,12 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 				 errmsg("function returning record called in context "
 						"that cannot accept type record")));
 
-	state = palloc0(sizeof(PopulateRecordsetState));
-	sem = palloc0(sizeof(JsonSemAction));
-
-
-	/* make these in a sufficiently long-lived memory context */
-	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
-
-	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
-	BlessTupleDesc(state->ret_tdesc);
-	state->tuple_store =
-		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
-							  false, work_mem);
-
-	MemoryContextSwitchTo(old_cxt);
-
 	/* if the json is null send back an empty set */
 	if (have_record_arg)
 	{
 		if (PG_ARGISNULL(1))
 			PG_RETURN_NULL();
 
-		json = PG_GETARG_TEXT_P(1);
-
 		if (PG_ARGISNULL(0))
 			rec = NULL;
 		else
@@ -1759,11 +2659,9 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 	}
 	else
 	{
-		if (PG_ARGISNULL(0))
+		if (PG_ARGISNULL(1))
 			PG_RETURN_NULL();
 
-		json = PG_GETARG_TEXT_P(0);
-
 		rec = NULL;
 	}
 
@@ -1771,8 +2669,6 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 	tupTypmod = tupdesc->tdtypmod;
 	ncolumns = tupdesc->natts;
 
-	lex = makeJsonLexContext(json, true);
-
 	/*
 	 * We arrange to look up the needed I/O info just once per series of
 	 * calls, assuming the record type doesn't change underneath us.
@@ -1801,23 +2697,80 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
 		my_extra->ncolumns = ncolumns;
 	}
 
-	sem->semstate = (void *) state;
-	sem->array_start = populate_recordset_array_start;
-	sem->array_element_start = populate_recordset_array_element_start;
-	sem->scalar = populate_recordset_scalar;
-	sem->object_field_start = populate_recordset_object_field_start;
-	sem->object_field_end = populate_recordset_object_field_end;
-	sem->object_start = populate_recordset_object_start;
-	sem->object_end = populate_recordset_object_end;
+	state = palloc0(sizeof(PopulateRecordsetState));
 
-	state->lex = lex;
+	/* make these in a sufficiently long-lived memory context */
+	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+	state->ret_tdesc = CreateTupleDescCopy(tupdesc);;
+	BlessTupleDesc(state->ret_tdesc);
+	state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
+											   SFRM_Materialize_Random,
+											   false, work_mem);
+	MemoryContextSwitchTo(old_cxt);
 
 	state->my_extra = my_extra;
 	state->rec = rec;
 	state->use_json_as_text = use_json_as_text;
 	state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
 
-	pg_parse_json(lex, sem);
+	if (jtype == JSONOID)
+	{
+		text	   *json = PG_GETARG_TEXT_P(have_record_arg ? 1 : 0);
+		JsonLexContext *lex;
+		JsonSemAction *sem;
+
+		sem = palloc0(sizeof(JsonSemAction));
+
+		lex = makeJsonLexContext(json, true);
+
+		sem->semstate = (void *) state;
+		sem->array_start = populate_recordset_array_start;
+		sem->array_element_start = populate_recordset_array_element_start;
+		sem->scalar = populate_recordset_scalar;
+		sem->object_field_start = populate_recordset_object_field_start;
+		sem->object_field_end = populate_recordset_object_field_end;
+		sem->object_start = populate_recordset_object_start;
+		sem->object_end = populate_recordset_object_end;
+
+		state->lex = lex;
+
+		pg_parse_json(lex, sem);
+
+	}
+	else
+	{
+		Jsonb	   *jb;
+		JsonbIterator *it;
+		JsonbValue	v;
+		bool		skipNested = false;
+		int			r;
+
+		Assert(jtype == JSONBOID);
+		jb = PG_GETARG_JSONB(have_record_arg ? 1 : 0);
+
+		if (JB_ROOT_IS_SCALAR(jb) || !JB_ROOT_IS_ARRAY(jb))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			   errmsg("cannot call jsonb_populate_recordset on non-array")));
+
+		it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+		while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+		{
+			skipNested = true;
+
+			if (r == WJB_ELEM)
+			{
+				Jsonb	   *element = JsonbValueToJsonb(&v);
+
+				if (!JB_ROOT_IS_OBJECT(element))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("jsonb_populate_recordset argument must be an array of objects")));
+				make_row_from_rec_and_jsonb(element, state);
+			}
+		}
+	}
 
 	rsi->setResult = state->tuple_store;
 	rsi->setDesc = state->ret_tdesc;
@@ -2067,3 +3020,19 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull)
 		hashentry->val = _state->saved_scalar;
 	}
 }
+
+/*
+ * findJsonbValueFromSuperHeader() wrapper that sets up JsonbValue key string.
+ */
+static JsonbValue *
+findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader, uint32 flags,
+								 char *key, uint32 keylen)
+{
+	JsonbValue	k;
+
+	k.type = jbvString;
+	k.string.val = key;
+	k.string.len = keylen;
+
+	return findJsonbValueFromSuperHeader(sheader, flags, NULL, &k);
+}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451dda01ffb7cc3d02718f69c25c63ff42a58..64eb0f8d16e668adfaca3e6527d4d97b82ecbe34 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -626,6 +626,44 @@ numeric_out_sci(Numeric num, int scale)
 	return str;
 }
 
+/*
+ * numeric_normalize() -
+ *
+ *	Output function for numeric data type without trailing zeroes.
+ */
+char *
+numeric_normalize(Numeric num)
+{
+	NumericVar	x;
+	char	   *str;
+	int			orig, last;
+
+	/*
+	 * Handle NaN
+	 */
+	if (NUMERIC_IS_NAN(num))
+		return pstrdup("NaN");
+
+	init_var_from_num(num, &x);
+
+	str = get_str_from_var(&x);
+
+	orig = last = strlen(str) - 1;
+
+	for (;;)
+	{
+		if (last == 0 || str[last] != '0')
+			break;
+
+		last--;
+	}
+
+	if (last > 0 && last != orig)
+		str[last] = '\0';
+
+	return str;
+}
+
 /*
  *		numeric_recv			- converts external binary format to numeric
  *
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 7c1bc1d04cdca9d95d3db4e7883b4d6f3f1a1947..262311355702a35b26fda0696acf5bec093ea085 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -777,6 +777,33 @@ DATA(insert (	4017   25 25 12 s	665 4000 0 ));
 DATA(insert (	4017   25 25 14 s	667 4000 0 ));
 DATA(insert (	4017   25 25 15 s	666 4000 0 ));
 
+/*
+ * btree jsonb_ops
+ */
+DATA(insert (	4033   3802 3802 1 s	3242 403 0 ));
+DATA(insert (	4033   3802 3802 2 s	3244 403 0 ));
+DATA(insert (	4033   3802 3802 3 s	3240 403 0 ));
+DATA(insert (	4033   3802 3802 4 s	3245 403 0 ));
+DATA(insert (	4033   3802 3802 5 s	3243 403 0 ));
+
+/*
+ * hash jsonb ops
+ */
+DATA(insert (	4034   3802 3802 1 s 3240 405 0 ));
+
+/*
+ * GIN jsonb ops
+ */
+DATA(insert (	4036   3802 3802 7 s 3246 2742 0 ));
+DATA(insert (	4036   3802 25 9 s 3247 2742 0 ));
+DATA(insert (	4036   3802 1009 10 s 3248 2742 0 ));
+DATA(insert (	4036   3802 1009 11 s 3249 2742 0 ));
+
+/*
+ * GIN jsonb hash ops
+ */
+DATA(insert (	4037   3802 3802 7 s 3246 2742 0 ));
+
 /*
  * SP-GiST range_ops
  */
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index e3773e97593457eb1052739d1e2b47c8a5017598..b28dd563a8c9f0de05a309621f41adb583551c42 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -138,6 +138,7 @@ DATA(insert (	3522   3500 3500 1 3514 ));
 DATA(insert (	3626   3614 3614 1 3622 ));
 DATA(insert (	3683   3615 3615 1 3668 ));
 DATA(insert (	3901   3831 3831 1 3870 ));
+DATA(insert (	4033   3802 3802 1 4044 ));
 
 
 /* hash */
@@ -175,6 +176,7 @@ DATA(insert (	2235   1033 1033 1 329 ));
 DATA(insert (	2969   2950 2950 1 2963 ));
 DATA(insert (	3523   3500 3500 1 3515 ));
 DATA(insert (	3903   3831 3831 1 3902 ));
+DATA(insert (	4034   3802 3802 1 4045 ));
 
 
 /* gist */
@@ -387,7 +389,16 @@ DATA(insert (	3659   3614 3614 3 3657 ));
 DATA(insert (	3659   3614 3614 4 3658 ));
 DATA(insert (	3659   3614 3614 5 2700 ));
 DATA(insert (	3659   3614 3614 6 3921 ));
-
+DATA(insert (	4036   3802 3802 1 3480 ));
+DATA(insert (	4036   3802 3802 2 3482 ));
+DATA(insert (	4036   3802 3802 3 3483 ));
+DATA(insert (	4036   3802 3802 4 3484 ));
+DATA(insert (	4036   3802 3802 6 3488 ));
+DATA(insert (	4037   3802 3802 1 351 ));
+DATA(insert (	4037   3802 3802 2 3485 ));
+DATA(insert (	4037   3802 3802 3 3486 ));
+DATA(insert (	4037   3802 3802 4 3487 ));
+DATA(insert (	4037   3802 3802 6 3489 ));
 
 /* sp-gist */
 DATA(insert (	3474   3831 3831 1 3469 ));
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 3544d0a6adc991726f9d9fb6eaf337658cf932fd..e037957472f3761f31849007ab50cc6c8d719981 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -359,4 +359,8 @@ DATA(insert ( 1560 1560 1685 i f ));
 DATA(insert ( 1562 1562 1687 i f ));
 DATA(insert ( 1700 1700 1703 i f ));
 
+/* json to/from jsonb */
+DATA(insert ( 114 3802 0 e i ));
+DATA(insert ( 3802 114 0 e i ));
+
 #endif   /* PG_CAST_H */
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 6860637482b2cb7df21cecdac0e654e5c3c4f47c..63a40a8412cce81c4d609eaec62169010ae7b201 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -228,5 +228,9 @@ DATA(insert (	4000	range_ops			PGNSP PGUID 3474  3831 t 0 ));
 DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
 DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
 DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
+DATA(insert (	403		jsonb_ops			PGNSP PGUID 4033  3802 t 0 ));
+DATA(insert (	405		jsonb_ops			PGNSP PGUID 4034  3802 t 0 ));
+DATA(insert (	2742	jsonb_ops			PGNSP PGUID 4036  3802 t 25 ));
+DATA(insert (	2742	jsonb_hash_ops		PGNSP PGUID 4037  3802 f 23 ));
 
 #endif   /* PG_OPCLASS_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index e07d6d9ef97189dda3cd5d75caa5ccdea439d2ca..ac09034f3d0e5eb1d522f2e84654d479dbfb9003 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1769,8 +1769,41 @@ DATA(insert OID = 3966 (  "#>"	   PGNSP PGUID b f f 114 1009 114 0 0 json_extrac
 DESCR("get value from json with path elements");
 DATA(insert OID = 3967 (  "#>>"    PGNSP PGUID b f f 114 1009 25 0 0 json_extract_path_text_op - - ));
 DESCR("get value from json as text with path elements");
-
-
+DATA(insert OID = 3211 (  "->"	   PGNSP PGUID b f f 3802 25 3802 0 0 jsonb_object_field - - ));
+DESCR("get jsonb object field");
+DATA(insert OID = 3477 (  "->>"    PGNSP PGUID b f f 3802 25 25 0 0 jsonb_object_field_text - - ));
+DESCR("get jsonb object field as text");
+DATA(insert OID = 3212 (  "->"	   PGNSP PGUID b f f 3802 23 3802 0 0 jsonb_array_element - - ));
+DESCR("get jsonb array element");
+DATA(insert OID = 3481 (  "->>"    PGNSP PGUID b f f 3802 23 25 0 0 jsonb_array_element_text - - ));
+DESCR("get jsonb array element as text");
+DATA(insert OID = 3213 (  "#>"	   PGNSP PGUID b f f 3802 1009 3802 0 0 jsonb_extract_path_op - - ));
+DESCR("get value from jsonb with path elements");
+DATA(insert OID = 3206 (  "#>>"    PGNSP PGUID b f f 3802 1009 25 0 0 jsonb_extract_path_text_op - - ));
+DESCR("get value from jsonb as text with path elements");
+DATA(insert OID = 3240 (  "="	 PGNSP PGUID b t t 3802 3802  16 3240 3241 jsonb_eq eqsel eqjoinsel ));
+DESCR("equal");
+DATA(insert OID = 3241 (  "<>"	 PGNSP PGUID b f f 3802 3802  16 3241 3240 jsonb_ne neqsel neqjoinsel ));
+DESCR("not equal");
+DATA(insert OID = 3242 (  "<"		PGNSP PGUID b f f 3802 3802 16 3243 3245 jsonb_lt scalarltsel scalarltjoinsel ));
+DESCR("less than");
+DATA(insert OID = 3243 (  ">"		PGNSP PGUID b f f 3802 3802 16 3242 3244 jsonb_gt scalargtsel scalargtjoinsel ));
+DESCR("greater than");
+DATA(insert OID = 3244 (  "<="	PGNSP PGUID b f f 3802 3802 16 3245 3243 jsonb_le scalarltsel scalarltjoinsel ));
+DESCR("less than or equal to");
+DATA(insert OID = 3245 (  ">="	PGNSP PGUID b f f 3802 3802 16 3244 3242 jsonb_ge scalargtsel scalargtjoinsel ));
+DESCR("greater than or equal to");
+/* No commutator? */
+DATA(insert OID = 3246 (  "@>"	   PGNSP PGUID b f f 3802 3802 16 0 3250 jsonb_contains contsel contjoinsel ));
+DESCR("contains");
+DATA(insert OID = 3247 (  "?"	   PGNSP PGUID b f f 3802 25 16 0 0 jsonb_exists contsel contjoinsel ));
+DESCR("exists");
+DATA(insert OID = 3248 (  "?|"	   PGNSP PGUID b f f 3802 1009 16 0 0 jsonb_exists_any contsel contjoinsel ));
+DESCR("exists any");
+DATA(insert OID = 3249 (  "?&"	   PGNSP PGUID b f f 3802 1009 16 0 0 jsonb_exists_all contsel contjoinsel ));
+DESCR("exists all");
+DATA(insert OID = 3250 (  "<@"	   PGNSP PGUID b f f 3802 3802 16 0 3246 jsonb_contained contsel contjoinsel ));
+DESCR("contained");
 
 /*
  * function prototypes
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index 229dcb12888ad9d71b6116c7dbe056900f65ae5f..775be86c1af34db287f81d9cf0a94df41fa56c8a 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -147,6 +147,11 @@ DATA(insert OID = 3474 (	4000	range_ops		PGNSP PGUID ));
 DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
 DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
 DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
+DATA(insert OID = 4033 (	403		jsonb_ops		PGNSP PGUID ));
+DATA(insert OID = 4034 (	405		jsonb_ops		PGNSP PGUID ));
+DATA(insert OID = 4035 (	783		jsonb_ops		PGNSP PGUID ));
+DATA(insert OID = 4036 (	2742	jsonb_ops		PGNSP PGUID ));
+DATA(insert OID = 4037 (	2742	jsonb_hash_ops	PGNSP PGUID ));
 #define TEXT_SPGIST_FAM_OID 4017
 
 #endif   /* PG_OPFAMILY_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 4bd23fc5fde6ca3d040d83551b7e50e333586589..56f0f11ebe27d1826b0db6c518e789c37fa85ac9 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4173,6 +4173,8 @@ DESCR("get value from json as text with path elements");
 DATA(insert OID = 3954 (  json_extract_path_text_op PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 25 "114 1009" _null_ _null_ "{from_json,path_elems}" _null_ json_extract_path_text _null_ _null_ _null_ ));
 DATA(insert OID = 3955 (  json_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 114 "114" "{114,114}" "{i,o}" "{from_json,value}" _null_ json_array_elements _null_ _null_ _null_ ));
 DESCR("key value pairs of a json object");
+DATA(insert OID = 3969 (  json_array_elements_text	PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" "{114,25}" "{i,o}" "{from_json,value}" _null_ json_array_elements_text _null_ _null_ _null_ ));
+DESCR("elements of json array");
 DATA(insert OID = 3956 (  json_array_length			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "114" _null_ _null_ _null_ _null_ json_array_length _null_ _null_ _null_ ));
 DESCR("length of json array");
 DATA(insert OID = 3957 (  json_object_keys			PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" _null_ _null_ _null_ _null_ json_object_keys _null_ _null_ _null_ ));
@@ -4191,8 +4193,6 @@ DATA(insert OID = 3205 (  json_to_recordset  PGNSP PGUID 12 1 100 0 0 f f f f f
 DESCR("get set of records with fields from a json array of objects");
 DATA(insert OID = 3968 (  json_typeof              PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "114" _null_ _null_ _null_ _null_ json_typeof _null_ _null_ _null_ ));
 DESCR("get the type of a json value");
-DATA(insert OID = 3969 (  json_array_elements_text	PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" "{114,25}" "{i,o}" "{from_json,value}" _null_ json_array_elements_text _null_ _null_ _null_ ));
-DESCR("elements of json array");
 
 /* uuid */
 DATA(insert OID = 2952 (  uuid_in		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));
@@ -4510,6 +4510,83 @@ DESCR("I/O");
 DATA(insert OID = 3774 (  regdictionarysend PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "3769" _null_ _null_ _null_ _null_ regdictionarysend _null_ _null_ _null_ ));
 DESCR("I/O");
 
+/* jsonb */
+DATA(insert OID =  3806 (  jsonb_in			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3802 "2275" _null_ _null_ _null_ _null_ jsonb_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID =  3805 (  jsonb_recv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3802 "2281" _null_ _null_ _null_ _null_ jsonb_recv _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID =  3804 (  jsonb_out		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3802" _null_ _null_ _null_ _null_ jsonb_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID =  3803 (  jsonb_send		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "3802" _null_ _null_ _null_ _null_	jsonb_send _null_ _null_ _null_ ));
+DESCR("I/O");
+
+DATA(insert OID = 3478 (  jsonb_object_field			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ jsonb_object_field _null_ _null_ _null_ ));
+DATA(insert OID = 3214 (  jsonb_object_field_text	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25  "3802 25" _null_ _null_ "{from_json, field_name}" _null_ jsonb_object_field_text _null_ _null_ _null_ ));
+DATA(insert OID = 3215 (  jsonb_array_element		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 23" _null_ _null_ "{from_json, element_index}" _null_ jsonb_array_element _null_ _null_ _null_ ));
+DATA(insert OID = 3216 (  jsonb_array_element_text	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25  "3802 23" _null_ _null_ "{from_json, element_index}" _null_ jsonb_array_element_text _null_ _null_ _null_ ));
+DATA(insert OID = 3217 (  jsonb_extract_path			PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 3802 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
+DESCR("get value from jsonb with path elements");
+DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
+DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
+DESCR("get value from jsonb as text with path elements");
+DATA(insert OID = 3218 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
+DATA(insert OID = 3219 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
+DESCR("elements of a jsonb array");
+DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
+DESCR("elements of jsonb array");
+DATA(insert OID = 3207 (  jsonb_array_length			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "3802" _null_ _null_ _null_ _null_ jsonb_array_length _null_ _null_ _null_ ));
+DESCR("length of jsonb array");
+DATA(insert OID = 3931 (  jsonb_object_keys			PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_object_keys _null_ _null_ _null_ ));
+DESCR("get jsonb object keys");
+DATA(insert OID = 3208 (  jsonb_each				   PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 2249 "3802" "{3802,25,3802}" "{i,o,o}" "{from_json,key,value}" _null_ jsonb_each _null_ _null_ _null_ ));
+DESCR("key value pairs of a jsonb object");
+DATA(insert OID = 3932 (  jsonb_each_text		   PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 2249 "3802" "{3802,25,25}" "{i,o,o}" "{from_json,key,value}" _null_ jsonb_each_text _null_ _null_ _null_ ));
+DESCR("key value pairs of a jsonb object");
+DATA(insert OID = 3209 (  jsonb_populate_record    PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2283 "2283 3802 16" _null_ _null_ _null_ _null_ jsonb_populate_record _null_ _null_ _null_ ));
+DESCR("get record fields from a jsonb object");
+DATA(insert OID = 3475 (  jsonb_populate_recordset	PGNSP PGUID 12 1 100 0 0 f f f f f t s 3 0 2283 "2283 3802 16" _null_ _null_ _null_ _null_ jsonb_populate_recordset _null_ _null_ _null_ ));
+DESCR("get set of records with fields from a jsonb array of objects");
+DATA(insert OID = 3210 (  jsonb_typeof				PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_typeof _null_ _null_ _null_ ));
+DESCR("get the type of a jsonb value");
+DATA(insert OID = 4038 (  jsonb_ne		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_ne _null_ _null_ _null_ ));
+DATA(insert OID = 4039 (  jsonb_lt		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_lt _null_ _null_ _null_ ));
+DATA(insert OID = 4040 (  jsonb_gt		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_gt _null_ _null_ _null_ ));
+DATA(insert OID = 4041 (  jsonb_le		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_le _null_ _null_ _null_ ));
+DATA(insert OID = 4042 (  jsonb_ge		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_ge _null_ _null_ _null_ ));
+DATA(insert OID = 4043 (  jsonb_eq		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_eq _null_ _null_ _null_ ));
+DATA(insert OID = 4044 (  jsonb_cmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "3802 3802" _null_ _null_ _null_ _null_ jsonb_cmp _null_ _null_ _null_ ));
+DESCR("less-equal-greater");
+DATA(insert OID = 4045 (  jsonb_hash	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "3802" _null_ _null_ _null_ _null_ jsonb_hash _null_ _null_ _null_ ));
+DESCR("hash");
+DATA(insert OID = 4046 (  jsonb_contains   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_contains _null_ _null_ _null_ ));
+DESCR("implementation of @> operator");
+DATA(insert OID = 4047 (  jsonb_exists	 PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 25" _null_ _null_ _null_ _null_ jsonb_exists _null_ _null_ _null_ ));
+DESCR("implementation of ? operator");
+DATA(insert OID = 4048 (  jsonb_exists_any	 PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 1009" _null_ _null_ _null_ _null_ jsonb_exists_any _null_ _null_ _null_ ));
+DESCR("implementation of ?| operator");
+DATA(insert OID = 4049 (  jsonb_exists_all	 PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 1009" _null_ _null_ _null_ _null_ jsonb_exists_all _null_ _null_ _null_ ));
+DESCR("implementation of ?& operator");
+DATA(insert OID = 4050 (  jsonb_contained	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_contained _null_ _null_ _null_ ));
+DESCR("implementation of <@ operator");
+DATA(insert OID = 3480 (  gin_compare_jsonb  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "25 25" _null_ _null_ _null_ _null_ gin_compare_jsonb _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3482 (  gin_extract_jsonb  PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3483 (  gin_extract_jsonb_query  PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 2281 "2277 2281 21 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb_query _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3484 (  gin_consistent_jsonb	PGNSP PGUID 12 1 0 0 0 f f f f t f i 8 0 16 "2281 21 2277 23 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_consistent_jsonb _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3488 (  gin_triconsistent_jsonb	PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 16 "2281 21 2277 23 2281 2281 2281" _null_ _null_ _null_ _null_ gin_triconsistent_jsonb _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3485 (  gin_extract_jsonb_hash  PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb_hash _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3486 (  gin_extract_jsonb_query_hash	PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 2281 "2277 2281 21 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb_query_hash _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3487 (  gin_consistent_jsonb_hash  PGNSP PGUID 12 1 0 0 0 f f f f t f i 8 0 16 "2281 21 2277 23 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_consistent_jsonb_hash _null_ _null_ _null_ ));
+DESCR("GIN support");
+DATA(insert OID = 3489 (  gin_triconsistent_jsonb_hash	PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 16 "2281 21 2277 23 2281 2281 2281" _null_ _null_ _null_ _null_ gin_triconsistent_jsonb_hash _null_ _null_ _null_ ));
+DESCR("GIN support");
+
 /* 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/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 4c060f54b334de18b9da9f052fb8793296429878..92d50bb36e99c9a1e67d643e01f816923f1ed8e0 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -606,6 +606,12 @@ DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_
 DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
 DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
 
+/* jsonb */
+DATA(insert OID = 3802 ( jsonb			PGNSP PGUID -1 f b C f t \054 0 0 3807 jsonb_in jsonb_out jsonb_recv jsonb_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("Binary JSON");
+#define JSONBOID 3802
+DATA(insert OID = 3807 ( _jsonb			PGNSP PGUID -1 f b A f t \054 0 3802 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+
 DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
 DESCR("txid snapshot");
 DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 9982e590b84edb48c46ab95504beb914de3c0532..3610fc80d883ecaf17485d89c54fdf764c832731 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -293,6 +293,15 @@ extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx);
 		PG_RETURN_DATUM(_result); \
 	} while (0)
 
+#define SRF_RETURN_NEXT_NULL(_funcctx) \
+	do { \
+		ReturnSetInfo	   *rsi; \
+		(_funcctx)->call_cntr++; \
+		rsi = (ReturnSetInfo *) fcinfo->resultinfo; \
+		rsi->isDone = ExprMultipleResult; \
+		PG_RETURN_NULL(); \
+	} while (0)
+
 #define  SRF_RETURN_DONE(_funcctx) \
 	do { \
 		ReturnSetInfo	   *rsi; \
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index baf751e99915bdb398cb8787378b70622bb8b36a..b5e947bd7af39622cb1879c8af7a0d3bc3ea66e8 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -64,4 +64,19 @@ extern Datum json_populate_recordset(PG_FUNCTION_ARGS);
 extern Datum json_to_record(PG_FUNCTION_ARGS);
 extern Datum json_to_recordset(PG_FUNCTION_ARGS);
 
+extern Datum jsonb_object_field(PG_FUNCTION_ARGS);
+extern Datum jsonb_object_field_text(PG_FUNCTION_ARGS);
+extern Datum jsonb_array_element(PG_FUNCTION_ARGS);
+extern Datum jsonb_array_element_text(PG_FUNCTION_ARGS);
+extern Datum jsonb_extract_path(PG_FUNCTION_ARGS);
+extern Datum jsonb_extract_path_text(PG_FUNCTION_ARGS);
+extern Datum jsonb_object_keys(PG_FUNCTION_ARGS);
+extern Datum jsonb_array_length(PG_FUNCTION_ARGS);
+extern Datum jsonb_each(PG_FUNCTION_ARGS);
+extern Datum jsonb_each_text(PG_FUNCTION_ARGS);
+extern Datum jsonb_array_elements_text(PG_FUNCTION_ARGS);
+extern Datum jsonb_array_elements(PG_FUNCTION_ARGS);
+extern Datum jsonb_populate_record(PG_FUNCTION_ARGS);
+extern Datum jsonb_populate_recordset(PG_FUNCTION_ARGS);
+
 #endif   /* JSON_H */
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 32fb0f79e7e12697455dcda6a39c4e3d8216f9cb..7a4fbfe4545c7c262c17979e387532e7029f4041 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -100,11 +100,17 @@ typedef struct JsonSemAction
 extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem);
 
 /*
- * constructor for JsonLexContext, with or without strval element.
+ * constructors for JsonLexContext, with or without strval element.
  * If supplied, the strval element will contain a de-escaped version of
  * the lexeme. However, doing this imposes a performance penalty, so
  * it should be avoided if the de-escaped lexeme is not required.
+ *
+ * If you already have the json as a text* value, use the first of these
+ * functions, otherwise use  makeJsonLexContextCstringLen().
  */
 extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes);
+extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
+													int len,
+													bool need_escapes);
 
 #endif   /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
new file mode 100644
index 0000000000000000000000000000000000000000..a70cbd59400dac9f3f793c14bca8cb4857fbc64b
--- /dev/null
+++ b/src/include/utils/jsonb.h
@@ -0,0 +1,320 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb.h
+ *	  Declarations for jsonb data type support.
+ *
+ * Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ * src/include/utils/jsonb.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef __JSONB_H__
+#define __JSONB_H__
+
+#include "lib/stringinfo.h"
+#include "utils/array.h"
+#include "utils/numeric.h"
+
+/*
+ * JB_CMASK is used to extract count of items
+ *
+ * It's not possible to get more than 2^28 items into an Jsonb.
+ */
+#define JB_CMASK				0x0FFFFFFF
+
+#define JB_FSCALAR				0x10000000
+#define JB_FOBJECT				0x20000000
+#define JB_FARRAY				0x40000000
+
+/* Get information on varlena Jsonb */
+#define JB_ROOT_COUNT(jbp_)		( *(uint32*) VARDATA(jbp_) & JB_CMASK)
+#define JB_ROOT_IS_SCALAR(jbp_)	( *(uint32*) VARDATA(jbp_) & JB_FSCALAR)
+#define JB_ROOT_IS_OBJECT(jbp_)	( *(uint32*) VARDATA(jbp_) & JB_FOBJECT)
+#define JB_ROOT_IS_ARRAY(jbp_)	( *(uint32*) VARDATA(jbp_) & JB_FARRAY)
+
+/* Jentry macros */
+#define JENTRY_POSMASK			0x0FFFFFFF
+#define JENTRY_ISFIRST			0x80000000
+#define JENTRY_TYPEMASK 		(~(JENTRY_POSMASK | JENTRY_ISFIRST))
+#define JENTRY_ISSTRING			0x00000000
+#define JENTRY_ISNUMERIC		0x10000000
+#define JENTRY_ISNEST			0x20000000
+#define JENTRY_ISNULL			0x40000000
+#define JENTRY_ISBOOL			(JENTRY_ISNUMERIC | JENTRY_ISNEST)
+#define JENTRY_ISFALSE			JENTRY_ISBOOL
+#define JENTRY_ISTRUE			(JENTRY_ISBOOL | 0x40000000)
+/* Note possible multiple evaluations, also access to prior array element */
+#define JBE_ISFIRST(je_)		(((je_).header & JENTRY_ISFIRST) != 0)
+#define JBE_ISSTRING(je_)		(((je_).header & JENTRY_TYPEMASK) == JENTRY_ISSTRING)
+#define JBE_ISNUMERIC(je_)		(((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNUMERIC)
+#define JBE_ISNEST(je_)			(((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNEST)
+#define JBE_ISNULL(je_)			(((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNULL)
+#define JBE_ISBOOL(je_)			(((je_).header & JENTRY_TYPEMASK & JENTRY_ISBOOL) == JENTRY_ISBOOL)
+#define JBE_ISBOOL_TRUE(je_)	(((je_).header & JENTRY_TYPEMASK) == JENTRY_ISTRUE)
+#define JBE_ISBOOL_FALSE(je_)	(JBE_ISBOOL(je_) && !JBE_ISBOOL_TRUE(je_))
+
+/* Get offset for Jentry  */
+#define JBE_ENDPOS(je_) 		((je_).header & JENTRY_POSMASK)
+#define JBE_OFF(je_) 			(JBE_ISFIRST(je_) ? 0 : JBE_ENDPOS((&(je_))[-1]))
+#define JBE_LEN(je_) 			(JBE_ISFIRST(je_) ? \
+								 JBE_ENDPOS(je_) \
+								 : JBE_ENDPOS(je_) - JBE_ENDPOS((&(je_))[-1]))
+
+/* Flags indicating a stage of sequential Jsonb processing */
+#define WJB_DONE				0x000
+#define WJB_KEY					0x001
+#define WJB_VALUE				0x002
+#define WJB_ELEM				0x004
+#define WJB_BEGIN_ARRAY			0x008
+#define WJB_END_ARRAY			0x010
+#define WJB_BEGIN_OBJECT		0x020
+#define WJB_END_OBJECT			0x040
+
+/*
+ * When using a GIN index for jsonb, we choose to index both keys and values.
+ * The storage format is text, with K, or V prepended to the string to indicate
+ * key/element or value/element.
+ *
+ * Jsonb Keys and string array elements are treated equivalently when
+ * serialized to text index storage.  One day we may wish to create an opclass
+ * that only indexes values, but for now keys and values are stored in GIN
+ * indexes in a way that doesn't really consider their relationship to each
+ * other.
+ */
+#define JKEYELEM	'K'
+#define JVAL		'V'
+
+#define JsonbContainsStrategyNumber		7
+#define JsonbExistsStrategyNumber		9
+#define JsonbExistsAnyStrategyNumber	10
+#define JsonbExistsAllStrategyNumber	11
+
+/* Convenience macros */
+#define DatumGetJsonb(d)	((Jsonb *) PG_DETOAST_DATUM(d))
+#define JsonbGetDatum(p)	PointerGetDatum(p)
+#define PG_GETARG_JSONB(x)	DatumGetJsonb(PG_GETARG_DATUM(x))
+#define PG_RETURN_JSONB(x)	PG_RETURN_POINTER(x)
+
+typedef struct JsonbPair JsonbPair;
+typedef struct JsonbValue JsonbValue;
+typedef	char*  JsonbSuperHeader;
+
+/*
+ * Jsonbs are varlena objects, so must meet the varlena convention that the
+ * first int32 of the object contains the total object size in bytes.  Be sure
+ * to use VARSIZE() and SET_VARSIZE() to access it, though!
+ *
+ * Jsonb is the on-disk representation, in contrast to the in-memory JsonbValue
+ * representation.  Often, JsonbValues are just shims through which a Jsonb
+ * buffer is accessed, but they can also be deep copied and passed around.
+ *
+ * We have an abstraction called a "superheader".  This is a pointer that
+ * conventionally points to the first item after our 4-byte uncompressed
+ * varlena header, from which we can read flags using bitwise operations.
+ *
+ * Frequently, we pass a superheader reference to a function, and it doesn't
+ * matter if it points to just after the start of a Jsonb, or to a temp buffer.
+ */
+typedef struct
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	uint32		superheader;
+	/* (array of JEntry follows, size determined using uint32 superheader) */
+} Jsonb;
+
+/*
+ * JEntry: there is one of these for each key _and_ value for objects.  Arrays
+ * have one per element.
+ *
+ * The position offset points to the _end_ so that we can get the length by
+ * subtraction from the previous entry.	 The JENTRY_ISFIRST flag indicates if
+ * there is a previous entry.
+ */
+typedef struct
+{
+	uint32		header;		/* Shares some flags with superheader */
+}	JEntry;
+
+#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
+									 (jsonbval)->type <= jbvBool)
+
+/*
+ * JsonbValue:  In-memory representation of Jsonb.  This is a convenient
+ * deserialized representation, that can easily support using the anonymous
+ * union across underlying types during manipulation.  The Jsonb on-disk
+ * representation has various alignment considerations.
+ */
+struct JsonbValue
+{
+	enum
+	{
+		/* Scalar types */
+		jbvNull = 0x0,
+		jbvString,
+		jbvNumeric,
+		jbvBool,
+		/* Composite types */
+		jbvArray = 0x10,
+		jbvObject,
+		/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
+		jbvBinary
+	} type;		/* Influences sort order */
+
+	int				estSize;	/* Estimated size of node (including
+								 * subnodes) */
+
+	union
+	{
+		Numeric		numeric;
+		bool		boolean;
+		struct
+		{
+			int			len;
+			char	   *val;		/* Not necessarily null-terminated */
+		} string;		/* String primitive type */
+
+		struct
+		{
+			int			nElems;
+			JsonbValue *elems;
+			bool		rawScalar;	/* Top-level "raw scalar" array? */
+		} array;		/* Array container type */
+
+		struct
+		{
+			int			nPairs;		/* 1 pair, 2 elements */
+			JsonbPair  *pairs;
+		} object;		/* Associative container type */
+
+		struct
+		{
+			int			len;
+			char	   *data;
+		} binary;
+	};
+};
+
+/*
+ * Pair within an Object.
+ *
+ * Pairs with duplicate keys are de-duplicated.  We store the order for the
+ * benefit of doing so in a well-defined way with respect to the original
+ * observed order (which is "last observed wins").  This is only used briefly
+ * when originally constructing a Jsonb.
+ */
+struct JsonbPair
+{
+	JsonbValue	key;			/* Must be a jbvString */
+	JsonbValue	value;			/* May be of any type */
+	uint32		order;			/* preserves order of pairs with equal keys */
+};
+
+/* Conversion state used when parsing Jsonb from text, or for type coercion */
+typedef struct JsonbParseState
+{
+	JsonbValue	contVal;
+	Size		size;
+	struct JsonbParseState *next;
+} JsonbParseState;
+
+/*
+ * JsonbIterator holds details of the type for each iteration. It also stores a
+ * Jsonb varlena buffer, which can be directly accessed in some contexts.
+ */
+typedef enum
+{
+	jbi_start = 0x0,
+	jbi_key,
+	jbi_value,
+	jbi_elem
+} JsonbIterState;
+
+typedef struct JsonbIterator
+{
+	/* Jsonb varlena buffer (may or may not be root) */
+	char	   *buffer;
+
+	/* Current value */
+	uint32		containerType; /* Never of value JB_FSCALAR, since
+								* scalars will appear in pseudo-arrays */
+	uint32		nElems;		   /* Number of elements in metaArray
+								* (will be nPairs for objects) */
+	bool		isScalar;	   /* Pseudo-array scalar value? */
+	JEntry	   *meta;
+
+	/* Current item in buffer (up to nElems, but must * 2 for objects) */
+	int			i;
+
+	/*
+	 * Data proper.  Note that this points just past end of "meta" array.  We
+	 * use its metadata (Jentrys) with JBE_OFF() macro to find appropriate
+	 * offsets into this array.
+	 */
+	char	   *dataProper;
+
+	/* Private state */
+	JsonbIterState state;
+
+	struct JsonbIterator *parent;
+} JsonbIterator;
+
+/* I/O routines */
+extern Datum jsonb_in(PG_FUNCTION_ARGS);
+extern Datum jsonb_out(PG_FUNCTION_ARGS);
+extern Datum jsonb_recv(PG_FUNCTION_ARGS);
+extern Datum jsonb_send(PG_FUNCTION_ARGS);
+extern Datum jsonb_typeof(PG_FUNCTION_ARGS);
+
+/* Indexing-related ops */
+extern Datum jsonb_exists(PG_FUNCTION_ARGS);
+extern Datum jsonb_exists_any(PG_FUNCTION_ARGS);
+extern Datum jsonb_exists_all(PG_FUNCTION_ARGS);
+extern Datum jsonb_contains(PG_FUNCTION_ARGS);
+extern Datum jsonb_contained(PG_FUNCTION_ARGS);
+extern Datum jsonb_ne(PG_FUNCTION_ARGS);
+extern Datum jsonb_lt(PG_FUNCTION_ARGS);
+extern Datum jsonb_gt(PG_FUNCTION_ARGS);
+extern Datum jsonb_le(PG_FUNCTION_ARGS);
+extern Datum jsonb_ge(PG_FUNCTION_ARGS);
+extern Datum jsonb_eq(PG_FUNCTION_ARGS);
+extern Datum jsonb_cmp(PG_FUNCTION_ARGS);
+extern Datum jsonb_hash(PG_FUNCTION_ARGS);
+
+/* GIN support functions */
+extern Datum gin_compare_jsonb(PG_FUNCTION_ARGS);
+extern Datum gin_extract_jsonb(PG_FUNCTION_ARGS);
+extern Datum gin_extract_jsonb_query(PG_FUNCTION_ARGS);
+extern Datum gin_consistent_jsonb(PG_FUNCTION_ARGS);
+extern Datum gin_triconsistent_jsonb(PG_FUNCTION_ARGS);
+/* GIN hash opclass functions */
+extern Datum gin_extract_jsonb_hash(PG_FUNCTION_ARGS);
+extern Datum gin_extract_jsonb_query_hash(PG_FUNCTION_ARGS);
+extern Datum gin_consistent_jsonb_hash(PG_FUNCTION_ARGS);
+extern Datum gin_triconsistent_jsonb_hash(PG_FUNCTION_ARGS);
+
+/* Support functions */
+extern int	compareJsonbSuperHeaderValue(JsonbSuperHeader a,
+										 JsonbSuperHeader b);
+extern JsonbValue *findJsonbValueFromSuperHeader(JsonbSuperHeader sheader,
+												 uint32 flags,
+												 uint32 *lowbound,
+												 JsonbValue *key);
+extern JsonbValue *getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader,
+												   uint32 i);
+extern JsonbValue *pushJsonbValue(JsonbParseState ** pstate, int seq,
+								  JsonbValue *scalarVal);
+extern JsonbIterator *JsonbIteratorInit(JsonbSuperHeader buffer);
+extern int JsonbIteratorNext(JsonbIterator **it, JsonbValue *val,
+							 bool skipNested);
+extern Jsonb *JsonbValueToJsonb(JsonbValue *val);
+extern bool JsonbDeepContains(JsonbIterator ** val,
+							  JsonbIterator ** mContained);
+extern JsonbValue *arrayToJsonbSortedArray(ArrayType *a);
+extern void JsonbHashScalarValue(const JsonbValue * scalarVal, uint32 * hash);
+
+/* jsonb.c support function */
+extern char *JsonbToCString(StringInfo out, JsonbSuperHeader in,
+							int estimated_len);
+
+#endif   /* __JSONB_H__ */
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index c230e0f4155594ad853ec81397f8243905f89b30..d298718f7d70d8b2bc8ba39170c0823006f7c7b4 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -58,5 +58,6 @@ typedef struct NumericData *Numeric;
 extern bool numeric_is_nan(Numeric num);
 int32		numeric_maximum_size(int32 typmod);
 extern char *numeric_out_sci(Numeric num, int scale);
+extern char *numeric_normalize(Numeric num);
 
 #endif   /* _PG_NUMERIC_H_ */
diff --git a/src/test/regress/data/jsonb.data b/src/test/regress/data/jsonb.data
new file mode 100644
index 0000000000000000000000000000000000000000..1352ebe3ac714529682c0909f644422fcd702ec9
--- /dev/null
+++ b/src/test/regress/data/jsonb.data
@@ -0,0 +1,1009 @@
+{"line":1, "date":"CB", "node":"AA"}
+{"cleaned":false, "status":59, "line":2, "disabled":false, "node":"CBB"}
+{"indexed":true, "status":35, "line":3, "disabled":false, "wait":"CAA", "subtitle":"BA", "user":"CCA"}
+{"line":4, "disabled":true, "space":"BB"}
+{"cleaned":false, "line":5, "wait":"BB", "query":"CAC", "coauthors":"ACA", "node":"CBA"}
+{"world":"CB", "query":"CBC", "indexed":false, "line":6, "pos":92, "date":"AAB", "space":"CB", "coauthors":"ACA", "node":"CBC"}
+{"state":98, "org":43, "line":7, "pos":97}
+{"auth":"BB", "title":"CAC", "query":"BA", "status":94, "line":8, "coauthors":"BBB"}
+{"auth":"BAC", "title":"CAA", "wait":"CA", "bad":true, "query":"AA", "indexed":true, "line":9, "pos":56}
+{"title":"AAC", "bad":true, "user":"AAB", "query":"AC", "line":10, "node":"AB"}
+{"world":"CAC", "user":"AB", "query":"ACA", "indexed":true, "line":11, "space":"CB"}
+{"line":12, "pos":72, "abstract":"BBA", "space":"AAC"}
+{}
+{"world":"CC", "query":"AA", "line":14, "disabled":false, "date":"CAC", "coauthors":"AB"}
+{"org":68, "title":"BBB", "query":"BAC", "line":15, "public":false}
+{"org":73, "user":"AA", "indexed":true, "line":16, "date":"CCC", "public":true, "coauthors":"AB"}
+{"indexed":false, "line":17}
+{"state":23, "auth":"BCC", "org":38, "status":28, "line":18, "disabled":false, "abstract":"CB"}
+{"state":99, "auth":"CA", "indexed":true, "line":19, "date":"BA"}
+{"wait":"CBA", "user":"BBA", "indexed":true, "line":20, "disabled":false, "abstract":"BA", "date":"ABA"}
+{"org":10, "query":"AC", "indexed":false, "line":21, "disabled":true, "abstract":"CA", "pos":44}
+{"state":65, "title":"AC", "user":"AAC", "cleaned":true, "status":93, "line":22, "abstract":"ABC", "node":"CCC"}
+{"subtitle":"AC", "user":"CCC", "line":23}
+{"state":67, "world":"ACB", "bad":true, "user":"CB", "line":24, "disabled":true}
+{}
+{"state":65, "title":"CBC", "wait":"AAC", "bad":true, "query":"ACA", "line":26, "disabled":false, "space":"CA"}
+{"auth":"BAA", "state":68, "indexed":true, "line":27, "space":"BA"}
+{"indexed":false, "line":28, "disabled":true, "space":"CC", "node":"BB"}
+{"auth":"BAB", "org":80, "title":"BBA", "query":"BBC", "status":3, "line":29}
+{"title":"AC", "status":16, "cleaned":true, "line":30}
+{"state":39, "world":"AAB", "user":"BB", "line":31, "disabled":true}
+{"wait":"BC", "bad":false, "query":"AA", "line":32, "coauthors":"CAC"}
+{"line":33, "pos":97}
+{"title":"AA", "world":"CCA", "wait":"CC", "bad":true, "status":86, "line":34, "disabled":true, "node":"ACA"}
+{}
+{"world":"BCC", "title":"ACB", "org":61, "status":99, "cleaned":true, "line":36, "pos":76, "space":"ACC", "coauthors":"AA", "node":"CB"}
+{"title":"CAA", "cleaned":false, "line":37, "abstract":"ACA", "node":"BC"}
+{"auth":"BC", "title":"BA", "world":"ACA", "indexed":true, "line":38, "abstract":"AAA", "public":true}
+{"org":90, "line":39, "public":false}
+{"state":16, "indexed":true, "line":40, "pos":53}
+{"auth":"AAB", "wait":"CAC", "status":44, "line":41}
+{"subtitle":"ACA", "bad":true, "line":42}
+{"org":19, "world":"BC", "user":"ABA", "indexed":false, "line":43, "disabled":true, "pos":48, "abstract":"CAB", "space":"CCB"}
+{"indexed":false, "line":44}
+{"indexed":true, "line":45}
+{"status":84, "line":46, "date":"CCC"}
+{"state":94, "title":"BAB", "bad":true, "user":"BBB", "indexed":true, "line":47, "public":false}
+{"org":90, "subtitle":"BAC", "query":"CAC", "cleaned":false, "line":48, "disabled":true, "abstract":"CC", "pos":17, "space":"BCA"}
+{"world":"CBC", "line":49}
+{"org":24, "line":50, "date":"CA", "public":false}
+{"world":"BC", "indexed":true, "status":44, "line":51, "pos":59, "date":"BA", "public":true}
+{"org":98, "line":52}
+{"title":"CA", "world":"ABC", "subtitle":"CBB", "line":53, "abstract":"BBA", "date":"ACB", "node":"CA"}
+{"user":"BAB", "cleaned":true, "line":54}
+{"subtitle":"CAA", "line":55, "disabled":false, "pos":55, "abstract":"AB", "public":false, "coauthors":"AA"}
+{"wait":"CC", "user":"CC", "cleaned":true, "line":56, "pos":73, "node":"ABC"}
+{"title":"BCC", "wait":"ABC", "indexed":true, "line":57, "disabled":false}
+{"org":42, "title":"BB", "line":58, "disabled":true, "public":true, "coauthors":"BCC"}
+{"wait":"CAB", "title":"CCB", "query":"BAC", "status":66, "line":59, "disabled":true}
+{"user":"CAC", "line":60}
+{"user":"BBB", "line":61, "disabled":false, "pos":31}
+{"org":18, "line":62, "coauthors":"CCC", "node":"CA"}
+{"line":63, "coauthors":"AB"}
+{"org":25, "wait":"CA", "line":64, "abstract":"BA", "date":"BBB"}
+{"title":"CB", "wait":"CC", "bad":false, "user":"BBB", "line":65, "abstract":"ACA", "public":true}
+{"line":66, "coauthors":"AC"}
+{"state":20, "wait":"CCB", "bad":true, "line":67, "abstract":"CB"}
+{"state":79, "wait":"BAC", "bad":false, "status":11, "line":68, "abstract":"BC", "public":true, "coauthors":"CBA"}
+{"state":39, "title":"CCA", "bad":false, "query":"BBA", "line":69, "pos":42, "public":false}
+{"title":"BC", "subtitle":"CA", "query":"BC", "line":70}
+{}
+{"bad":true, "query":"BBB", "line":72}
+{"state":35, "world":"CC", "bad":false, "line":73, "space":"BB", "public":false}
+{"title":"ACC", "wait":"CAB", "subtitle":"CB", "status":19, "line":74, "disabled":false, "space":"BAA", "coauthors":"CBC", "node":"AC"}
+{"subtitle":"BCB", "indexed":false, "status":83, "line":75, "public":true}
+{"state":32, "line":76, "disabled":false, "pos":66, "space":"CC"}
+{"state":43, "cleaned":true, "line":77}
+{}
+{"state":97, "wait":"CBA", "indexed":false, "cleaned":false, "line":79, "abstract":"CB", "date":"ACC", "public":false}
+{"user":"AAB", "line":80, "pos":85, "date":"AC"}
+{"world":"AC", "wait":"CC", "subtitle":"AAB", "bad":false, "cleaned":false, "line":81, "pos":91, "node":"CCC"}
+{}
+{"org":87, "bad":false, "user":"AAC", "query":"CCC", "line":83, "disabled":false, "abstract":"AC", "date":"CCA", "public":false}
+{"state":50, "line":84}
+{"wait":"AA", "subtitle":"AA", "query":"BB", "status":97, "line":85, "disabled":true, "abstract":"CB"}
+{}
+{"subtitle":"CA", "query":"BC", "line":87}
+{}
+{"title":"CC", "line":89, "disabled":false, "pos":49, "date":"CCB", "space":"CB", "node":"BB"}
+{"auth":"CC", "wait":"AA", "title":"BC", "bad":true, "line":90}
+{"state":37, "org":85, "indexed":false, "line":91, "space":"CAA", "public":true, "coauthors":"BA"}
+{"wait":"BBB", "title":"BBC", "org":95, "subtitle":"AC", "line":92, "pos":23, "date":"AC", "public":true, "space":"BBC"}
+{"org":48, "user":"AC", "line":93, "space":"CCC"}
+{}
+{"state":77, "wait":"ABA", "subtitle":"AC", "user":"BA", "status":43, "line":95, "public":false}
+{"title":"CA", "indexed":true, "status":26, "line":96}
+{"auth":"BCA", "subtitle":"ACC", "user":"CA", "line":97, "disabled":false, "node":"ACB"}
+{"query":"BB", "line":98, "coauthors":"AB"}
+{}
+{"auth":"AA", "title":"ACB", "org":58, "subtitle":"AC", "bad":false, "cleaned":false, "line":100, "space":"ACC", "public":true}
+{"subtitle":"AAB", "bad":false, "line":101, "public":true}
+{"subtitle":"AAA", "indexed":false, "cleaned":false, "line":102, "disabled":true, "pos":35}
+{}
+{"world":"CAC", "org":10, "query":"AAA", "cleaned":true, "status":79, "indexed":true, "line":104, "pos":65, "public":false, "node":"BAB"}
+{"bad":false, "line":105, "abstract":"BA", "node":"CBB"}
+{"world":"BB", "wait":"BAA", "title":"ACA", "line":106, "date":"CBC", "space":"BA"}
+{"state":92, "wait":"CAC", "title":"AAA", "bad":false, "line":107, "abstract":"CBC", "date":"BCC", "public":false}
+{"title":"CCC", "indexed":true, "line":108, "abstract":"ACB", "public":false, "coauthors":"ABB"}
+{"auth":"BB", "query":"ACC", "status":68, "line":109}
+{"user":"CC", "cleaned":false, "indexed":true, "line":110, "date":"BAA", "space":"BCB"}
+{"auth":"CC", "org":4, "wait":"BAC", "bad":false, "indexed":false, "line":111, "pos":55, "node":"BBC"}
+{"line":112, "disabled":true}
+{"org":66, "cleaned":true, "indexed":false, "line":113, "pos":96}
+{"world":"CA", "title":"ACA", "org":83, "query":"BAC", "user":"BBC", "indexed":false, "line":114}
+{"subtitle":"BCC", "line":115, "space":"AA", "public":true, "node":"CBA"}
+{"state":77, "status":23, "line":116}
+{"bad":false, "status":4, "line":117, "node":"CC"}
+{"state":99, "title":"BCC", "query":"AC", "status":98, "line":118, "date":"BA"}
+{"status":55, "line":119, "public":false, "coauthors":"BBA", "node":"BCA"}
+{"query":"CAA", "status":40, "indexed":false, "line":120, "disabled":false, "coauthors":"CA"}
+{"title":"BBC", "org":82, "subtitle":"ACB", "line":121, "abstract":"BB", "node":"CC"}
+{"state":66, "world":"AB", "subtitle":"BA", "query":"CB", "line":122, "abstract":"BBC", "pos":65, "date":"BAB"}
+{"state":96, "title":"CBC", "status":44, "line":123, "abstract":"BA", "space":"ACA", "node":"AAC"}
+{"auth":"CA", "state":59, "bad":false, "cleaned":false, "line":124, "pos":41, "date":"BBA", "coauthors":"ABB"}
+{"wait":"ACC", "line":125}
+{"org":30, "wait":"CBB", "subtitle":"CCA", "cleaned":true, "line":126, "date":"AC", "node":"ABC"}
+{}
+{"auth":"BBA", "org":66, "subtitle":"CCB", "bad":true, "cleaned":false, "line":128, "abstract":"BB", "public":true, "coauthors":"BA"}
+{"subtitle":"AC", "bad":false, "user":"BAA", "line":129, "date":"BCB", "node":"BAC"}
+{"wait":"CC", "subtitle":"CA", "line":130, "disabled":false, "pos":49, "node":"BA"}
+{"indexed":false, "line":131, "pos":79, "date":"AAA", "node":"CAC"}
+{"wait":"AC", "world":"CB", "title":"AAA", "user":"ABC", "indexed":false, "status":15, "line":132, "coauthors":"BA"}
+{"state":96, "bad":true, "line":133, "disabled":false, "space":"BAC", "coauthors":"ABA"}
+{"world":"BAC", "line":134}
+{"title":"CCC", "line":135, "coauthors":"CC"}
+{"cleaned":true, "line":136}
+{"bad":true, "query":"CCA", "user":"CA", "cleaned":false, "line":137, "disabled":true}
+{}
+{"world":"CC", "subtitle":"BBB", "line":139}
+{"wait":"CA", "status":2, "line":140}
+{"world":"BC", "bad":false, "user":"BBC", "query":"ACB", "line":141, "pos":33, "space":"ACA"}
+{"state":92, "title":"CA", "bad":true, "query":"AB", "line":142, "abstract":"BA", "date":"ABB", "space":"BC", "coauthors":"CAA"}
+{"state":79, "query":"AB", "user":"CCA", "indexed":true, "cleaned":true, "line":143, "public":true}
+{"org":37, "query":"CA", "cleaned":true, "line":144, "disabled":true, "date":"CC"}
+{"wait":"AC", "title":"CBA", "user":"AAA", "status":24, "line":145, "date":"CBC", "public":false, "coauthors":"BAC", "node":"ACC"}
+{"user":"CA", "indexed":true, "line":146, "disabled":false, "coauthors":"BA"}
+{"wait":"BC", "org":35, "bad":false, "query":"CBB", "line":147, "date":"AAA", "public":false, "space":"BBB"}
+{"org":56, "user":"AB", "indexed":true, "line":148}
+{}
+{"title":"CBB", "org":78, "subtitle":"CBA", "bad":true, "user":"AAB", "line":150, "disabled":true, "abstract":"BAC"}
+{"world":"CCA", "query":"BC", "cleaned":true, "indexed":false, "line":151, "abstract":"BC", "pos":43, "coauthors":"AB", "node":"CBA"}
+{"auth":"ABA", "status":13, "line":152, "date":"AA"}
+{"world":"CA", "line":153, "space":"CBC"}
+{"world":"BA", "user":"BBB", "status":72, "line":154}
+{"auth":"ABB", "line":155, "disabled":true, "node":"BBC"}
+{"world":"BBB", "bad":false, "line":156, "abstract":"CBC"}
+{"line":157, "pos":60, "node":"ACC"}
+{"line":158, "node":"CC"}
+{"line":159, "public":true}
+{}
+{"query":"BA", "status":53, "cleaned":false, "line":161, "public":true}
+{"line":162, "date":"CC"}
+{}
+{"title":"BC", "bad":false, "query":"CC", "line":164, "abstract":"CCB", "date":"BA"}
+{"status":36, "line":165}
+{"title":"AB", "bad":false, "status":64, "line":166, "abstract":"AB", "coauthors":"AA", "node":"AA"}
+{"wait":"AA", "line":167}
+{"subtitle":"CBC", "user":"AC", "cleaned":false, "line":168, "disabled":true, "coauthors":"BAB", "node":"CC"}
+{"state":34, "status":73, "cleaned":true, "line":169, "abstract":"BC", "public":false, "space":"BBC", "node":"BAA"}
+{"state":10, "auth":"BBB", "bad":true, "indexed":false, "status":34, "line":170, "abstract":"BC"}
+{"subtitle":"AAA", "bad":false, "user":"ACA", "status":53, "line":171, "disabled":false, "date":"AAA"}
+{"subtitle":"CB", "query":"CC", "indexed":true, "line":172, "node":"BBC"}
+{"state":5, "world":"ABC", "bad":false, "line":173, "public":false}
+{"subtitle":"AC", "line":174}
+{"auth":"AC", "org":72, "query":"CA", "indexed":false, "cleaned":true, "line":175, "disabled":true, "pos":54}
+{"title":"BCB", "bad":false, "line":176, "pos":35, "coauthors":"AAC", "node":"ABB"}
+{"title":"BB", "cleaned":true, "status":26, "line":177}
+{"state":61, "wait":"BB", "world":"CB", "query":"BAB", "line":178, "abstract":"BB", "date":"CBB", "space":"CA", "node":"AB"}
+{"wait":"CA", "cleaned":false, "indexed":true, "line":179, "space":"CBC"}
+{"org":68, "line":180}
+{"wait":"ABB", "subtitle":"CCC", "cleaned":true, "line":181, "abstract":"BC", "coauthors":"BA"}
+{"title":"ACA", "subtitle":"AAB", "line":182, "node":"BAC"}
+{}
+{}
+{"subtitle":"BA", "query":"BBB", "indexed":true, "cleaned":true, "line":185, "node":"BCC"}
+{"org":6, "title":"BCC", "user":"BA", "line":186, "pos":67, "abstract":"CBA", "coauthors":"CBB", "node":"CBC"}
+{"org":50, "title":"CAB", "subtitle":"CB", "query":"CBB", "line":187, "coauthors":"CA", "node":"CC"}
+{"bad":false, "line":188, "node":"CCB"}
+{"org":4, "world":"AAC", "query":"CAC", "line":189, "pos":90, "node":"AC"}
+{"state":86, "line":190, "pos":79}
+{"org":98, "title":"AAC", "cleaned":true, "line":191, "space":"BC", "coauthors":"AA"}
+{"wait":"CAA", "bad":false, "user":"BC", "status":23, "line":192, "disabled":true, "date":"CA", "coauthors":"BBB"}
+{"status":26, "line":193, "disabled":true}
+{"world":"CA", "subtitle":"CCC", "query":"ABB", "status":86, "line":194, "pos":97, "space":"CAC"}
+{"cleaned":true, "line":195}
+{"state":53, "org":84, "wait":"BC", "query":"BCC", "line":196, "disabled":true, "abstract":"AAC", "node":"CAC"}
+{"state":25, "status":70, "cleaned":false, "line":197, "disabled":true, "space":"AA", "public":false}
+{"org":82, "subtitle":"AAC", "line":198}
+{"org":87, "bad":true, "status":69, "line":199, "public":false}
+{"wait":"CC", "org":60, "subtitle":"BCA", "bad":true, "cleaned":false, "indexed":true, "line":200, "date":"BA"}
+{"state":9, "world":"CAA", "org":78, "user":"ACB", "cleaned":true, "line":201, "disabled":true, "abstract":"ACC", "public":false}
+{"state":50, "world":"AAA", "title":"CAA", "user":"AB", "status":37, "line":202, "disabled":false}
+{"org":36, "subtitle":"CB", "query":"BAA", "status":35, "line":203, "abstract":"CC"}
+{"auth":"CCC", "bad":true, "query":"CB", "status":84, "line":204, "disabled":false, "date":"BB"}
+{"auth":"AC", "query":"BA", "indexed":false, "line":205, "date":"AAB", "space":"ABB"}
+{"state":30, "world":"CCA", "query":"CC", "user":"BAA", "line":206}
+{"title":"CAB", "wait":"BAB", "bad":true, "query":"BCB", "indexed":true, "status":48, "cleaned":true, "line":207, "node":"ACB"}
+{"state":97, "subtitle":"BC", "status":99, "line":208, "abstract":"CB"}
+{"title":"CA", "world":"BBA", "bad":true, "indexed":false, "cleaned":false, "status":82, "line":209, "disabled":false, "pos":44, "space":"ACA"}
+{"line":210, "public":true}
+{"line":211, "space":"BBC", "node":"AAA"}
+{"wait":"BAA", "org":50, "line":212, "abstract":"BB", "public":true, "space":"AB"}
+{"line":213, "pos":57, "date":"CC", "space":"AC"}
+{"state":23, "user":"BAB", "query":"BCB", "line":214, "abstract":"BAB"}
+{"world":"ACB", "org":21, "line":215, "abstract":"AC", "public":false}
+{"state":14, "wait":"ACB", "org":79, "title":"BB", "subtitle":"BA", "line":216}
+{"wait":"BC", "line":217, "date":"BB"}
+{"wait":"AC", "user":"BB", "indexed":false, "status":83, "line":218}
+{"auth":"BC", "org":9, "user":"BA", "status":31, "line":219, "disabled":false}
+{"state":80, "world":"BA", "wait":"CA", "line":220, "pos":65, "node":"CAC"}
+{"wait":"AC", "subtitle":"ABB", "status":79, "indexed":true, "line":221, "abstract":"AC", "pos":33, "space":"BA"}
+{"state":69, "org":83, "world":"CBC", "subtitle":"CAC", "cleaned":false, "line":222, "space":"BC", "node":"CCA"}
+{"line":223, "abstract":"BC"}
+{}
+{"world":"BB", "title":"BC", "bad":false, "query":"BBC", "cleaned":false, "line":225, "disabled":false, "public":true}
+{"line":226, "date":"AC"}
+{"auth":"CB", "subtitle":"AB", "indexed":false, "status":2, "line":227, "pos":53, "space":"AB", "coauthors":"BCA"}
+{"title":"ABA", "org":36, "line":228, "space":"AA"}
+{"world":"AB", "line":229, "pos":78, "date":"BC", "space":"CC"}
+{"wait":"BBC", "org":47, "cleaned":true, "status":5, "line":230, "pos":2, "date":"CCA"}
+{"line":231, "coauthors":"CB"}
+{"state":1, "user":"CAA", "cleaned":false, "line":232, "date":"BA", "public":true, "coauthors":"AAA", "node":"BCC"}
+{"auth":"AB", "world":"CAC", "query":"BC", "cleaned":true, "line":233, "pos":47, "space":"AB", "node":"AB"}
+{"title":"CAA", "line":234, "pos":9, "public":true, "node":"AB"}
+{"auth":"CCA", "title":"AA", "org":6, "subtitle":"CA", "cleaned":true, "status":12, "indexed":false, "line":235, "space":"ABB"}
+{"auth":"CA", "bad":false, "query":"BC", "status":61, "cleaned":false, "line":236, "disabled":true, "public":true}
+{"user":"BCB", "line":237, "pos":70, "node":"CBA"}
+{"query":"CCB", "line":238, "disabled":true, "coauthors":"BAB", "node":"BC"}
+{"auth":"AC", "org":73, "title":"CA", "bad":false, "status":94, "line":239, "abstract":"CC"}
+{"subtitle":"BC", "indexed":false, "line":240, "disabled":true}
+{"auth":"AAC", "org":73, "title":"CB", "bad":true, "query":"CA", "cleaned":true, "line":241, "disabled":false, "public":false}
+{"line":242, "public":false}
+{"auth":"AC", "title":"BC", "status":61, "line":243, "disabled":false}
+{"auth":"ABB", "bad":false, "indexed":false, "line":244, "abstract":"BAB", "date":"ABC", "coauthors":"BC"}
+{"query":"BA", "line":245, "disabled":false, "space":"BAB"}
+{"world":"BCC", "bad":false, "indexed":false, "line":246, "disabled":true, "pos":80, "public":false, "coauthors":"BC"}
+{"indexed":true, "line":247}
+{"wait":"CCA", "subtitle":"CBB", "bad":false, "line":248, "pos":83, "public":false, "space":"BA"}
+{}
+{"auth":"ABA", "org":13, "title":"BA", "bad":false, "indexed":true, "line":250, "disabled":false, "abstract":"BBA", "date":"AB"}
+{"state":37, "title":"AAA", "bad":false, "line":251, "disabled":false, "coauthors":"CBC"}
+{"auth":"ACB", "world":"AC", "title":"CAA", "subtitle":"BCA", "bad":false, "status":32, "line":252, "pos":84}
+{"query":"BA", "indexed":false, "status":0, "line":253, "abstract":"CCB", "pos":48, "date":"AC", "space":"AAC"}
+{"subtitle":"BBA", "line":254, "node":"AAA"}
+{"query":"AC", "user":"CAA", "status":13, "line":255, "public":true, "coauthors":"BCC"}
+{"auth":"AAA", "state":31, "line":256}
+{}
+{}
+{"wait":"AC", "query":"AAA", "cleaned":true, "indexed":false, "line":259, "pos":89, "coauthors":"BCA", "node":"BC"}
+{"world":"CC", "query":"BB", "line":260}
+{}
+{"org":99, "bad":false, "user":"ABA", "line":262, "abstract":"BA", "coauthors":"BCC"}
+{"auth":"CAC", "world":"CBC", "subtitle":"CA", "bad":false, "status":22, "line":263, "pos":4, "public":true, "node":"BB"}
+{"wait":"BB", "subtitle":"BCC", "indexed":true, "line":264, "node":"CAC"}
+{"subtitle":"BB", "query":"CBB", "line":265}
+{"state":35, "query":"AA", "line":266, "coauthors":"AAA"}
+{"status":6, "line":267, "pos":66}
+{"auth":"BAA", "subtitle":"CCA", "bad":false, "query":"CCB", "line":268, "public":true, "space":"CAB", "node":"CAC"}
+{"world":"AC", "org":58, "user":"AC", "line":269, "node":"AB"}
+{"auth":"BCB", "org":36, "title":"AB", "line":270, "abstract":"CAB", "date":"CAB", "public":true, "coauthors":"CB", "node":"AB"}
+{"cleaned":true, "line":271}
+{"world":"ACC", "cleaned":true, "status":11, "line":272, "disabled":false, "abstract":"AA", "space":"BCA", "node":"BA"}
+{"cleaned":true, "line":273, "pos":50, "public":true}
+{"status":95, "line":274, "abstract":"BB", "coauthors":"AC"}
+{"auth":"BCC", "state":80, "cleaned":true, "line":275, "abstract":"AC"}
+{"wait":"BA", "line":276}
+{"org":62, "subtitle":"CAA", "query":"BA", "user":"BCC", "indexed":false, "line":277, "disabled":false, "abstract":"ACA", "date":"AB"}
+{"org":63, "bad":true, "line":278, "pos":26, "coauthors":"BA"}
+{"auth":"CBB", "indexed":false, "line":279, "pos":40, "space":"CA", "coauthors":"CC"}
+{"auth":"BA", "line":280, "abstract":"AAA", "public":true, "coauthors":"CAC"}
+{"org":10, "status":16, "line":281, "date":"CCC", "space":"AC"}
+{"org":76, "user":"BBC", "indexed":false, "line":282, "pos":56, "node":"CBA"}
+{"auth":"CA", "subtitle":"AB", "query":"AA", "indexed":true, "line":283, "disabled":false, "coauthors":"ABC", "node":"CAA"}
+{"title":"BA", "status":91, "line":284, "pos":7, "coauthors":"BB"}
+{"wait":"CCA", "line":285, "public":true}
+{"world":"AC", "line":286, "disabled":true}
+{"line":287, "abstract":"AAA"}
+{"user":"CCB", "status":50, "line":288, "public":false}
+{"state":41, "world":"CCC", "query":"AA", "line":289, "disabled":true, "pos":49, "public":false}
+{"wait":"CBC", "line":290, "abstract":"CCA", "space":"BBC"}
+{"auth":"CCB", "world":"BAB", "user":"CCC", "status":93, "line":291, "pos":77, "node":"BAC"}
+{"wait":"BCC", "org":8, "user":"AC", "cleaned":true, "line":292, "disabled":true, "pos":67, "date":"AA"}
+{"org":56, "query":"BCA", "line":293, "pos":81, "coauthors":"AAA", "node":"CAB"}
+{"world":"CB", "subtitle":"CBC", "bad":true, "query":"ACB", "indexed":false, "line":294, "pos":58, "date":"BC", "node":"CB"}
+{"wait":"BC", "user":"CA", "line":295}
+{"world":"ABA", "wait":"BA", "user":"BB", "status":65, "line":296, "pos":45, "date":"BB"}
+{}
+{}
+{"auth":"BA", "user":"AA", "indexed":false, "line":299, "space":"ABA", "public":false, "coauthors":"BC"}
+{"line":300, "space":"ABA"}
+{"state":36, "org":16, "world":"BBC", "status":13, "line":301, "public":false}
+{"subtitle":"CB", "user":"BC", "line":302, "date":"AA", "coauthors":"CAC"}
+{"wait":"CBC", "indexed":true, "cleaned":true, "line":303, "date":"ACC", "public":true}
+{"user":"CAC", "status":81, "line":304, "node":"CAB"}
+{"title":"CBB", "org":89, "subtitle":"CAA", "user":"CCA", "indexed":true, "line":305}
+{"state":10, "title":"CBA", "org":66, "cleaned":true, "line":306, "pos":59, "coauthors":"CAC"}
+{}
+{"auth":"AAA", "world":"AC", "wait":"ACA", "subtitle":"BAA", "status":64, "line":308, "node":"CCA"}
+{"state":31, "world":"CCC", "title":"BCB", "cleaned":false, "status":11, "line":309, "disabled":true, "date":"AA"}
+{"title":"BC", "subtitle":"CB", "indexed":false, "line":310, "disabled":true, "abstract":"BA", "space":"ACA"}
+{"wait":"ABB", "cleaned":true, "indexed":false, "line":311, "space":"CAB"}
+{}
+{"subtitle":"CA", "line":313}
+{"org":91, "title":"CAB", "line":314, "date":"CA"}
+{}
+{"state":65, "line":316, "node":"CC"}
+{"line":317, "space":"AA"}
+{}
+{"wait":"AA", "indexed":true, "line":319}
+{"wait":"BB", "org":42, "world":"AC", "subtitle":"ACC", "indexed":true, "line":320, "disabled":true}
+{}
+{"auth":"CAC", "line":322}
+{}
+{"line":324, "pos":38, "space":"CC", "node":"BBC"}
+{"title":"CC", "line":325, "public":true, "coauthors":"BAC", "node":"ACC"}
+{"world":"CC", "subtitle":"BBC", "bad":false, "user":"BA", "line":326, "date":"AAA", "space":"AA"}
+{"state":81, "title":"BC", "wait":"BA", "indexed":false, "status":48, "line":327, "coauthors":"AB"}
+{"line":328, "space":"ABB"}
+{"line":329, "date":"CCA"}
+{}
+{"auth":"BB", "world":"BAB", "subtitle":"BA", "query":"ABB", "line":331, "disabled":true, "date":"AAA", "node":"BC"}
+{"auth":"ABA", "title":"CC", "user":"CBA", "line":332, "disabled":true, "space":"ACC"}
+{"org":98, "subtitle":"ACB", "line":333, "abstract":"BC", "public":false, "coauthors":"BC", "node":"ABA"}
+{}
+{"world":"BC", "subtitle":"BAC", "user":"AB", "query":"BAA", "cleaned":true, "line":335, "space":"AC", "node":"BAA"}
+{"state":76, "indexed":true, "cleaned":false, "line":336, "node":"CAC"}
+{"org":95, "status":84, "line":337}
+{}
+{"world":"BBA", "title":"BCC", "subtitle":"ACB", "query":"BA", "line":339, "space":"ABC", "node":"AC"}
+{"title":"CBB", "user":"CBA", "cleaned":true, "line":340, "public":true, "space":"CB", "coauthors":"CAB"}
+{"wait":"AA", "status":82, "line":341}
+{"world":"CC", "line":342}
+{"auth":"BAB", "title":"CAC", "query":"BCC", "indexed":true, "line":343}
+{"org":77, "world":"BAC", "subtitle":"AA", "user":"ABA", "line":344}
+{"state":99, "org":56, "world":"CC", "title":"CAB", "wait":"CB", "subtitle":"BCC", "line":345, "pos":65}
+{"state":68, "org":97, "title":"AA", "indexed":true, "line":346, "node":"CC"}
+{"state":3, "title":"CBC", "user":"BAA", "status":98, "line":347, "disabled":true, "pos":96, "date":"BBA"}
+{"auth":"BAA", "world":"ABB", "line":348, "disabled":false, "abstract":"ACA", "pos":66, "space":"CCC", "coauthors":"CBB", "node":"BC"}
+{}
+{"status":54, "line":350}
+{"wait":"CC", "query":"ABA", "user":"AB", "status":76, "cleaned":false, "line":351, "abstract":"CBA"}
+{"line":352, "disabled":true, "public":false}
+{"state":93, "org":92, "status":88, "line":353, "space":"AB", "coauthors":"CB"}
+{"org":34, "wait":"ABC", "world":"CBA", "bad":false, "query":"BB", "indexed":false, "line":354, "date":"CB", "public":true}
+{"wait":"CBA", "title":"CAC", "cleaned":true, "indexed":true, "line":355, "pos":9, "date":"CAA"}
+{"user":"BC", "indexed":false, "cleaned":true, "status":73, "line":356, "disabled":true, "space":"CB"}
+{"state":20, "cleaned":false, "line":357, "pos":28, "abstract":"CCB", "space":"BC"}
+{"state":17, "wait":"ABC", "query":"CB", "cleaned":false, "status":4, "line":358, "disabled":false}
+{}
+{"state":83, "world":"CC", "org":53, "cleaned":false, "status":64, "line":360, "abstract":"CBC", "coauthors":"BC"}
+{"title":"BB", "indexed":false, "line":361}
+{"state":49, "wait":"BCA", "line":362}
+{"world":"CCC", "title":"CA", "query":"CCC", "cleaned":true, "line":363, "space":"AA", "coauthors":"AAC"}
+{"state":8, "wait":"BBB", "line":364, "pos":70, "public":false, "space":"BAA", "coauthors":"AB"}
+{"state":20, "indexed":false, "status":87, "cleaned":true, "line":365, "public":true}
+{}
+{"state":92, "title":"CCC", "subtitle":"CAB", "status":39, "line":367}
+{"state":54, "org":38, "line":368}
+{}
+{"auth":"ACA", "subtitle":"CBC", "status":52, "line":370, "date":"ACC", "public":true}
+{"indexed":true, "line":371, "pos":98, "node":"CBA"}
+{"world":"BA", "status":40, "line":372, "coauthors":"AA"}
+{}
+{"query":"BA", "indexed":false, "cleaned":true, "line":374, "date":"BCC"}
+{"query":"CA", "indexed":true, "line":375, "public":false}
+{"auth":"CCA", "wait":"BBC", "bad":false, "status":91, "line":376, "abstract":"BBC", "date":"ABA"}
+{"user":"BA", "query":"CB", "status":86, "indexed":false, "line":377, "pos":83, "abstract":"BCC", "space":"CBC", "public":true}
+{"title":"ACA", "org":15, "wait":"CBC", "status":85, "line":378}
+{"state":57, "bad":true, "line":379, "abstract":"BC", "date":"CAC"}
+{"world":"CC", "cleaned":true, "line":380}
+{"title":"CB", "subtitle":"AC", "line":381, "public":false}
+{}
+{}
+{"status":12, "line":384, "coauthors":"CC"}
+{"auth":"BAC", "bad":false, "line":385, "abstract":"CBB", "public":false, "space":"BBC"}
+{}
+{}
+{"world":"BBC", "bad":true, "status":71, "cleaned":false, "line":388, "node":"BB"}
+{"cleaned":false, "line":389}
+{"state":73, "line":390}
+{"wait":"BB", "org":5, "subtitle":"BAA", "bad":false, "indexed":false, "line":391, "public":false, "node":"BAA"}
+{"auth":"CCC", "org":51, "bad":false, "cleaned":true, "line":392, "space":"AC", "node":"CC"}
+{}
+{"line":394, "abstract":"ACC", "public":true}
+{"org":44, "subtitle":"BAC", "query":"BAC", "line":395}
+{"wait":"BC", "line":396}
+{"state":68, "world":"AB", "title":"ABB", "user":"CBC", "cleaned":false, "indexed":true, "line":397, "abstract":"BA", "pos":11}
+{"world":"CA", "title":"AB", "subtitle":"BC", "user":"BCB", "line":398}
+{"bad":true, "query":"BCC", "line":399}
+{"wait":"BB", "user":"BB", "cleaned":true, "indexed":false, "line":400, "date":"BC", "public":false}
+{}
+{"wait":"BA", "line":402}
+{"title":"AC", "subtitle":"BCB", "query":"BA", "line":403}
+{}
+{"auth":"BA", "org":19, "query":"CCB", "line":405, "pos":82, "date":"CAA"}
+{"state":26, "world":"CB", "subtitle":"AB", "cleaned":false, "line":406, "disabled":true, "date":"AC"}
+{}
+{"state":11, "bad":true, "indexed":true, "line":408, "pos":79, "abstract":"BA", "date":"CB", "space":"BBA"}
+{"auth":"AC", "status":59, "line":409}
+{"org":15, "line":410, "disabled":true, "date":"BAC", "space":"CCA"}
+{}
+{}
+{"state":65, "world":"AB", "status":69, "line":413, "space":"BA"}
+{}
+{"title":"CCB", "line":415}
+{"title":"BAB", "subtitle":"CA", "indexed":false, "line":416, "public":true}
+{"wait":"CAB", "user":"CAB", "cleaned":true, "line":417, "date":"BC", "coauthors":"BBA"}
+{"subtitle":"ABA", "user":"BB", "query":"AA", "indexed":true, "line":418, "pos":8, "space":"BB", "coauthors":"CBA"}
+{"state":11, "indexed":true, "line":419, "node":"AA"}
+{"state":86, "cleaned":false, "line":420, "pos":2, "node":"CBC"}
+{"org":73, "line":421, "disabled":false}
+{"query":"BAC", "user":"CB", "status":69, "line":422}
+{"status":22, "line":423}
+{"auth":"CB", "wait":"CCA", "world":"AAB", "line":424, "disabled":false, "space":"BA", "public":false}
+{"state":81, "world":"AC", "subtitle":"CBA", "bad":true, "cleaned":false, "indexed":true, "line":425, "date":"AAB", "coauthors":"BC", "node":"BAC"}
+{"wait":"CB", "query":"BCC", "status":97, "line":426}
+{"org":47, "query":"CB", "cleaned":true, "line":427, "date":"CC"}
+{"org":33, "query":"AC", "status":48, "indexed":false, "line":428, "disabled":true, "abstract":"BC", "space":"ACC"}
+{"org":10, "query":"AB", "line":429, "pos":77, "date":"BC"}
+{"line":430, "pos":7, "abstract":"CCA", "space":"AA"}
+{"bad":false, "user":"CA", "query":"CAB", "line":431, "node":"AC"}
+{"auth":"CA", "bad":false, "line":432}
+{}
+{"auth":"BAA", "org":98, "title":"CCC", "world":"BAC", "line":434, "public":true}
+{"state":54, "wait":"AA", "user":"BBA", "indexed":false, "line":435, "disabled":true, "pos":12, "space":"AB"}
+{"world":"AC", "title":"CA", "query":"AAA", "line":436, "space":"AB", "coauthors":"AA"}
+{"auth":"CB", "wait":"CCC", "bad":false, "line":437, "pos":42, "date":"ABC", "space":"AB", "coauthors":"ABC"}
+{"auth":"CBB", "title":"BB", "query":"CB", "line":438, "pos":15, "abstract":"BC", "node":"BBB"}
+{"title":"CC", "line":439, "disabled":false}
+{"title":"AB", "line":440, "disabled":false}
+{"org":3, "bad":true, "user":"BCB", "query":"AB", "indexed":false, "cleaned":true, "line":441, "disabled":false, "space":"BA", "node":"BB"}
+{"state":62, "user":"BCC", "status":12, "line":442, "pos":58, "date":"CC", "node":"CB"}
+{"world":"BCB", "bad":true, "line":443, "space":"AAB"}
+{"state":56, "bad":false, "cleaned":false, "line":444, "disabled":false, "date":"CA", "space":"BBB", "public":true}
+{}
+{"org":31, "world":"ABC", "cleaned":true, "line":446, "disabled":true, "public":true, "coauthors":"CB"}
+{"state":54, "indexed":true, "line":447}
+{"state":98, "title":"AC", "wait":"AAC", "world":"BC", "bad":false, "line":448, "disabled":true, "public":true, "node":"ABB"}
+{"world":"AAC", "indexed":true, "line":449, "disabled":true, "pos":61}
+{"org":56, "title":"CA", "line":450}
+{"auth":"BBB", "line":451, "pos":58, "date":"BB", "space":"ABA"}
+{"auth":"AB", "world":"CA", "cleaned":true, "line":452}
+{"bad":true, "line":453, "disabled":true, "abstract":"AC", "pos":20, "date":"ABB", "node":"CAB"}
+{}
+{"state":91, "wait":"AC", "org":96, "world":"AA", "subtitle":"BBC", "query":"AA", "cleaned":true, "line":455, "public":false}
+{"status":99, "line":456, "disabled":true}
+{"org":86, "line":457, "public":true, "coauthors":"AC"}
+{"status":14, "cleaned":true, "line":458, "disabled":true}
+{"world":"AB", "user":"CB", "query":"AAB", "line":459, "pos":66, "public":false, "node":"BBA"}
+{"state":58, "world":"BB", "wait":"CBA", "title":"BCA", "line":460, "pos":95, "abstract":"CCA", "space":"BC", "coauthors":"CB"}
+{}
+{"auth":"CAC", "title":"AB", "query":"BBA", "user":"CB", "line":462, "abstract":"BCC", "pos":89, "coauthors":"ABB"}
+{"org":13, "bad":false, "query":"AA", "status":49, "line":463, "disabled":false}
+{"bad":true, "cleaned":false, "line":464, "coauthors":"BB"}
+{"org":14, "query":"BA", "line":465, "pos":25, "abstract":"BBA", "space":"AAA", "node":"CAC"}
+{"org":63, "title":"CA", "subtitle":"ACC", "query":"BAC", "status":76, "line":466, "abstract":"ACA"}
+{"wait":"BA", "subtitle":"BC", "line":467, "disabled":false, "abstract":"AC"}
+{"org":76, "title":"CA", "query":"AB", "line":468, "public":false}
+{"state":95, "world":"AC", "bad":false, "status":65, "cleaned":false, "line":469, "disabled":false}
+{"wait":"AB", "subtitle":"AA", "bad":false, "user":"CC", "query":"BBC", "status":6, "line":470, "date":"CCC"}
+{"state":82, "bad":true, "indexed":true, "line":471, "date":"BB", "coauthors":"AAA"}
+{}
+{"state":12, "auth":"ACB", "world":"CBC", "bad":false, "indexed":true, "line":473, "date":"CA", "space":"ABB", "coauthors":"CC"}
+{"subtitle":"AA", "bad":false, "user":"ACC", "line":474, "pos":86, "abstract":"CAC", "space":"BBA"}
+{"cleaned":true, "line":475}
+{"title":"CC", "wait":"BB", "status":6, "line":476, "abstract":"ACC", "date":"CB", "space":"BA", "public":true}
+{"state":96, "wait":"BA", "org":30, "subtitle":"BB", "user":"CBB", "status":19, "line":477}
+{"state":78, "org":99, "title":"CC", "line":478, "node":"BAB"}
+{"world":"CBC", "bad":false, "line":479, "date":"ACB", "public":true, "node":"CB"}
+{"state":0, "query":"ABC", "status":65, "line":480, "disabled":true, "space":"CBA", "node":"BA"}
+{"auth":"BAC", "org":24, "subtitle":"BBC", "bad":false, "user":"CAC", "line":481, "date":"BBB", "public":true, "coauthors":"CBA"}
+{"org":18, "bad":true, "cleaned":false, "status":3, "indexed":true, "line":482, "date":"BB", "coauthors":"ACC"}
+{"wait":"CB", "user":"AC", "line":483, "disabled":false}
+{"world":"AC", "subtitle":"AA", "query":"AAB", "line":484, "disabled":true, "space":"CAA"}
+{"line":485, "pos":2, "space":"CA"}
+{"org":42, "indexed":false, "line":486, "date":"CB"}
+{"org":3, "wait":"CAA", "subtitle":"CA", "cleaned":true, "line":487, "disabled":true}
+{"org":68, "subtitle":"CCB", "query":"CAA", "cleaned":false, "status":46, "line":488, "pos":87, "public":false, "node":"BC"}
+{}
+{"status":60, "cleaned":false, "line":490, "space":"CC", "node":"BCB"}
+{"state":42, "org":9, "subtitle":"CBA", "user":"BA", "status":96, "line":491, "pos":36}
+{"state":16, "title":"BCC", "user":"ABC", "indexed":false, "status":24, "line":492, "disabled":true, "node":"CBC"}
+{"auth":"CC", "wait":"BBB", "line":493, "disabled":false, "public":false, "coauthors":"AB"}
+{}
+{"wait":"BB", "title":"BBC", "subtitle":"BA", "status":3, "cleaned":false, "line":495, "disabled":false, "coauthors":"AB", "node":"BAC"}
+{}
+{"query":"CC", "indexed":false, "line":497, "coauthors":"CAC", "node":"BC"}
+{"auth":"BBA", "state":68, "line":498}
+{"state":21, "title":"CCB", "wait":"AAA", "subtitle":"CCC", "user":"BAA", "indexed":true, "line":499, "coauthors":"BB"}
+{"auth":"AAA", "subtitle":"CC", "bad":true, "user":"CC", "indexed":true, "line":500, "disabled":true, "date":"AB", "node":"AC"}
+{"auth":"BB", "title":"CCA", "user":"BA", "cleaned":true, "line":501, "pos":37, "space":"BA", "public":false}
+{"auth":"BCA", "line":502, "date":"BA"}
+{"world":"ABA", "bad":true, "indexed":false, "line":503, "disabled":true, "abstract":"AC", "pos":1, "public":false}
+{"auth":"BBB", "subtitle":"ACB", "line":504, "space":"AC", "node":"BB"}
+{"auth":"CAC", "state":19, "title":"ACA", "wait":"BA", "query":"CC", "line":505}
+{"subtitle":"BC", "cleaned":false, "indexed":false, "line":506, "date":"CAB", "public":false, "node":"ABC"}
+{"state":87, "wait":"CCC", "query":"CAC", "user":"CBB", "line":507, "abstract":"BBC", "date":"AA", "coauthors":"CA"}
+{"auth":"AC", "subtitle":"BC", "bad":false, "query":"ABA", "user":"CBB", "indexed":true, "cleaned":false, "line":508, "coauthors":"BA"}
+{"auth":"AA", "title":"ABA", "subtitle":"CCA", "query":"CC", "line":509, "pos":27, "node":"CBB"}
+{"org":5, "title":"CAC", "subtitle":"BBB", "line":510, "pos":76, "abstract":"AAB", "space":"AA"}
+{"bad":true, "line":511}
+{"wait":"ACB", "indexed":false, "line":512}
+{"auth":"CBA", "world":"BA", "bad":true, "user":"CBA", "query":"CC", "line":513, "public":false, "coauthors":"CC"}
+{}
+{"state":97, "wait":"BB", "line":515, "date":"CBC", "space":"CA"}
+{"auth":"CBC", "line":516, "disabled":true}
+{"state":91, "user":"CCA", "line":517, "coauthors":"BA", "node":"CBA"}
+{"bad":false, "cleaned":true, "line":518, "space":"AAB"}
+{}
+{"title":"CA", "cleaned":false, "status":38, "line":520}
+{"auth":"BCA", "world":"AC", "org":71, "user":"CA", "line":521, "abstract":"AAB"}
+{"bad":true, "line":522, "pos":28, "abstract":"BAA"}
+{"line":523, "coauthors":"CBC", "node":"AAB"}
+{"status":51, "cleaned":false, "line":524}
+{"query":"AAB", "line":525, "disabled":true, "date":"AA", "public":true, "coauthors":"CA"}
+{"org":15, "user":"AC", "cleaned":false, "line":526, "coauthors":"CAC", "node":"BAB"}
+{"world":"ABA", "line":527, "disabled":true, "public":true}
+{"auth":"BBC", "state":48, "bad":false, "line":528, "abstract":"BB", "date":"BAC", "space":"BA", "public":true}
+{"auth":"BA", "wait":"CAC", "subtitle":"ABC", "query":"CB", "indexed":false, "cleaned":false, "line":529, "disabled":true, "date":"CA"}
+{"wait":"AC", "world":"ABA", "org":55, "bad":true, "indexed":true, "line":530, "pos":32, "space":"BCA", "public":true}
+{"title":"CBC", "wait":"BAA", "line":531}
+{"world":"AA", "line":532, "pos":35, "space":"AAB", "public":true}
+{"line":533, "space":"AB", "coauthors":"BA"}
+{"auth":"CBC", "world":"BB", "line":534, "space":"ACA", "coauthors":"CBB"}
+{"wait":"ACA", "status":47, "line":535, "public":true, "node":"BAA"}
+{"org":16, "subtitle":"BBB", "line":536, "abstract":"AC", "space":"CB", "coauthors":"CC", "node":"CBC"}
+{"wait":"AAB", "line":537, "abstract":"AB", "space":"CAC"}
+{"query":"CAC", "line":538}
+{"world":"AC", "query":"AAA", "indexed":false, "status":18, "line":539, "pos":62, "space":"BC", "coauthors":"BAC"}
+{"org":30, "world":"AA", "query":"BC", "user":"BAC", "status":12, "cleaned":true, "line":540, "space":"AB"}
+{"org":30, "user":"CCB", "query":"BB", "cleaned":false, "line":541, "disabled":true, "public":true, "node":"CBA"}
+{}
+{"subtitle":"ABB", "bad":true, "line":543}
+{"subtitle":"BBB", "bad":true, "line":544, "pos":43, "coauthors":"ABB"}
+{}
+{"subtitle":"AB", "user":"BA", "line":546, "node":"CB"}
+{"title":"BBB", "user":"AA", "line":547, "abstract":"CBB", "pos":45}
+{"wait":"CCB", "title":"AC", "world":"AAA", "line":548, "abstract":"BBC", "pos":23, "coauthors":"ACC"}
+{"org":55, "subtitle":"BA", "line":549, "disabled":true, "date":"CB", "space":"AA"}
+{"org":39, "cleaned":true, "line":550, "public":false}
+{"state":41, "auth":"CC", "world":"CB", "line":551, "space":"AAB"}
+{}
+{"state":26, "bad":false, "query":"BAA", "status":84, "indexed":true, "line":553, "disabled":false, "coauthors":"CC", "node":"CBB"}
+{"world":"ABA", "user":"CCC", "query":"ABB", "line":554, "space":"ABC", "node":"AAA"}
+{"state":18, "wait":"CCB", "bad":true, "user":"BA", "line":555, "space":"CC", "coauthors":"BB", "node":"BBB"}
+{"auth":"AA", "state":71, "subtitle":"AA", "query":"ACC", "indexed":true, "line":556, "space":"BAB", "public":false}
+{"indexed":true, "cleaned":true, "line":557, "disabled":false, "abstract":"AB"}
+{"auth":"BCC", "title":"ACB", "world":"BCA", "user":"BAB", "cleaned":false, "line":558, "space":"BB", "coauthors":"CBC"}
+{}
+{"auth":"ACC", "org":18, "wait":"AB", "status":1, "indexed":true, "line":560}
+{"status":8, "line":561, "abstract":"BA", "public":false}
+{"state":27, "title":"ABA", "bad":true, "query":"AAB", "indexed":false, "line":562, "pos":86, "public":true, "coauthors":"BA"}
+{}
+{"title":"BAC", "wait":"CCC", "user":"BA", "line":564, "disabled":false, "date":"BB", "public":true, "space":"CB", "coauthors":"CCB"}
+{"wait":"CAA", "line":565, "pos":80, "space":"AB"}
+{"auth":"CBB", "subtitle":"BCA", "user":"CB", "line":566, "abstract":"BC", "date":"AB"}
+{"title":"CCB", "status":78, "line":567, "pos":68, "node":"BA"}
+{"auth":"BC", "query":"AB", "line":568, "space":"AB", "node":"BB"}
+{}
+{"line":570, "pos":54}
+{"world":"BBB", "user":"CC", "indexed":true, "line":571, "abstract":"CC", "coauthors":"BA", "node":"ABB"}
+{"state":41, "line":572}
+{"subtitle":"CBC", "cleaned":true, "line":573, "node":"BCB"}
+{"title":"ABA", "line":574, "pos":27, "space":"CC"}
+{"status":29, "indexed":false, "cleaned":false, "line":575, "pos":52, "public":false, "coauthors":"ACC"}
+{"title":"BBB", "org":86, "wait":"AAA", "user":"CC", "query":"CA", "line":576, "disabled":false, "date":"AB", "node":"BC"}
+{"line":577, "abstract":"CAA", "date":"BB"}
+{"auth":"CCC", "subtitle":"BBB", "query":"ABA", "line":578, "pos":99, "space":"CCB", "public":true, "coauthors":"ACA", "node":"ACB"}
+{"wait":"BCC", "line":579}
+{"state":99, "world":"BAC", "user":"CA", "line":580}
+{"state":55, "world":"AAA", "title":"AAA", "cleaned":false, "line":581, "date":"AC", "public":true, "node":"AA"}
+{"query":"ACC", "cleaned":true, "line":582, "disabled":false}
+{"auth":"AAB", "query":"BAC", "line":583}
+{"auth":"AA", "user":"BAC", "line":584}
+{}
+{"org":96, "wait":"BC", "bad":false, "cleaned":false, "status":96, "line":586, "pos":95}
+{"auth":"BC", "subtitle":"BCB", "bad":true, "user":"BBC", "line":587, "pos":79, "node":"BA"}
+{"state":55, "line":588}
+{"title":"ABC", "world":"AB", "subtitle":"CBC", "user":"BA", "query":"BAB", "line":589, "date":"AC", "node":"CB"}
+{"world":"BAA", "bad":false, "user":"AAB", "cleaned":false, "indexed":false, "line":590}
+{"title":"CB", "wait":"BC", "subtitle":"BAC", "cleaned":true, "line":591, "disabled":false, "abstract":"CBB", "public":false, "node":"ACC"}
+{"user":"BC", "line":592, "public":false}
+{}
+{"wait":"CC", "org":57, "title":"BAC", "line":594, "abstract":"AA"}
+{"auth":"BBC", "state":3, "world":"AAC", "query":"BA", "line":595, "coauthors":"BB"}
+{}
+{"subtitle":"CC", "user":"CC", "line":597}
+{"wait":"BBA", "user":"AAA", "line":598, "space":"ACB", "node":"AA"}
+{"auth":"BB", "user":"ABA", "line":599, "abstract":"AB", "node":"BA"}
+{}
+{"world":"AAA", "user":"BB", "cleaned":false, "line":601, "space":"AC", "coauthors":"ABB"}
+{"title":"CAB", "bad":false, "line":602, "coauthors":"ABB"}
+{}
+{"world":"CCC", "org":79, "line":604}
+{"org":56, "query":"AB", "cleaned":true, "indexed":true, "status":20, "line":605, "public":true, "coauthors":"ACA"}
+{"auth":"BBC", "org":13, "subtitle":"CC", "bad":true, "user":"ABC", "line":606, "date":"CA", "public":false}
+{"query":"BA", "line":607}
+{"bad":true, "line":608, "pos":12, "coauthors":"CB"}
+{"bad":false, "status":42, "line":609}
+{}
+{"bad":true, "line":611}
+{"auth":"CCA", "subtitle":"BC", "bad":true, "query":"CAA", "cleaned":false, "line":612, "public":false, "node":"CBA"}
+{"org":65, "query":"BC", "line":613}
+{}
+{"wait":"BAC", "title":"AAB", "user":"CAC", "line":615, "pos":69, "space":"CC", "node":"AAC"}
+{"bad":false, "line":616, "abstract":"AB", "pos":65, "coauthors":"BBB"}
+{}
+{"org":38, "world":"BA", "line":618, "coauthors":"AA", "node":"BC"}
+{"cleaned":false, "line":619, "disabled":false}
+{"auth":"BC", "line":620, "pos":79, "date":"AB", "coauthors":"BAA", "node":"CB"}
+{"auth":"CAA", "title":"CB", "user":"BAC", "cleaned":false, "line":621, "public":false, "space":"CBA"}
+{}
+{"bad":false, "status":12, "line":623}
+{"auth":"BBB", "wait":"BAC", "org":36, "title":"AB", "indexed":false, "cleaned":false, "line":624, "date":"AB", "coauthors":"CB"}
+{"wait":"AA", "subtitle":"AB", "query":"CCB", "line":625, "node":"CBB"}
+{"wait":"BC", "subtitle":"BA", "bad":true, "user":"AA", "line":626, "pos":3, "date":"BB"}
+{"org":28, "user":"BC", "query":"AC", "status":63, "line":627, "pos":45, "public":true, "node":"BC"}
+{"query":"BC", "status":47, "line":628, "disabled":false, "date":"CA", "public":false}
+{}
+{"wait":"CB", "line":630, "pos":67, "coauthors":"AC"}
+{"org":33, "world":"BBB", "query":"BB", "status":92, "line":631}
+{"state":65, "title":"AC", "world":"CBC", "query":"CBC", "line":632, "date":"CAC", "space":"CC", "coauthors":"CC"}
+{}
+{"auth":"CC", "query":"BCA", "status":46, "line":634, "disabled":false, "pos":69}
+{"wait":"CB", "line":635, "pos":34}
+{"state":9, "wait":"CC", "status":23, "line":636, "disabled":true, "date":"BB", "space":"AC"}
+{"user":"CCB", "indexed":false, "cleaned":true, "line":637, "pos":65, "date":"AA", "public":true}
+{"auth":"BC", "wait":"AB", "title":"BB", "bad":true, "line":638, "abstract":"ACC", "date":"BC", "public":false}
+{"state":44, "auth":"BC", "world":"CBC", "line":639, "disabled":false, "date":"CAA"}
+{"world":"CB", "title":"ACB", "user":"BA", "query":"AA", "line":640, "disabled":true, "space":"AC"}
+{"state":37, "line":641, "disabled":true, "pos":66}
+{"world":"AAA", "bad":true, "user":"AAA", "query":"BA", "line":642, "disabled":true, "coauthors":"CBC"}
+{"world":"BA", "title":"ABB", "org":96, "bad":false, "query":"AAA", "status":75, "cleaned":false, "line":643, "space":"BA"}
+{"state":36, "org":66, "subtitle":"AA", "query":"CA", "cleaned":true, "status":79, "line":644, "date":"CB"}
+{"wait":"BC", "line":645, "date":"CBA", "space":"BCB", "public":true, "node":"ABA"}
+{"auth":"BB", "org":37, "query":"CAA", "indexed":true, "line":646, "abstract":"CBA", "coauthors":"CBA"}
+{}
+{}
+{"state":58, "world":"BAB", "org":11, "user":"CC", "line":649}
+{"title":"CB", "status":19, "line":650, "disabled":false, "public":false, "coauthors":"AA"}
+{"user":"BBC", "indexed":true, "line":651, "disabled":true, "pos":8}
+{"query":"CC", "cleaned":true, "indexed":false, "line":652, "pos":67, "date":"AA"}
+{"auth":"AAC", "line":653, "disabled":true, "public":false, "coauthors":"AAA", "node":"CBB"}
+{"bad":true, "query":"AC", "line":654, "disabled":false}
+{"world":"CCA", "org":15, "bad":false, "user":"CCC", "line":655, "public":true, "space":"CC"}
+{"line":656, "coauthors":"BBB"}
+{"title":"BA", "line":657, "date":"ACB"}
+{"user":"BC", "query":"CC", "cleaned":true, "line":658, "pos":51, "abstract":"BA"}
+{"subtitle":"CCA", "user":"CCA", "cleaned":false, "line":659, "abstract":"BA", "pos":95, "date":"CA"}
+{"auth":"CA", "state":23, "org":19, "bad":false, "user":"BCB", "indexed":false, "line":660, "date":"ABA"}
+{"state":64, "org":97, "bad":false, "indexed":false, "line":661, "space":"BAB", "coauthors":"BB", "node":"BA"}
+{"status":11, "line":662}
+{"title":"BCC", "org":44, "subtitle":"ACB", "cleaned":false, "line":663, "pos":58}
+{"auth":"ABB", "bad":true, "line":664, "pos":82, "coauthors":"CC", "node":"AB"}
+{"bad":false, "cleaned":true, "status":25, "line":665, "disabled":false, "abstract":"BB", "public":true}
+{"wait":"AC", "user":"CB", "line":666, "pos":71, "abstract":"ACA", "coauthors":"CBB"}
+{"title":"AA", "bad":true, "user":"BB", "line":667, "date":"CA", "space":"BC", "node":"CC"}
+{}
+{"auth":"AAB", "line":669}
+{"wait":"AAC", "query":"ABA", "status":35, "line":670, "disabled":false, "pos":56}
+{"org":3, "line":671}
+{"state":46, "bad":false, "cleaned":true, "line":672}
+{"state":30, "org":9, "status":72, "line":673, "abstract":"ACA", "coauthors":"CB"}
+{"auth":"BB", "wait":"CA", "title":"BBB", "bad":true, "user":"AAA", "status":86, "indexed":false, "line":674, "node":"BCC"}
+{"indexed":true, "line":675, "pos":63}
+{"bad":true, "query":"CBB", "status":5, "line":676, "abstract":"CCC", "public":false, "space":"BB"}
+{"title":"BBB", "org":60, "bad":true, "cleaned":false, "line":677, "pos":82, "date":"BAA", "space":"BB", "coauthors":"CAA"}
+{}
+{}
+{"state":73, "bad":false, "cleaned":false, "line":680, "abstract":"CA", "date":"CCA", "space":"CB"}
+{"state":92, "query":"CC", "line":681, "abstract":"AB", "date":"BBB", "public":true, "coauthors":"CBA"}
+{"subtitle":"CCA", "line":682}
+{"world":"BAC", "subtitle":"AC", "line":683, "disabled":true, "abstract":"AA", "pos":55, "space":"AC", "node":"CA"}
+{"state":75, "world":"ACA", "query":"BC", "line":684, "coauthors":"AAC"}
+{"status":21, "line":685}
+{"state":39, "wait":"CB", "title":"CBC", "query":"BB", "cleaned":true, "line":686, "disabled":false}
+{"world":"CCA", "wait":"AB", "user":"CC", "query":"BB", "cleaned":true, "line":687}
+{"auth":"CAC", "state":94, "wait":"ACC", "title":"BBC", "user":"BB", "line":688, "disabled":false, "pos":16, "coauthors":"AAC"}
+{}
+{"org":43, "line":690}
+{}
+{"state":4, "title":"CA", "subtitle":"AA", "query":"BC", "line":692, "pos":57, "date":"BCA", "public":false, "coauthors":"ABB"}
+{"wait":"BBA", "line":693}
+{"auth":"BCA", "bad":false, "user":"BBA", "line":694, "disabled":false, "date":"CC", "public":true, "coauthors":"CB"}
+{"state":66, "wait":"BB", "user":"CC", "indexed":true, "line":695, "pos":99, "space":"BCA"}
+{"org":1, "line":696, "disabled":false, "space":"BCC", "coauthors":"BC"}
+{"auth":"BC", "cleaned":true, "indexed":false, "line":697, "space":"CBB"}
+{"wait":"AC", "indexed":false, "line":698, "pos":44}
+{"wait":"AA", "title":"BBB", "org":31, "indexed":true, "line":699, "disabled":false}
+{"auth":"BB", "world":"ACC", "bad":true, "indexed":false, "line":700, "abstract":"CB", "pos":5, "space":"ACB", "node":"CC"}
+{"cleaned":false, "line":701, "space":"CB"}
+{"line":702, "space":"CCC"}
+{"world":"CA", "subtitle":"ABA", "line":703, "pos":5, "date":"BA", "coauthors":"AB"}
+{}
+{"line":705, "date":"BBB"}
+{"state":10, "query":"CB", "status":70, "line":706, "abstract":"ABA", "date":"BC"}
+{"auth":"CB", "line":707}
+{"wait":"BBA", "cleaned":true, "line":708, "pos":94, "date":"CBC"}
+{"state":86, "org":5, "world":"BB", "indexed":false, "line":709, "date":"BBB", "space":"CA", "public":true}
+{"world":"ACA", "query":"ABC", "status":40, "line":710, "disabled":true, "public":true, "node":"CA"}
+{"bad":true, "line":711}
+{"query":"AB", "line":712, "coauthors":"BBC", "node":"AA"}
+{"user":"ABB", "line":713, "public":false, "space":"AAA", "node":"BBA"}
+{"auth":"AC", "wait":"BAC", "bad":true, "line":714, "public":false}
+{"line":715, "abstract":"CA", "public":false}
+{"user":"AC", "indexed":true, "line":716, "coauthors":"CB"}
+{"state":4, "title":"ABB", "org":26, "indexed":true, "line":717, "public":true, "coauthors":"CCA", "node":"AC"}
+{"wait":"CA", "title":"CCA", "world":"CCC", "line":718, "abstract":"ACA"}
+{"auth":"ACA", "org":29, "subtitle":"AA", "user":"CA", "status":24, "indexed":true, "line":719, "public":false, "node":"CA"}
+{}
+{"line":721, "disabled":true, "abstract":"BAC"}
+{"world":"BC", "line":722}
+{"state":27, "auth":"AA", "title":"BC", "world":"CC", "query":"BCC", "line":723, "disabled":true, "pos":9, "public":false, "node":"BCC"}
+{"org":78, "wait":"ABA", "cleaned":true, "indexed":true, "line":724, "date":"ACB", "space":"AA"}
+{"state":60, "line":725}
+{}
+{}
+{"wait":"ABA", "title":"CAC", "user":"CCC", "line":728}
+{"wait":"CC", "indexed":true, "status":39, "line":729, "disabled":true, "public":false}
+{"auth":"CB", "subtitle":"BBA", "line":730, "coauthors":"CAC"}
+{"world":"CBB", "line":731, "space":"BCB"}
+{"cleaned":true, "line":732}
+{"org":67, "bad":true, "line":733, "pos":9, "node":"ACC"}
+{"world":"BC", "wait":"CAC", "org":58, "subtitle":"ACC", "bad":true, "query":"CAA", "line":734, "abstract":"BCA", "pos":1, "public":true}
+{"state":45, "query":"AB", "indexed":false, "line":735, "pos":82, "date":"BC", "public":false, "coauthors":"BA"}
+{"state":68, "title":"BC", "cleaned":true, "status":34, "line":736, "disabled":true, "node":"BBB"}
+{"auth":"AC", "line":737}
+{"line":738, "date":"BA", "space":"CCC", "public":false}
+{"line":739, "node":"BAA"}
+{"org":72, "title":"BC", "line":740, "pos":51, "coauthors":"CA"}
+{"state":72, "user":"CCB", "query":"ACA", "line":741}
+{"org":80, "subtitle":"BBA", "bad":true, "user":"BC", "line":742, "pos":52, "coauthors":"BCA"}
+{}
+{"query":"BC", "line":744, "abstract":"AB", "public":false, "node":"BAC"}
+{"world":"CAC", "line":745}
+{"auth":"CBB", "title":"AA", "user":"AB", "line":746, "pos":35, "public":false, "space":"AAB"}
+{"state":69, "world":"AB", "org":78, "subtitle":"BA", "bad":false, "line":747, "node":"AAA"}
+{"bad":true, "line":748, "public":true}
+{"wait":"BC", "org":47, "query":"BBB", "line":749}
+{"title":"BBB", "line":750}
+{"org":33, "query":"CB", "line":751, "disabled":true}
+{"subtitle":"BB", "line":752, "space":"CC"}
+{"org":89, "line":753}
+{"auth":"ABA", "line":754, "coauthors":"ACC"}
+{"subtitle":"BA", "line":755, "pos":47}
+{"state":81, "subtitle":"CB", "query":"AB", "status":25, "cleaned":false, "line":756, "pos":72, "date":"BA", "coauthors":"BCA"}
+{"state":46, "status":88, "line":757, "disabled":false, "public":true}
+{"world":"AB", "line":758, "disabled":true, "abstract":"BB", "coauthors":"AAA"}
+{"query":"AC", "line":759, "abstract":"AAB"}
+{"auth":"BC", "indexed":false, "line":760, "abstract":"BA", "node":"CAA"}
+{"state":10, "auth":"BAC", "title":"BC", "query":"BCA", "cleaned":true, "line":761, "disabled":true, "space":"ACC", "coauthors":"ABA"}
+{"line":762, "disabled":true, "pos":43}
+{"world":"CBA", "user":"BBC", "indexed":true, "line":763}
+{"wait":"ACB", "query":"BA", "status":22, "line":764, "pos":70, "abstract":"BAC", "public":false, "space":"BC"}
+{}
+{"line":766, "disabled":false, "abstract":"CBC", "date":"CA"}
+{"title":"CC", "bad":true, "user":"BCC", "indexed":false, "line":767, "date":"BCB", "node":"AAA"}
+{"title":"CB", "line":768, "abstract":"AA", "node":"ABB"}
+{"org":21, "user":"ABC", "line":769, "abstract":"BB", "date":"CBB", "space":"CC"}
+{"auth":"AC", "org":66, "user":"CC", "line":770, "public":false, "space":"CA", "coauthors":"AA"}
+{"org":58, "line":771, "coauthors":"BCC", "node":"AC"}
+{}
+{"auth":"BC", "wait":"CC", "line":773, "abstract":"ACC", "pos":98, "date":"CCC", "space":"ABB", "node":"CB"}
+{}
+{"query":"BC", "user":"AC", "indexed":true, "line":775, "abstract":"AAA"}
+{"subtitle":"BAA", "indexed":false, "line":776}
+{"line":777, "pos":33, "date":"CCB", "public":true}
+{"world":"BCA", "bad":true, "line":778}
+{"auth":"CA", "line":779, "date":"AC", "space":"CAC"}
+{"title":"BB", "bad":false, "cleaned":true, "line":780, "disabled":false, "date":"BAB", "space":"ACB"}
+{"auth":"CAC", "title":"AAB", "subtitle":"CA", "bad":false, "line":781, "disabled":false, "space":"CB"}
+{"state":78, "auth":"AC", "bad":true, "status":46, "line":782, "abstract":"CCA", "pos":97, "public":true}
+{"user":"BBA", "line":783}
+{}
+{"state":63, "title":"CA", "cleaned":true, "line":785, "abstract":"BA", "space":"BCC"}
+{"line":786, "node":"CAC"}
+{"line":787, "pos":65}
+{"line":788, "space":"ABB"}
+{}
+{"org":14, "line":790, "abstract":"CAB", "coauthors":"BBC"}
+{"subtitle":"CBA", "cleaned":false, "line":791, "disabled":false, "pos":57, "node":"CB"}
+{"auth":"CAA", "org":84, "wait":"AB", "indexed":true, "status":51, "line":792, "abstract":"CC"}
+{"org":72, "bad":true, "line":793, "space":"ACA"}
+{}
+{"auth":"BC", "state":76, "wait":"CC", "user":"ABB", "cleaned":false, "line":795, "pos":99, "abstract":"CA"}
+{"wait":"CCA", "world":"CBC", "line":796, "date":"CB", "public":false}
+{"state":49, "line":797, "coauthors":"CC"}
+{"wait":"BBB", "title":"ABB", "org":74, "line":798, "disabled":false, "pos":34, "space":"BB"}
+{"line":799, "abstract":"CB"}
+{"state":84, "user":"ABB", "cleaned":false, "status":18, "line":800, "disabled":true, "date":"CCA", "node":"BA"}
+{"state":81, "auth":"CB", "world":"CA", "user":"CAA", "line":801, "date":"AC", "space":"CBC", "coauthors":"BCB"}
+{"org":4, "line":802, "disabled":false, "abstract":"ABA", "public":false}
+{"auth":"CBC", "state":99, "cleaned":true, "line":803, "disabled":true, "space":"BC", "node":"BBC"}
+{"auth":"AC", "wait":"CA", "cleaned":false, "line":804, "pos":54, "date":"BAA", "public":true, "space":"AB"}
+{}
+{"auth":"BCB", "wait":"BCC", "subtitle":"AAA", "line":806}
+{"line":807, "disabled":false, "space":"ACA"}
+{"org":96, "query":"CBA", "line":808, "disabled":false, "pos":74, "space":"CA", "public":false}
+{}
+{"state":12, "title":"AA", "bad":false, "status":20, "line":810, "disabled":true, "coauthors":"CAC", "node":"AB"}
+{"auth":"ABC", "line":811, "date":"CA"}
+{"title":"AB", "indexed":false, "line":812, "disabled":false, "node":"AAC"}
+{}
+{"world":"CBA", "status":15, "line":814, "abstract":"CBA"}
+{"status":49, "line":815, "pos":49}
+{"subtitle":"CAB", "line":816}
+{}
+{}
+{"world":"CAC", "title":"CB", "wait":"AA", "query":"CA", "indexed":true, "line":819, "disabled":true}
+{"auth":"ABB", "wait":"AC", "query":"CC", "cleaned":true, "indexed":false, "line":820, "abstract":"AA", "public":false, "node":"AB"}
+{"org":5, "wait":"BA", "indexed":true, "line":821, "node":"AB"}
+{"title":"CC", "wait":"CC", "bad":false, "query":"BCC", "indexed":false, "line":822, "pos":27, "date":"CB", "node":"CBA"}
+{"query":"BC", "status":28, "line":823, "public":false}
+{"status":1, "line":824, "abstract":"BB"}
+{}
+{"auth":"AA", "title":"BC", "query":"CA", "status":33, "line":826}
+{"state":9, "title":"BB", "subtitle":"ACC", "bad":true, "query":"BA", "status":41, "line":827, "abstract":"ACB", "public":false}
+{"auth":"AB", "subtitle":"CAB", "line":828, "public":false, "coauthors":"AB", "node":"BAC"}
+{"line":829, "disabled":false, "public":true, "node":"CBC"}
+{"auth":"BAB", "line":830}
+{"wait":"BBA", "bad":true, "indexed":false, "line":831, "space":"BB"}
+{"org":70, "wait":"BC", "world":"AC", "indexed":true, "status":96, "line":832, "disabled":true, "space":"AB"}
+{"state":8, "world":"BAB", "bad":true, "indexed":true, "status":18, "line":833, "date":"BA", "space":"BA"}
+{"query":"AB", "line":834}
+{"bad":true, "status":5, "line":835}
+{"world":"BAC", "subtitle":"BB", "bad":false, "user":"AB", "indexed":false, "cleaned":true, "line":836}
+{"line":837, "public":false}
+{"line":838, "pos":7}
+{"auth":"CA", "query":"ABB", "indexed":true, "line":839, "public":true}
+{"wait":"CC", "bad":false, "line":840, "date":"AAB", "public":false, "coauthors":"BCB"}
+{"auth":"AB", "state":97, "org":24, "line":841, "pos":41, "node":"BC"}
+{"wait":"BB", "world":"CBA", "user":"BAA", "status":18, "line":842, "date":"BAB", "public":true}
+{"title":"BA", "subtitle":"CA", "query":"CCB", "line":843, "space":"BB"}
+{"auth":"BB", "world":"ACA", "line":844, "pos":29}
+{"state":65, "org":40, "query":"CAA", "user":"AB", "indexed":false, "cleaned":false, "line":845}
+{"title":"CB", "cleaned":false, "indexed":false, "line":846}
+{"wait":"CAA", "indexed":false, "line":847, "disabled":false}
+{}
+{"title":"CB", "query":"CC", "line":849, "abstract":"CB", "pos":10, "public":true}
+{"auth":"CB", "subtitle":"BA", "cleaned":true, "line":850, "disabled":true, "pos":84}
+{"org":45, "wait":"BA", "query":"CCC", "line":851, "date":"BCA", "coauthors":"CAB"}
+{"state":84, "title":"CB", "line":852, "pos":71, "space":"CA"}
+{"line":853, "public":true, "node":"AA"}
+{"subtitle":"BC", "user":"AB", "cleaned":true, "line":854, "public":true}
+{"state":26, "wait":"BA", "world":"BBB", "user":"BB", "status":53, "line":855, "abstract":"ABA", "pos":72, "space":"AC"}
+{"line":856, "public":false, "coauthors":"CA"}
+{"wait":"ABB", "subtitle":"CBA", "line":857, "pos":44}
+{"auth":"ABA", "wait":"CC", "org":0, "bad":true, "query":"BB", "line":858, "disabled":false, "public":true}
+{"bad":false, "user":"AA", "line":859, "pos":90}
+{"world":"BCC", "title":"BAA", "bad":false, "user":"CAA", "query":"BBC", "line":860, "date":"CAA", "space":"BCB", "public":true, "coauthors":"CB"}
+{"title":"BAB", "world":"BC", "subtitle":"AA", "cleaned":false, "status":9, "line":861, "pos":95}
+{"title":"AC", "line":862}
+{"wait":"CB", "bad":false, "status":89, "line":863, "coauthors":"AB"}
+{"subtitle":"ACC", "indexed":true, "cleaned":true, "line":864}
+{"title":"AB", "subtitle":"CBB", "query":"ACA", "indexed":true, "line":865, "disabled":true}
+{"title":"BC", "world":"BB", "query":"AA", "user":"ACB", "status":43, "cleaned":false, "line":866, "coauthors":"CBC", "node":"ACB"}
+{}
+{"org":25, "wait":"AC", "indexed":false, "line":868, "disabled":false, "abstract":"CA", "pos":48}
+{"bad":true, "status":34, "line":869, "pos":32, "date":"AC", "public":true, "node":"AA"}
+{"state":33, "wait":"AAC", "indexed":true, "status":20, "line":870, "abstract":"BA"}
+{"wait":"CCC", "subtitle":"AC", "line":871, "disabled":false, "space":"BA", "public":true, "coauthors":"BCC"}
+{"status":49, "line":872}
+{"state":90, "title":"ACC", "world":"CBB", "subtitle":"BAB", "bad":false, "status":94, "line":873, "abstract":"CB"}
+{"title":"BCB", "line":874}
+{"cleaned":false, "line":875}
+{"wait":"BAA", "subtitle":"BBC", "line":876}
+{"auth":"AB", "org":35, "bad":false, "indexed":false, "line":877, "coauthors":"BBA"}
+{"line":878, "public":false}
+{}
+{"auth":"CB", "wait":"CBC", "indexed":false, "line":880, "public":true}
+{"query":"CC", "status":4, "line":881, "disabled":true, "node":"CA"}
+{"title":"BB", "line":882}
+{"state":53, "bad":false, "cleaned":false, "status":63, "line":883, "coauthors":"BAA"}
+{"auth":"ACA", "world":"AC", "user":"CBC", "line":884, "date":"BCA", "node":"BBC"}
+{"auth":"BAB", "state":11, "world":"CB", "org":77, "query":"BA", "cleaned":false, "line":885, "space":"AC"}
+{"world":"CA", "user":"CA", "line":886, "node":"CB"}
+{"state":32, "org":50, "wait":"AA", "line":887, "disabled":false, "space":"BBA", "public":false}
+{"cleaned":true, "line":888, "pos":70, "node":"ABC"}
+{"org":63, "user":"AAB", "query":"BB", "line":889, "date":"BC", "space":"CBB", "node":"ABC"}
+{"wait":"BB", "user":"BB", "query":"CB", "line":890, "space":"BB", "coauthors":"BA", "node":"ABC"}
+{"auth":"BC", "world":"CC", "subtitle":"CB", "line":891, "public":false, "coauthors":"BC"}
+{"state":13, "org":38, "line":892, "coauthors":"BC", "node":"ABC"}
+{"auth":"CC", "world":"CAC", "line":893, "date":"BBA", "node":"CBC"}
+{}
+{"auth":"AA", "line":895, "coauthors":"BB"}
+{"auth":"AA", "state":76, "status":85, "line":896, "date":"CCC", "public":true, "coauthors":"AB"}
+{"auth":"AB", "indexed":true, "cleaned":false, "line":897}
+{"line":898, "coauthors":"CBB"}
+{}
+{"wait":"ACC", "line":900, "abstract":"BBA"}
+{"auth":"AA", "wait":"BCB", "cleaned":false, "line":901, "abstract":"AAC"}
+{"state":68, "title":"AC", "subtitle":"BB", "line":902}
+{"state":41, "wait":"ABA", "bad":false, "user":"BBA", "status":46, "line":903, "node":"AAB"}
+{}
+{"cleaned":false, "line":905, "pos":33}
+{"bad":false, "query":"BA", "line":906, "pos":48, "space":"CB", "public":true}
+{"query":"CB", "indexed":true, "line":907, "pos":41, "abstract":"CBB", "space":"BA", "public":false, "node":"BC"}
+{"title":"AB", "line":908}
+{"auth":"BC", "title":"CB", "line":909, "disabled":false, "space":"CA", "public":true, "coauthors":"BC"}
+{"world":"AA", "user":"ABA", "indexed":false, "line":910, "abstract":"CC"}
+{"auth":"CCA", "indexed":false, "line":911, "date":"AC", "public":false}
+{"world":"AAB", "bad":true, "line":912}
+{"subtitle":"CBB", "line":913, "public":true}
+{"wait":"CB", "line":914, "disabled":false, "pos":71, "date":"BA", "space":"CBA", "public":false, "coauthors":"BB"}
+{"org":67, "wait":"CA", "bad":false, "line":915, "disabled":false, "public":true}
+{}
+{"line":917, "date":"CB"}
+{"auth":"CBB", "world":"AAA", "status":83, "indexed":false, "line":918, "disabled":true, "date":"CBA", "coauthors":"ACC"}
+{"wait":"AB", "title":"BA", "status":33, "line":919, "disabled":false}
+{"wait":"ACB", "cleaned":false, "line":920, "abstract":"AA", "coauthors":"BCB"}
+{"wait":"ABB", "org":40, "world":"BC", "subtitle":"CA", "user":"AAC", "status":14, "indexed":true, "line":921, "pos":66}
+{"auth":"BA", "org":22, "wait":"BAB", "bad":true, "user":"ACC", "status":32, "line":922}
+{"world":"BA", "query":"CAB", "status":0, "line":923}
+{"org":84, "bad":true, "line":924, "coauthors":"BAB"}
+{"auth":"ACC", "subtitle":"AAA", "query":"CCA", "cleaned":false, "line":925, "pos":60, "space":"BC"}
+{"wait":"BC", "subtitle":"CCC", "bad":false, "cleaned":false, "indexed":false, "line":926, "public":false, "coauthors":"AC"}
+{"auth":"CA", "state":91, "org":6, "world":"AA", "wait":"ABB", "query":"AAC", "line":927, "date":"CA", "node":"BAC"}
+{"world":"BCA", "query":"AA", "user":"BBC", "line":928, "disabled":false}
+{"world":"CBC", "user":"CBC", "line":929, "date":"CAC"}
+{"world":"BCB", "bad":false, "user":"BB", "line":930}
+{"auth":"CA", "world":"AA", "query":"ABA", "user":"AA", "indexed":true, "line":931, "coauthors":"BBC"}
+{"auth":"BAA", "bad":true, "line":932, "disabled":false, "pos":93, "abstract":"CCA", "date":"BBA", "coauthors":"AA"}
+{"line":933, "space":"CAB", "node":"AB"}
+{}
+{"status":60, "cleaned":true, "indexed":true, "line":935, "pos":35, "space":"CAB", "node":"BBB"}
+{"wait":"AAA", "bad":true, "status":26, "line":936, "abstract":"ACB", "space":"BBA", "coauthors":"CCC"}
+{"title":"BA", "bad":true, "line":937, "date":"BBA", "public":true}
+{}
+{"query":"BA", "user":"BA", "line":939, "disabled":true}
+{"line":940, "date":"CC"}
+{"wait":"CAB", "query":"BCA", "user":"BC", "line":941, "pos":8, "coauthors":"ACC"}
+{"line":942, "space":"BA"}
+{"auth":"CA", "org":11, "line":943, "pos":99}
+{"org":83, "line":944, "disabled":false, "date":"BBA", "space":"AC", "node":"AC"}
+{"world":"CCA", "line":945, "node":"BC"}
+{"org":95, "title":"CA", "world":"BA", "line":946, "pos":36, "coauthors":"CA"}
+{"state":93, "line":947}
+{"cleaned":false, "status":5, "line":948, "abstract":"BB", "public":false, "coauthors":"ABC"}
+{"world":"CA", "org":61, "bad":false, "query":"CC", "cleaned":true, "line":949, "pos":14, "space":"CC"}
+{"state":91, "line":950, "abstract":"BA", "date":"AB"}
+{"auth":"BBC", "line":951, "date":"BB"}
+{"auth":"BAB", "line":952, "disabled":true, "node":"AAA"}
+{"auth":"CAA", "subtitle":"ABA", "bad":true, "line":953}
+{"auth":"CA", "wait":"BB", "org":12, "user":"BCC", "cleaned":false, "line":954, "public":false, "coauthors":"AA"}
+{"org":93, "cleaned":true, "line":955, "disabled":true, "public":true, "node":"ACA"}
+{"line":956, "pos":10}
+{"org":74, "world":"CCC", "subtitle":"AB", "user":"AAA", "cleaned":true, "line":957, "pos":70, "public":true, "node":"CC"}
+{"state":51, "line":958}
+{"world":"CCA", "title":"BCB", "user":"AB", "indexed":true, "line":959, "disabled":true, "pos":21, "date":"CBC"}
+{"org":86, "wait":"BC", "query":"BB", "user":"AA", "indexed":true, "line":960, "pos":58, "date":"AB"}
+{"line":961, "node":"CC"}
+{"auth":"BCB", "world":"ACC", "subtitle":"CA", "bad":true, "user":"BA", "indexed":false, "line":962, "public":false}
+{}
+{"status":37, "line":964}
+{"state":70, "status":76, "indexed":false, "line":965, "disabled":true, "space":"BB"}
+{}
+{"state":67, "world":"CA", "title":"AA", "line":967, "abstract":"BA", "space":"BAA"}
+{"auth":"CA", "world":"AA", "bad":true, "query":"BC", "status":53, "indexed":false, "line":968, "date":"AB", "node":"BAA"}
+{"query":"AC", "cleaned":true, "line":969, "abstract":"BC", "space":"CAB", "coauthors":"BAA"}
+{"wait":"BCA", "world":"CB", "title":"BC", "indexed":false, "line":970, "disabled":true, "pos":70, "date":"AB"}
+{}
+{"subtitle":"BC", "query":"AA", "line":972}
+{"line":973, "public":true}
+{"org":75, "world":"AAB", "subtitle":"BB", "user":"CC", "line":974, "space":"CA"}
+{"auth":"BCB", "cleaned":true, "line":975}
+{"title":"BAC", "user":"CB", "line":976, "public":false}
+{"subtitle":"BAC", "indexed":false, "cleaned":false, "line":977, "disabled":false, "abstract":"ABC", "space":"ABA"}
+{"state":63, "bad":false, "line":978, "pos":93, "node":"AAC"}
+{}
+{"cleaned":false, "line":980, "abstract":"CCB"}
+{"state":40, "title":"ABA", "subtitle":"CAB", "query":"BC", "line":981, "date":"CA", "coauthors":"AB"}
+{}
+{"auth":"ABA", "subtitle":"ACC", "user":"AA", "query":"AC", "cleaned":true, "line":983, "date":"ACB", "node":"CB"}
+{"state":32, "title":"ABC", "org":58, "status":95, "line":984, "disabled":true, "pos":6, "space":"CBB"}
+{"title":"BCC", "subtitle":"CCC", "user":"BBC", "line":985, "public":false, "coauthors":"CCB", "node":"AA"}
+{"subtitle":"ACA", "query":"BCC", "status":43, "cleaned":true, "indexed":true, "line":986, "abstract":"CAC"}
+{}
+{"world":"CAB", "org":21, "indexed":true, "line":988, "abstract":"ABC"}
+{"title":"CBC", "status":66, "line":989}
+{}
+{"array":[5]}
+{"array":["foo", "bar", "baz"]}
+{"array":["bar", "baz", "foo"]}
+{"array":["bar", "baz"]}
+{"array":["baz", "foo"]}
+{"line":991, "abstract":"BA", "node":"BBB"}
+{"line":992, "disabled":true, "pos":29, "public":false}
+{"state":53, "wait":"CB", "subtitle":"CCC", "line":993, "date":"CAC", "public":false, "coauthors":"BB"}
+{"wait":"CBA", "title":"CA", "subtitle":"BB", "user":"BAA", "line":994, "disabled":true, "date":"BB", "coauthors":"CCC", "node":"CC"}
+{"title":"BB", "user":"AA", "query":"CAA", "status":43, "line":995, "pos":6, "abstract":"CC", "public":true}
+{"wait":"AC", "query":"BA", "line":996, "coauthors":"BB", "node":"CCC"}
+{"auth":"BC", "title":"CAC", "subtitle":"BA", "line":997, "date":"BAA"}
+{"wait":"AB", "user":"ABC", "line":998, "pos":41, "node":"CAC"}
+{"state":4, "title":"AC", "bad":true, "status":59, "line":999, "disabled":true}
+{"user":"BC", "line":1000}
+{"wait":null, "line":1000}
+{"age":25}
+{"age":25.0}
+{}
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index 04b969ae101518498f5f85fff69c8e38b10abfff..9f086763c253998821c6077252af50faafbce779 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -464,8 +464,8 @@ CREATE TEMP TABLE test_json (
 );
 INSERT INTO test_json VALUES
 ('scalar','"a scalar"'),
-('array','["zero", "one","two",null,"four","five"]'),
-('object','{"field1":"val1","field2":"val2","field3":null}');
+('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'),
+('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}');
 SELECT test_json -> 'x'
 FROM test_json
 WHERE json_type = 'scalar';
@@ -522,6 +522,36 @@ WHERE json_type = 'array';
  two
 (1 row)
 
+SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array';
+ ?column? 
+----------
+ [1,2,3]
+(1 row)
+
+SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array';
+ ?column? 
+----------
+ {"f1":9}
+(1 row)
+
+SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object';
+ ?column? 
+----------
+ 4
+(1 row)
+
+SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object';
+ ?column? 
+----------
+ [1,2,3]
+(1 row)
+
+SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object';
+ ?column? 
+----------
+ {"f1":9}
+(1 row)
+
 SELECT json_object_keys(test_json)
 FROM test_json
 WHERE json_type = 'scalar';
@@ -538,7 +568,20 @@ WHERE json_type = 'object';
  field1
  field2
  field3
-(3 rows)
+ field4
+ field5
+ field6
+(6 rows)
+
+-- test extending object_keys resultset - initial resultset size is 256
+select count(*) from
+    (select json_object_keys(json_object(array_agg(g)))
+     from (select unnest(array['f'||n,n::text])as g
+           from generate_series(1,300) as n) x ) y;
+ count 
+-------
+   300
+(1 row)
 
 -- nulls
 select (test_json->'field3') is null as expect_false
diff --git a/src/test/regress/expected/json_1.out b/src/test/regress/expected/json_1.out
index 07b25ca96c61a2d7ccf44540a9f7968e1002631d..13f7687608edd720ca5c218df3866c768e9dd366 100644
--- a/src/test/regress/expected/json_1.out
+++ b/src/test/regress/expected/json_1.out
@@ -464,8 +464,8 @@ CREATE TEMP TABLE test_json (
 );
 INSERT INTO test_json VALUES
 ('scalar','"a scalar"'),
-('array','["zero", "one","two",null,"four","five"]'),
-('object','{"field1":"val1","field2":"val2","field3":null}');
+('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'),
+('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}');
 SELECT test_json -> 'x'
 FROM test_json
 WHERE json_type = 'scalar';
@@ -522,6 +522,36 @@ WHERE json_type = 'array';
  two
 (1 row)
 
+SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array';
+ ?column? 
+----------
+ [1,2,3]
+(1 row)
+
+SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array';
+ ?column? 
+----------
+ {"f1":9}
+(1 row)
+
+SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object';
+ ?column? 
+----------
+ 4
+(1 row)
+
+SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object';
+ ?column? 
+----------
+ [1,2,3]
+(1 row)
+
+SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object';
+ ?column? 
+----------
+ {"f1":9}
+(1 row)
+
 SELECT json_object_keys(test_json)
 FROM test_json
 WHERE json_type = 'scalar';
@@ -538,7 +568,20 @@ WHERE json_type = 'object';
  field1
  field2
  field3
-(3 rows)
+ field4
+ field5
+ field6
+(6 rows)
+
+-- test extending object_keys resultset - initial resultset size is 256
+select count(*) from
+    (select json_object_keys(json_object(array_agg(g)))
+     from (select unnest(array['f'||n,n::text])as g
+           from generate_series(1,300) as n) x ) y;
+ count 
+-------
+   300
+(1 row)
 
 -- nulls
 select (test_json->'field3') is null as expect_false
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
new file mode 100644
index 0000000000000000000000000000000000000000..e25483d2f537d46da7c708e6cfd28259d209bf79
--- /dev/null
+++ b/src/test/regress/expected/jsonb.out
@@ -0,0 +1,2056 @@
+-- Strings.
+SELECT '""'::jsonb;				-- OK.
+ jsonb 
+-------
+ ""
+(1 row)
+
+SELECT $$''$$::jsonb;			-- ERROR, single quotes are not allowed
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT $$''$$::jsonb;
+               ^
+DETAIL:  Token "'" is invalid.
+CONTEXT:  JSON data, line 1: '...
+SELECT '"abc"'::jsonb;			-- OK
+ jsonb 
+-------
+ "abc"
+(1 row)
+
+SELECT '"abc'::jsonb;			-- ERROR, quotes not closed
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"abc'::jsonb;
+               ^
+DETAIL:  Token ""abc" is invalid.
+CONTEXT:  JSON data, line 1: "abc
+SELECT '"abc
+def"'::jsonb;					-- ERROR, unescaped newline in string constant
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"abc
+               ^
+DETAIL:  Character with value 0x0a must be escaped.
+CONTEXT:  JSON data, line 1: "abc
+SELECT '"\n\"\\"'::jsonb;		-- OK, legal escapes
+  jsonb   
+----------
+ "\n\"\\"
+(1 row)
+
+SELECT '"\v"'::jsonb;			-- ERROR, not a valid JSON escape
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\v"'::jsonb;
+               ^
+DETAIL:  Escape sequence "\v" is invalid.
+CONTEXT:  JSON data, line 1: "\v...
+SELECT '"\u"'::jsonb;			-- ERROR, incomplete escape
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\u"'::jsonb;
+               ^
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u"
+SELECT '"\u00"'::jsonb;			-- ERROR, incomplete escape
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\u00"'::jsonb;
+               ^
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u00"
+SELECT '"\u000g"'::jsonb;		-- ERROR, g is not a hex digit
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\u000g"'::jsonb;
+               ^
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u000g...
+SELECT '"\u0000"'::jsonb;		-- OK, legal escape
+   jsonb   
+-----------
+ "\\u0000"
+(1 row)
+
+-- use octet_length here so we don't get an odd unicode char in the
+-- output
+SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK
+ octet_length 
+--------------
+            5
+(1 row)
+
+-- Numbers.
+SELECT '1'::jsonb;				-- OK
+ jsonb 
+-------
+ 1
+(1 row)
+
+SELECT '0'::jsonb;				-- OK
+ jsonb 
+-------
+ 0
+(1 row)
+
+SELECT '01'::jsonb;				-- ERROR, not valid according to JSON spec
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '01'::jsonb;
+               ^
+DETAIL:  Token "01" is invalid.
+CONTEXT:  JSON data, line 1: 01
+SELECT '0.1'::jsonb;				-- OK
+ jsonb 
+-------
+ 0.1
+(1 row)
+
+SELECT '9223372036854775808'::jsonb;	-- OK, even though it's too large for int8
+        jsonb        
+---------------------
+ 9223372036854775808
+(1 row)
+
+SELECT '1e100'::jsonb;			-- OK
+                                                 jsonb                                                 
+-------------------------------------------------------------------------------------------------------
+ 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+(1 row)
+
+SELECT '1.3e100'::jsonb;			-- OK
+                                                 jsonb                                                 
+-------------------------------------------------------------------------------------------------------
+ 13000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+(1 row)
+
+SELECT '1f2'::jsonb;				-- ERROR
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '1f2'::jsonb;
+               ^
+DETAIL:  Token "1f2" is invalid.
+CONTEXT:  JSON data, line 1: 1f2
+SELECT '0.x1'::jsonb;			-- ERROR
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '0.x1'::jsonb;
+               ^
+DETAIL:  Token "0.x1" is invalid.
+CONTEXT:  JSON data, line 1: 0.x1
+SELECT '1.3ex100'::jsonb;		-- ERROR
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '1.3ex100'::jsonb;
+               ^
+DETAIL:  Token "1.3ex100" is invalid.
+CONTEXT:  JSON data, line 1: 1.3ex100
+-- Arrays.
+SELECT '[]'::jsonb;				-- OK
+ jsonb 
+-------
+ []
+(1 row)
+
+SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb;  -- OK
+                                                                                                  jsonb                                                                                                   
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
+(1 row)
+
+SELECT '[1,2]'::jsonb;			-- OK
+ jsonb  
+--------
+ [1, 2]
+(1 row)
+
+SELECT '[1,2,]'::jsonb;			-- ERROR, trailing comma
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '[1,2,]'::jsonb;
+               ^
+DETAIL:  Expected JSON value, but found "]".
+CONTEXT:  JSON data, line 1: [1,2,]
+SELECT '[1,2'::jsonb;			-- ERROR, no closing bracket
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '[1,2'::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: [1,2
+SELECT '[1,[2]'::jsonb;			-- ERROR, no closing bracket
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '[1,[2]'::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: [1,[2]
+-- Objects.
+SELECT '{}'::jsonb;				-- OK
+ jsonb 
+-------
+ {}
+(1 row)
+
+SELECT '{"abc"}'::jsonb;			-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc"}'::jsonb;
+               ^
+DETAIL:  Expected ":", but found "}".
+CONTEXT:  JSON data, line 1: {"abc"}
+SELECT '{"abc":1}'::jsonb;		-- OK
+   jsonb    
+------------
+ {"abc": 1}
+(1 row)
+
+SELECT '{1:"abc"}'::jsonb;		-- ERROR, keys must be strings
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{1:"abc"}'::jsonb;
+               ^
+DETAIL:  Expected string or "}", but found "1".
+CONTEXT:  JSON data, line 1: {1...
+SELECT '{"abc",1}'::jsonb;		-- ERROR, wrong separator
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc",1}'::jsonb;
+               ^
+DETAIL:  Expected ":", but found ",".
+CONTEXT:  JSON data, line 1: {"abc",...
+SELECT '{"abc"=1}'::jsonb;		-- ERROR, totally wrong separator
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc"=1}'::jsonb;
+               ^
+DETAIL:  Token "=" is invalid.
+CONTEXT:  JSON data, line 1: {"abc"=...
+SELECT '{"abc"::1}'::jsonb;		-- ERROR, another wrong separator
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc"::1}'::jsonb;
+               ^
+DETAIL:  Expected JSON value, but found ":".
+CONTEXT:  JSON data, line 1: {"abc"::...
+SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK
+                               jsonb                                
+--------------------------------------------------------------------
+ {"abc": 1, "def": 2, "ghi": [3, 4], "hij": {"klm": 5, "nop": [6]}}
+(1 row)
+
+SELECT '{"abc":1:2}'::jsonb;		-- ERROR, colon in wrong spot
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc":1:2}'::jsonb;
+               ^
+DETAIL:  Expected "," or "}", but found ":".
+CONTEXT:  JSON data, line 1: {"abc":1:...
+SELECT '{"abc":1,3}'::jsonb;		-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc":1,3}'::jsonb;
+               ^
+DETAIL:  Expected string, but found "3".
+CONTEXT:  JSON data, line 1: {"abc":1,3...
+-- Miscellaneous stuff.
+SELECT 'true'::jsonb;			-- OK
+ jsonb 
+-------
+ true
+(1 row)
+
+SELECT 'false'::jsonb;			-- OK
+ jsonb 
+-------
+ false
+(1 row)
+
+SELECT 'null'::jsonb;			-- OK
+ jsonb 
+-------
+ null
+(1 row)
+
+SELECT ' true '::jsonb;			-- OK, even with extra whitespace
+ jsonb 
+-------
+ true
+(1 row)
+
+SELECT 'true false'::jsonb;		-- ERROR, too many values
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'true false'::jsonb;
+               ^
+DETAIL:  Expected end of input, but found "false".
+CONTEXT:  JSON data, line 1: true false
+SELECT 'true, false'::jsonb;		-- ERROR, too many values
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'true, false'::jsonb;
+               ^
+DETAIL:  Expected end of input, but found ",".
+CONTEXT:  JSON data, line 1: true,...
+SELECT 'truf'::jsonb;			-- ERROR, not a keyword
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'truf'::jsonb;
+               ^
+DETAIL:  Token "truf" is invalid.
+CONTEXT:  JSON data, line 1: truf
+SELECT 'trues'::jsonb;			-- ERROR, not a keyword
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'trues'::jsonb;
+               ^
+DETAIL:  Token "trues" is invalid.
+CONTEXT:  JSON data, line 1: trues
+SELECT ''::jsonb;				-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT ''::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: 
+SELECT '    '::jsonb;			-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '    '::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1:     
+-- make sure jsonb is passed through json generators without being escaped
+SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
+      array_to_json       
+--------------------------
+ [{"a": 1},{"b": [2, 3]}]
+(1 row)
+
+-- jsonb extraction functions
+CREATE TEMP TABLE test_jsonb (
+       json_type text,
+       test_json jsonb
+);
+INSERT INTO test_jsonb VALUES
+('scalar','"a scalar"'),
+('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'),
+('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}');
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_object_field (jsonb -> text operator) on a scalar
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array';
+ERROR:  cannot call jsonb_object_field (jsonb -> text operator) on an array
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ "val2"
+(1 row)
+
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_object_field_text (jsonb ->> text operator) on a scalar
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array';
+ERROR:  cannot call jsonb_object_field_text (jsonb ->> text operator) on an array
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ val2
+(1 row)
+
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_array_element (jsonb -> int operator) on a scalar
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array';
+ ?column? 
+----------
+ "two"
+(1 row)
+
+SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object';
+ERROR:  cannot call jsonb_array_element (jsonb -> int operator) on an object
+SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array';
+ ?column?  
+-----------
+ {"f1": 9}
+(1 row)
+
+SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ 4
+(1 row)
+
+SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object';
+ ?column?  
+-----------
+ {"f1": 9}
+(1 row)
+
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_array_element_text on a scalar
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array';
+ ?column? 
+----------
+ two
+(1 row)
+
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object';
+ERROR:  cannot call jsonb_array_element_text on an object
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_object_keys on a scalar
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array';
+ERROR:  cannot call jsonb_object_keys on an array
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object';
+ jsonb_object_keys 
+-------------------
+ field1
+ field2
+ field3
+ field4
+ field5
+ field6
+(6 rows)
+
+-- nulls
+SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object';
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object';
+ expect_true 
+-------------
+ t
+(1 row)
+
+SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array';
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array';
+ expect_true 
+-------------
+ t
+(1 row)
+
+-- equality and inequality
+SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+CREATE TABLE testjsonb (j jsonb);
+\copy testjsonb FROM 'data/jsonb.data'
+-- containment
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}');
+ jsonb_contains 
+----------------
+ t
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}');
+ jsonb_contains 
+----------------
+ t
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}');
+ jsonb_contains 
+----------------
+ t
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ t
+(1 row)
+
+SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ t
+(1 row)
+
+SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ t
+(1 row)
+
+SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- Raw scalar may contain another raw scalar, array may contain a raw scalar
+SELECT '[5]'::jsonb @> '[5]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '5'::jsonb @> '5';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '[5]'::jsonb @> '5';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- But a raw scalar cannot contain an array
+SELECT '5'::jsonb @> '[5]';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- In general, one thing should always contain itself. Test array containment:
+SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- array containment string matching confusion bug
+SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- array length
+SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+ jsonb_array_length 
+--------------------
+                  5
+(1 row)
+
+SELECT jsonb_array_length('[]');
+ jsonb_array_length 
+--------------------
+                  0
+(1 row)
+
+SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}');
+ERROR:  cannot get array length of a non-array
+SELECT jsonb_array_length('4');
+ERROR:  cannot get array length of a scalar
+-- each
+SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+     jsonb_each     
+--------------------
+ (f1,"[1, 2, 3]")
+ (f2,"{""f3"": 1}")
+ (f4,null)
+(3 rows)
+
+SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+                          q                           
+------------------------------------------------------
+ (1,"""first""")
+ (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}")
+ (b,"[1, 2]")
+ (c,"""cc""")
+ (n,null)
+(5 rows)
+
+SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ key |   value   
+-----+-----------
+ f1  | [1, 2, 3]
+ f2  | {"f3": 1}
+ f4  | null
+ f5  | 99
+ f6  | "stringy"
+(5 rows)
+
+SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+ key |               value                
+-----+------------------------------------
+ 1   | "first"
+ a   | {"1": "first", "b": "c", "c": "b"}
+ b   | [1, 2]
+ c   | "cc"
+ n   | null
+(5 rows)
+
+SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+  jsonb_each_text   
+--------------------
+ (f1,"[1, 2, 3]")
+ (f2,"{""f3"": 1}")
+ (f4,)
+ (f5,null)
+(4 rows)
+
+SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+                          q                           
+------------------------------------------------------
+ (1,first)
+ (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}")
+ (b,"[1, 2]")
+ (c,cc)
+ (n,)
+(5 rows)
+
+SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ key |   value   
+-----+-----------
+ f1  | [1, 2, 3]
+ f2  | {"f3": 1}
+ f4  | 
+ f5  | 99
+ f6  | stringy
+(5 rows)
+
+SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+ key |               value                
+-----+------------------------------------
+ 1   | first
+ a   | {"1": "first", "b": "c", "c": "b"}
+ b   | [1, 2]
+ c   | cc
+ n   | 
+(5 rows)
+
+-- exists
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a');
+ jsonb_exists 
+--------------
+ t
+(1 row)
+
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b');
+ jsonb_exists 
+--------------
+ t
+(1 row)
+
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c');
+ jsonb_exists 
+--------------
+ f
+(1 row)
+
+SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a');
+ jsonb_exists 
+--------------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'a';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'b';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'c';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- array exists - array elements should behave as keys
+SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
+ count 
+-------
+     3
+(1 row)
+
+-- type sensitive array exists - should return no rows (since "exists" only
+-- matches strings that are either object keys or array elements)
+SELECT count(*) from testjsonb  WHERE j->'array' ? '5'::text;
+ count 
+-------
+     0
+(1 row)
+
+-- However, a raw scalar is *contained* within the array
+SELECT count(*) from testjsonb  WHERE j->'array' @> '5'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']);
+ jsonb_exists_any 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']);
+ jsonb_exists_any 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']);
+ jsonb_exists_any 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']);
+ jsonb_exists_any 
+------------------
+ f
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]);
+ jsonb_exists_any 
+------------------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d'];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']);
+ jsonb_exists_all 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']);
+ jsonb_exists_all 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']);
+ jsonb_exists_all 
+------------------
+ f
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']);
+ jsonb_exists_all 
+------------------
+ f
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]);
+ jsonb_exists_all 
+------------------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a'];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d'];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[];
+ ?column? 
+----------
+ t
+(1 row)
+
+-- typeof
+SELECT jsonb_typeof('{}') AS object;
+ object 
+--------
+ object
+(1 row)
+
+SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object;
+ object 
+--------
+ object
+(1 row)
+
+SELECT jsonb_typeof('[]') AS array;
+ array 
+-------
+ array
+(1 row)
+
+SELECT jsonb_typeof('["a", 1]') AS array;
+ array 
+-------
+ array
+(1 row)
+
+SELECT jsonb_typeof('null') AS "null";
+ null 
+------
+ null
+(1 row)
+
+SELECT jsonb_typeof('1') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('-1') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('1.0') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('1e2') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('-1.0') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('true') AS boolean;
+ boolean 
+---------
+ boolean
+(1 row)
+
+SELECT jsonb_typeof('false') AS boolean;
+ boolean 
+---------
+ boolean
+(1 row)
+
+SELECT jsonb_typeof('"hello"') AS string;
+ string 
+--------
+ string
+(1 row)
+
+SELECT jsonb_typeof('"true"') AS string;
+ string 
+--------
+ string
+(1 row)
+
+SELECT jsonb_typeof('"1.0"') AS string;
+ string 
+--------
+ string
+(1 row)
+
+-- extract_path, extract_path_as_text
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ jsonb_extract_path 
+--------------------
+ "stringy"
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ jsonb_extract_path 
+--------------------
+ {"f3": 1}
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ jsonb_extract_path 
+--------------------
+ "f3"
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ jsonb_extract_path 
+--------------------
+ 1
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ jsonb_extract_path_text 
+-------------------------
+ stringy
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ jsonb_extract_path_text 
+-------------------------
+ {"f3": 1}
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ jsonb_extract_path_text 
+-------------------------
+ f3
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ jsonb_extract_path_text 
+-------------------------
+ 1
+(1 row)
+
+-- extract_path nulls
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false;
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true;
+ expect_true 
+-------------
+ t
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false;
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true;
+ expect_true 
+-------------
+ t
+(1 row)
+
+-- extract_path operators
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6'];
+ ?column?  
+-----------
+ "stringy"
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2'];
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0'];
+ ?column? 
+----------
+ "f3"
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1'];
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6'];
+ ?column? 
+----------
+ stringy
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2'];
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0'];
+ ?column? 
+----------
+ f3
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1'];
+ ?column? 
+----------
+ 1
+(1 row)
+
+-- same using array literals
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f4,f6}';
+ ?column?  
+-----------
+ "stringy"
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2}';
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,0}';
+ ?column? 
+----------
+ "f3"
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,1}';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f4,f6}';
+ ?column? 
+----------
+ stringy
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2}';
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,0}';
+ ?column? 
+----------
+ f3
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,1}';
+ ?column? 
+----------
+ 1
+(1 row)
+
+-- same on jsonb scalars (expecting errors)
+SELECT '42'::jsonb#>array['f2'];
+ERROR:  cannot call extract path from a scalar
+SELECT '42'::jsonb#>array['0'];
+ERROR:  cannot call extract path from a scalar
+SELECT '42'::jsonb#>>array['f2'];
+ERROR:  cannot call extract path from a scalar
+SELECT '42'::jsonb#>>array['0'];
+ERROR:  cannot call extract path from a scalar
+-- array_elements
+SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+    jsonb_array_elements    
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ null
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+(6 rows)
+
+SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+           value            
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ null
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+(6 rows)
+
+SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+ jsonb_array_elements_text  
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ 
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+ stringy
+(7 rows)
+
+SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
+           value            
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ 
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+ stringy
+(7 rows)
+
+-- populate_record
+CREATE TYPE jbpop AS (a text, b int, c timestamp);
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q;
+   a    | b | c 
+--------+---+---
+ blurfl |   | 
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}', true) q;
+   a    | b | c 
+--------+---+---
+ blurfl |   | 
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}', true) q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a         | b | c 
+-------------------+---+---
+ [100, 200, false] |   | 
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a         | b |            c             
+-------------------+---+--------------------------
+ [100, 200, false] | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}', true) q;
+ERROR:  invalid input syntax for type timestamp: "[100, 200, false]"
+-- populate_recordset
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl |   | 
+        | 3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+   a    | b  |            c             
+--------+----+--------------------------
+ blurfl | 99 | 
+ def    |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl |   | 
+        | 3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+   a    | b  |            c             
+--------+----+--------------------------
+ blurfl | 99 | 
+ def    |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+        a        | b  |            c             
+-----------------+----+--------------------------
+ [100, 200, 300] | 99 | 
+ {"z": true}     |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ERROR:  invalid input syntax for type timestamp: "[100, 200, 300]"
+-- using the default use_json_as_text argument
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl |   | 
+        | 3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+   a    | b  |            c             
+--------+----+--------------------------
+ blurfl | 99 | 
+ def    |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ERROR:  cannot populate with a nested object unless use_json_as_text is true
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ERROR:  cannot populate with a nested object unless use_json_as_text is true
+-- handling of unicode surrogate pairs
+SELECT octet_length((jsonb '{ "a":  "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8;
+ correct_in_utf8 
+-----------------
+              10
+(1 row)
+
+SELECT jsonb '{ "a":  "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ud83d\ud83d" }' -> 'a';
+                     ^
+DETAIL:  Unicode high surrogate must not follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ude04\ud83d" }' -> 'a';
+                     ^
+DETAIL:  Unicode low surrogate must follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ud83dX" }' -> 'a'; -- orphan high surrogate
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ud83dX" }' -> 'a';
+                     ^
+DETAIL:  Unicode low surrogate must follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ude04X" }' -> 'a'; -- orphan low surrogate
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ude04X" }' -> 'a';
+                     ^
+DETAIL:  Unicode low surrogate must follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+-- handling of simple unicode escapes
+SELECT jsonb '{ "a":  "the Copyright \u00a9 sign" }' ->> 'a' AS correct_in_utf8;
+   correct_in_utf8    
+----------------------
+ the Copyright © sign
+(1 row)
+
+SELECT jsonb '{ "a":  "dollar \u0024 character" }' ->> 'a' AS correct_everyWHERE;
+ correct_everywhere 
+--------------------
+ dollar $ character
+(1 row)
+
+SELECT jsonb '{ "a":  "null \u0000 escape" }' ->> 'a' AS not_unescaped;
+   not_unescaped    
+--------------------
+ null \u0000 escape
+(1 row)
+
+-- indexing
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ? 'public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
+ count 
+-------
+   337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+ count 
+-------
+    42
+(1 row)
+
+CREATE INDEX jidx ON testjsonb USING gin (j);
+SET enable_seqscan = off;
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}';
+ count 
+-------
+     3
+(1 row)
+
+-- excercise GIN_SEARCH_MODE_ALL
+SELECT count(*) FROM testjsonb WHERE j @> '{}';
+ count 
+-------
+  1009
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ? 'public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
+ count 
+-------
+   337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+ count 
+-------
+    42
+(1 row)
+
+-- array exists - array elements should behave as keys (for GIN index scans too)
+CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
+SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
+ count 
+-------
+     3
+(1 row)
+
+-- type sensitive array exists - should return no rows (since "exists" only
+-- matches strings that are either object keys or array elements)
+SELECT count(*) from testjsonb  WHERE j->'array' ? '5'::text;
+ count 
+-------
+     0
+(1 row)
+
+-- However, a raw scalar is *contained* within the array
+SELECT count(*) from testjsonb  WHERE j->'array' @> '5'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+RESET enable_seqscan;
+SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow;
+ count 
+-------
+  4788
+(1 row)
+
+SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key;
+    key    | count 
+-----------+-------
+ line      |   884
+ query     |   207
+ pos       |   203
+ node      |   202
+ space     |   197
+ status    |   195
+ public    |   194
+ title     |   190
+ wait      |   190
+ org       |   189
+ user      |   189
+ coauthors |   188
+ disabled  |   185
+ indexed   |   184
+ cleaned   |   180
+ bad       |   179
+ date      |   179
+ world     |   176
+ state     |   172
+ subtitle  |   169
+ auth      |   168
+ abstract  |   161
+ array     |     5
+ age       |     2
+(24 rows)
+
+-- sort/hash
+SELECT count(distinct j) FROM testjsonb;
+ count 
+-------
+   891
+(1 row)
+
+SET enable_hashagg = off;
+SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2;
+ count 
+-------
+   891
+(1 row)
+
+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;
+ count 
+-------
+   891
+(1 row)
+
+SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j);
+ j  
+----
+ {}
+(1 row)
+
+SET enable_sort = on;
+RESET enable_hashagg;
+RESET enable_sort;
+DROP INDEX jidx;
+DROP INDEX jidx_array;
+-- btree
+CREATE INDEX jidx ON testjsonb USING btree (j);
+SET enable_seqscan = off;
+SELECT count(*) FROM testjsonb WHERE j > '{"p":1}';
+ count 
+-------
+   884
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}';
+ count 
+-------
+     1
+(1 row)
+
+--gin hash
+DROP INDEX jidx;
+CREATE INDEX jidx ON testjsonb USING gin (j jsonb_hash_ops);
+SET enable_seqscan = off;
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+ count 
+-------
+     2
+(1 row)
+
+-- excercise GIN_SEARCH_MODE_ALL
+SELECT count(*) FROM testjsonb WHERE j @> '{}';
+ count 
+-------
+  1009
+(1 row)
+
+RESET enable_seqscan;
+DROP INDEX jidx;
+-- nested tests
+SELECT '{"ff":{"a":12,"b":16}}'::jsonb;
+           jsonb            
+----------------------------
+ {"ff": {"a": 12, "b": 16}}
+(1 row)
+
+SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb;
+                 jsonb                 
+---------------------------------------
+ {"ff": {"a": 12, "b": 16}, "qq": 123}
+(1 row)
+
+SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb;
+                                              jsonb                                               
+--------------------------------------------------------------------------------------------------
+ {"aa": ["a", "aaa"], "qq": {"a": 12, "b": 16, "c": ["c1", "c2"], "d": {"d1": "d3", "d2": "d2"}}}
+(1 row)
+
+SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb;
+                                                jsonb                                                 
+------------------------------------------------------------------------------------------------------
+ {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2"], "d": {"d1": "d1", "d2": "d2"}}}
+(1 row)
+
+SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb;
+                                                          jsonb                                                          
+-------------------------------------------------------------------------------------------------------------------------
+ {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2", ["c3"], {"c4": 4}], "d": {"d1": "d1", "d2": "d2"}}}
+(1 row)
+
+SELECT '{"ff":["a","aaa"]}'::jsonb;
+        jsonb         
+----------------------
+ {"ff": ["a", "aaa"]}
+(1 row)
+
+SELECT
+  '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff',
+  '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq',
+  ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f,
+  ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t,
+   '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x';
+      ?column?      | ?column? | f | t | ?column? 
+--------------------+----------+---+---+----------
+ {"a": 12, "b": 16} | 123      | f | t | [1, 2]
+(1 row)
+
+-- nested containment
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- nested object field / array index lookup
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n';
+ ?column? 
+----------
+ null
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b';
+ ?column? 
+----------
+ [1, 2]
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c';
+ ?column? 
+----------
+ {"1": 2}
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd';
+   ?column?    
+---------------
+ {"1": [2, 3]}
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1';
+ ?column? 
+----------
+ [2, 3]
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error
+ERROR:  cannot call jsonb_array_element (jsonb -> int operator) on an object
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 0;
+ ?column? 
+----------
+ "a"
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 1;
+ ?column? 
+----------
+ "b"
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 2;
+ ?column? 
+----------
+ "c"
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 3;
+ ?column? 
+----------
+ [1, 2]
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1;
+ ?column? 
+----------
+ 2
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 4;
+ ?column? 
+----------
+ null
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 5;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> -1;
+ ?column? 
+----------
+ 
+(1 row)
+
+--nested path extraction
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}';
+ ?column? 
+----------
+ "b"
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}';
+ ?column? 
+----------
+ 2
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}';
+ ?column? 
+----------
+ 3
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}';
+ ?column? 
+----------
+ 0
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}';
+ ?column? 
+----------
+ [3, 4]
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}';
+   ?column?    
+---------------
+ {"5": "five"}
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}';
+ ?column? 
+----------
+ "five"
+(1 row)
+
+--nested exists
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e';
+ ?column? 
+----------
+ f
+(1 row)
+
diff --git a/src/test/regress/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out
new file mode 100644
index 0000000000000000000000000000000000000000..06af5c8252ad984b8c46b19d452e7671b181deb8
--- /dev/null
+++ b/src/test/regress/expected/jsonb_1.out
@@ -0,0 +1,2056 @@
+-- Strings.
+SELECT '""'::jsonb;				-- OK.
+ jsonb 
+-------
+ ""
+(1 row)
+
+SELECT $$''$$::jsonb;			-- ERROR, single quotes are not allowed
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT $$''$$::jsonb;
+               ^
+DETAIL:  Token "'" is invalid.
+CONTEXT:  JSON data, line 1: '...
+SELECT '"abc"'::jsonb;			-- OK
+ jsonb 
+-------
+ "abc"
+(1 row)
+
+SELECT '"abc'::jsonb;			-- ERROR, quotes not closed
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"abc'::jsonb;
+               ^
+DETAIL:  Token ""abc" is invalid.
+CONTEXT:  JSON data, line 1: "abc
+SELECT '"abc
+def"'::jsonb;					-- ERROR, unescaped newline in string constant
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"abc
+               ^
+DETAIL:  Character with value 0x0a must be escaped.
+CONTEXT:  JSON data, line 1: "abc
+SELECT '"\n\"\\"'::jsonb;		-- OK, legal escapes
+  jsonb   
+----------
+ "\n\"\\"
+(1 row)
+
+SELECT '"\v"'::jsonb;			-- ERROR, not a valid JSON escape
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\v"'::jsonb;
+               ^
+DETAIL:  Escape sequence "\v" is invalid.
+CONTEXT:  JSON data, line 1: "\v...
+SELECT '"\u"'::jsonb;			-- ERROR, incomplete escape
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\u"'::jsonb;
+               ^
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u"
+SELECT '"\u00"'::jsonb;			-- ERROR, incomplete escape
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\u00"'::jsonb;
+               ^
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u00"
+SELECT '"\u000g"'::jsonb;		-- ERROR, g is not a hex digit
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '"\u000g"'::jsonb;
+               ^
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u000g...
+SELECT '"\u0000"'::jsonb;		-- OK, legal escape
+   jsonb   
+-----------
+ "\\u0000"
+(1 row)
+
+-- use octet_length here so we don't get an odd unicode char in the
+-- output
+SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT octet_length('"\uaBcD"'::jsonb::text);
+                            ^
+DETAIL:  Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8.
+CONTEXT:  JSON data, line 1: ...
+-- Numbers.
+SELECT '1'::jsonb;				-- OK
+ jsonb 
+-------
+ 1
+(1 row)
+
+SELECT '0'::jsonb;				-- OK
+ jsonb 
+-------
+ 0
+(1 row)
+
+SELECT '01'::jsonb;				-- ERROR, not valid according to JSON spec
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '01'::jsonb;
+               ^
+DETAIL:  Token "01" is invalid.
+CONTEXT:  JSON data, line 1: 01
+SELECT '0.1'::jsonb;				-- OK
+ jsonb 
+-------
+ 0.1
+(1 row)
+
+SELECT '9223372036854775808'::jsonb;	-- OK, even though it's too large for int8
+        jsonb        
+---------------------
+ 9223372036854775808
+(1 row)
+
+SELECT '1e100'::jsonb;			-- OK
+                                                 jsonb                                                 
+-------------------------------------------------------------------------------------------------------
+ 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+(1 row)
+
+SELECT '1.3e100'::jsonb;			-- OK
+                                                 jsonb                                                 
+-------------------------------------------------------------------------------------------------------
+ 13000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+(1 row)
+
+SELECT '1f2'::jsonb;				-- ERROR
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '1f2'::jsonb;
+               ^
+DETAIL:  Token "1f2" is invalid.
+CONTEXT:  JSON data, line 1: 1f2
+SELECT '0.x1'::jsonb;			-- ERROR
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '0.x1'::jsonb;
+               ^
+DETAIL:  Token "0.x1" is invalid.
+CONTEXT:  JSON data, line 1: 0.x1
+SELECT '1.3ex100'::jsonb;		-- ERROR
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '1.3ex100'::jsonb;
+               ^
+DETAIL:  Token "1.3ex100" is invalid.
+CONTEXT:  JSON data, line 1: 1.3ex100
+-- Arrays.
+SELECT '[]'::jsonb;				-- OK
+ jsonb 
+-------
+ []
+(1 row)
+
+SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb;  -- OK
+                                                                                                  jsonb                                                                                                   
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
+(1 row)
+
+SELECT '[1,2]'::jsonb;			-- OK
+ jsonb  
+--------
+ [1, 2]
+(1 row)
+
+SELECT '[1,2,]'::jsonb;			-- ERROR, trailing comma
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '[1,2,]'::jsonb;
+               ^
+DETAIL:  Expected JSON value, but found "]".
+CONTEXT:  JSON data, line 1: [1,2,]
+SELECT '[1,2'::jsonb;			-- ERROR, no closing bracket
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '[1,2'::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: [1,2
+SELECT '[1,[2]'::jsonb;			-- ERROR, no closing bracket
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '[1,[2]'::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: [1,[2]
+-- Objects.
+SELECT '{}'::jsonb;				-- OK
+ jsonb 
+-------
+ {}
+(1 row)
+
+SELECT '{"abc"}'::jsonb;			-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc"}'::jsonb;
+               ^
+DETAIL:  Expected ":", but found "}".
+CONTEXT:  JSON data, line 1: {"abc"}
+SELECT '{"abc":1}'::jsonb;		-- OK
+   jsonb    
+------------
+ {"abc": 1}
+(1 row)
+
+SELECT '{1:"abc"}'::jsonb;		-- ERROR, keys must be strings
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{1:"abc"}'::jsonb;
+               ^
+DETAIL:  Expected string or "}", but found "1".
+CONTEXT:  JSON data, line 1: {1...
+SELECT '{"abc",1}'::jsonb;		-- ERROR, wrong separator
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc",1}'::jsonb;
+               ^
+DETAIL:  Expected ":", but found ",".
+CONTEXT:  JSON data, line 1: {"abc",...
+SELECT '{"abc"=1}'::jsonb;		-- ERROR, totally wrong separator
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc"=1}'::jsonb;
+               ^
+DETAIL:  Token "=" is invalid.
+CONTEXT:  JSON data, line 1: {"abc"=...
+SELECT '{"abc"::1}'::jsonb;		-- ERROR, another wrong separator
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc"::1}'::jsonb;
+               ^
+DETAIL:  Expected JSON value, but found ":".
+CONTEXT:  JSON data, line 1: {"abc"::...
+SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK
+                               jsonb                                
+--------------------------------------------------------------------
+ {"abc": 1, "def": 2, "ghi": [3, 4], "hij": {"klm": 5, "nop": [6]}}
+(1 row)
+
+SELECT '{"abc":1:2}'::jsonb;		-- ERROR, colon in wrong spot
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc":1:2}'::jsonb;
+               ^
+DETAIL:  Expected "," or "}", but found ":".
+CONTEXT:  JSON data, line 1: {"abc":1:...
+SELECT '{"abc":1,3}'::jsonb;		-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{"abc":1,3}'::jsonb;
+               ^
+DETAIL:  Expected string, but found "3".
+CONTEXT:  JSON data, line 1: {"abc":1,3...
+-- Miscellaneous stuff.
+SELECT 'true'::jsonb;			-- OK
+ jsonb 
+-------
+ true
+(1 row)
+
+SELECT 'false'::jsonb;			-- OK
+ jsonb 
+-------
+ false
+(1 row)
+
+SELECT 'null'::jsonb;			-- OK
+ jsonb 
+-------
+ null
+(1 row)
+
+SELECT ' true '::jsonb;			-- OK, even with extra whitespace
+ jsonb 
+-------
+ true
+(1 row)
+
+SELECT 'true false'::jsonb;		-- ERROR, too many values
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'true false'::jsonb;
+               ^
+DETAIL:  Expected end of input, but found "false".
+CONTEXT:  JSON data, line 1: true false
+SELECT 'true, false'::jsonb;		-- ERROR, too many values
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'true, false'::jsonb;
+               ^
+DETAIL:  Expected end of input, but found ",".
+CONTEXT:  JSON data, line 1: true,...
+SELECT 'truf'::jsonb;			-- ERROR, not a keyword
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'truf'::jsonb;
+               ^
+DETAIL:  Token "truf" is invalid.
+CONTEXT:  JSON data, line 1: truf
+SELECT 'trues'::jsonb;			-- ERROR, not a keyword
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT 'trues'::jsonb;
+               ^
+DETAIL:  Token "trues" is invalid.
+CONTEXT:  JSON data, line 1: trues
+SELECT ''::jsonb;				-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT ''::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: 
+SELECT '    '::jsonb;			-- ERROR, no value
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '    '::jsonb;
+               ^
+DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1:     
+-- make sure jsonb is passed through json generators without being escaped
+SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
+      array_to_json       
+--------------------------
+ [{"a": 1},{"b": [2, 3]}]
+(1 row)
+
+-- jsonb extraction functions
+CREATE TEMP TABLE test_jsonb (
+       json_type text,
+       test_json jsonb
+);
+INSERT INTO test_jsonb VALUES
+('scalar','"a scalar"'),
+('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'),
+('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}');
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_object_field (jsonb -> text operator) on a scalar
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array';
+ERROR:  cannot call jsonb_object_field (jsonb -> text operator) on an array
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ "val2"
+(1 row)
+
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_object_field_text (jsonb ->> text operator) on a scalar
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array';
+ERROR:  cannot call jsonb_object_field_text (jsonb ->> text operator) on an array
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ val2
+(1 row)
+
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_array_element (jsonb -> int operator) on a scalar
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array';
+ ?column? 
+----------
+ "two"
+(1 row)
+
+SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object';
+ERROR:  cannot call jsonb_array_element (jsonb -> int operator) on an object
+SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array';
+ ?column?  
+-----------
+ {"f1": 9}
+(1 row)
+
+SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object';
+ ?column? 
+----------
+ 4
+(1 row)
+
+SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object';
+ ?column?  
+-----------
+ {"f1": 9}
+(1 row)
+
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_array_element_text on a scalar
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array';
+ ?column? 
+----------
+ two
+(1 row)
+
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object';
+ERROR:  cannot call jsonb_array_element_text on an object
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar';
+ERROR:  cannot call jsonb_object_keys on a scalar
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array';
+ERROR:  cannot call jsonb_object_keys on an array
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object';
+ jsonb_object_keys 
+-------------------
+ field1
+ field2
+ field3
+ field4
+ field5
+ field6
+(6 rows)
+
+-- nulls
+SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object';
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object';
+ expect_true 
+-------------
+ t
+(1 row)
+
+SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array';
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array';
+ expect_true 
+-------------
+ t
+(1 row)
+
+-- equality and inequality
+SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+CREATE TABLE testjsonb (j jsonb);
+\copy testjsonb FROM 'data/jsonb.data'
+-- containment
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}');
+ jsonb_contains 
+----------------
+ t
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}');
+ jsonb_contains 
+----------------
+ t
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}');
+ jsonb_contains 
+----------------
+ t
+(1 row)
+
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}');
+ jsonb_contains 
+----------------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ t
+(1 row)
+
+SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ t
+(1 row)
+
+SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ t
+(1 row)
+
+SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}');
+ jsonb_contained 
+-----------------
+ f
+(1 row)
+
+SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- Raw scalar may contain another raw scalar, array may contain a raw scalar
+SELECT '[5]'::jsonb @> '[5]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '5'::jsonb @> '5';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '[5]'::jsonb @> '5';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- But a raw scalar cannot contain an array
+SELECT '5'::jsonb @> '[5]';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- In general, one thing should always contain itself. Test array containment:
+SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- array containment string matching confusion bug
+SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- array length
+SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+ jsonb_array_length 
+--------------------
+                  5
+(1 row)
+
+SELECT jsonb_array_length('[]');
+ jsonb_array_length 
+--------------------
+                  0
+(1 row)
+
+SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}');
+ERROR:  cannot get array length of a non-array
+SELECT jsonb_array_length('4');
+ERROR:  cannot get array length of a scalar
+-- each
+SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+     jsonb_each     
+--------------------
+ (f1,"[1, 2, 3]")
+ (f2,"{""f3"": 1}")
+ (f4,null)
+(3 rows)
+
+SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+                          q                           
+------------------------------------------------------
+ (1,"""first""")
+ (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}")
+ (b,"[1, 2]")
+ (c,"""cc""")
+ (n,null)
+(5 rows)
+
+SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ key |   value   
+-----+-----------
+ f1  | [1, 2, 3]
+ f2  | {"f3": 1}
+ f4  | null
+ f5  | 99
+ f6  | "stringy"
+(5 rows)
+
+SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+ key |               value                
+-----+------------------------------------
+ 1   | "first"
+ a   | {"1": "first", "b": "c", "c": "b"}
+ b   | [1, 2]
+ c   | "cc"
+ n   | null
+(5 rows)
+
+SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+  jsonb_each_text   
+--------------------
+ (f1,"[1, 2, 3]")
+ (f2,"{""f3"": 1}")
+ (f4,)
+ (f5,null)
+(4 rows)
+
+SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+                          q                           
+------------------------------------------------------
+ (1,first)
+ (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}")
+ (b,"[1, 2]")
+ (c,cc)
+ (n,)
+(5 rows)
+
+SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ key |   value   
+-----+-----------
+ f1  | [1, 2, 3]
+ f2  | {"f3": 1}
+ f4  | 
+ f5  | 99
+ f6  | stringy
+(5 rows)
+
+SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+ key |               value                
+-----+------------------------------------
+ 1   | first
+ a   | {"1": "first", "b": "c", "c": "b"}
+ b   | [1, 2]
+ c   | cc
+ n   | 
+(5 rows)
+
+-- exists
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a');
+ jsonb_exists 
+--------------
+ t
+(1 row)
+
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b');
+ jsonb_exists 
+--------------
+ t
+(1 row)
+
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c');
+ jsonb_exists 
+--------------
+ f
+(1 row)
+
+SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a');
+ jsonb_exists 
+--------------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'a';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'b';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'c';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- array exists - array elements should behave as keys
+SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
+ count 
+-------
+     3
+(1 row)
+
+-- type sensitive array exists - should return no rows (since "exists" only
+-- matches strings that are either object keys or array elements)
+SELECT count(*) from testjsonb  WHERE j->'array' ? '5'::text;
+ count 
+-------
+     0
+(1 row)
+
+-- However, a raw scalar is *contained* within the array
+SELECT count(*) from testjsonb  WHERE j->'array' @> '5'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']);
+ jsonb_exists_any 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']);
+ jsonb_exists_any 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']);
+ jsonb_exists_any 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']);
+ jsonb_exists_any 
+------------------
+ f
+(1 row)
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]);
+ jsonb_exists_any 
+------------------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d'];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']);
+ jsonb_exists_all 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']);
+ jsonb_exists_all 
+------------------
+ t
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']);
+ jsonb_exists_all 
+------------------
+ f
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']);
+ jsonb_exists_all 
+------------------
+ f
+(1 row)
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]);
+ jsonb_exists_all 
+------------------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a'];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d'];
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b'];
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[];
+ ?column? 
+----------
+ t
+(1 row)
+
+-- typeof
+SELECT jsonb_typeof('{}') AS object;
+ object 
+--------
+ object
+(1 row)
+
+SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object;
+ object 
+--------
+ object
+(1 row)
+
+SELECT jsonb_typeof('[]') AS array;
+ array 
+-------
+ array
+(1 row)
+
+SELECT jsonb_typeof('["a", 1]') AS array;
+ array 
+-------
+ array
+(1 row)
+
+SELECT jsonb_typeof('null') AS "null";
+ null 
+------
+ null
+(1 row)
+
+SELECT jsonb_typeof('1') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('-1') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('1.0') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('1e2') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('-1.0') AS number;
+ number 
+--------
+ number
+(1 row)
+
+SELECT jsonb_typeof('true') AS boolean;
+ boolean 
+---------
+ boolean
+(1 row)
+
+SELECT jsonb_typeof('false') AS boolean;
+ boolean 
+---------
+ boolean
+(1 row)
+
+SELECT jsonb_typeof('"hello"') AS string;
+ string 
+--------
+ string
+(1 row)
+
+SELECT jsonb_typeof('"true"') AS string;
+ string 
+--------
+ string
+(1 row)
+
+SELECT jsonb_typeof('"1.0"') AS string;
+ string 
+--------
+ string
+(1 row)
+
+-- extract_path, extract_path_as_text
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ jsonb_extract_path 
+--------------------
+ "stringy"
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ jsonb_extract_path 
+--------------------
+ {"f3": 1}
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ jsonb_extract_path 
+--------------------
+ "f3"
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ jsonb_extract_path 
+--------------------
+ 1
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ jsonb_extract_path_text 
+-------------------------
+ stringy
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ jsonb_extract_path_text 
+-------------------------
+ {"f3": 1}
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ jsonb_extract_path_text 
+-------------------------
+ f3
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ jsonb_extract_path_text 
+-------------------------
+ 1
+(1 row)
+
+-- extract_path nulls
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false;
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true;
+ expect_true 
+-------------
+ t
+(1 row)
+
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false;
+ expect_false 
+--------------
+ f
+(1 row)
+
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true;
+ expect_true 
+-------------
+ t
+(1 row)
+
+-- extract_path operators
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6'];
+ ?column?  
+-----------
+ "stringy"
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2'];
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0'];
+ ?column? 
+----------
+ "f3"
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1'];
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6'];
+ ?column? 
+----------
+ stringy
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2'];
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0'];
+ ?column? 
+----------
+ f3
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1'];
+ ?column? 
+----------
+ 1
+(1 row)
+
+-- same using array literals
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f4,f6}';
+ ?column?  
+-----------
+ "stringy"
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2}';
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,0}';
+ ?column? 
+----------
+ "f3"
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,1}';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f4,f6}';
+ ?column? 
+----------
+ stringy
+(1 row)
+
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2}';
+ ?column?  
+-----------
+ {"f3": 1}
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,0}';
+ ?column? 
+----------
+ f3
+(1 row)
+
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,1}';
+ ?column? 
+----------
+ 1
+(1 row)
+
+-- same on jsonb scalars (expecting errors)
+SELECT '42'::jsonb#>array['f2'];
+ERROR:  cannot call extract path from a scalar
+SELECT '42'::jsonb#>array['0'];
+ERROR:  cannot call extract path from a scalar
+SELECT '42'::jsonb#>>array['f2'];
+ERROR:  cannot call extract path from a scalar
+SELECT '42'::jsonb#>>array['0'];
+ERROR:  cannot call extract path from a scalar
+-- array_elements
+SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+    jsonb_array_elements    
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ null
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+(6 rows)
+
+SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+           value            
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ null
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+(6 rows)
+
+SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+ jsonb_array_elements_text  
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ 
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+ stringy
+(7 rows)
+
+SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
+           value            
+----------------------------
+ 1
+ true
+ [1, [2, 3]]
+ 
+ {"f1": 1, "f2": [7, 8, 9]}
+ false
+ stringy
+(7 rows)
+
+-- populate_record
+CREATE TYPE jbpop AS (a text, b int, c timestamp);
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q;
+   a    | b | c 
+--------+---+---
+ blurfl |   | 
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}', true) q;
+   a    | b | c 
+--------+---+---
+ blurfl |   | 
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}', true) q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a         | b | c 
+-------------------+---+---
+ [100, 200, false] |   | 
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a         | b |            c             
+-------------------+---+--------------------------
+ [100, 200, false] | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}', true) q;
+ERROR:  invalid input syntax for type timestamp: "[100, 200, false]"
+-- populate_recordset
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl |   | 
+        | 3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+   a    | b  |            c             
+--------+----+--------------------------
+ blurfl | 99 | 
+ def    |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl |   | 
+        | 3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+   a    | b  |            c             
+--------+----+--------------------------
+ blurfl | 99 | 
+ def    |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+        a        | b  |            c             
+-----------------+----+--------------------------
+ [100, 200, 300] | 99 | 
+ {"z": true}     |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ERROR:  invalid input syntax for type timestamp: "[100, 200, 300]"
+-- using the default use_json_as_text argument
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl |   | 
+        | 3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+   a    | b  |            c             
+--------+----+--------------------------
+ blurfl | 99 | 
+ def    |  3 | Fri Jan 20 10:42:53 2012
+(2 rows)
+
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ERROR:  cannot populate with a nested object unless use_json_as_text is true
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ERROR:  cannot populate with a nested object unless use_json_as_text is true
+-- handling of unicode surrogate pairs
+SELECT octet_length((jsonb '{ "a":  "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8;
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT octet_length((jsonb '{ "a":  "\ud83d\ude04\ud83d\udc3...
+                                   ^
+DETAIL:  Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ud83d\ud83d" }' -> 'a';
+                     ^
+DETAIL:  Unicode high surrogate must not follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ude04\ud83d" }' -> 'a';
+                     ^
+DETAIL:  Unicode low surrogate must follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ud83dX" }' -> 'a'; -- orphan high surrogate
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ud83dX" }' -> 'a';
+                     ^
+DETAIL:  Unicode low surrogate must follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "\ude04X" }' -> 'a'; -- orphan low surrogate
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "\ude04X" }' -> 'a';
+                     ^
+DETAIL:  Unicode low surrogate must follow a high surrogate.
+CONTEXT:  JSON data, line 1: { "a":...
+-- handling of simple unicode escapes
+SELECT jsonb '{ "a":  "the Copyright \u00a9 sign" }' ->> 'a' AS correct_in_utf8;
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a":  "the Copyright \u00a9 sign" }' ->> 'a'...
+                     ^
+DETAIL:  Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8.
+CONTEXT:  JSON data, line 1: { "a":...
+SELECT jsonb '{ "a":  "dollar \u0024 character" }' ->> 'a' AS correct_everyWHERE;
+ correct_everywhere 
+--------------------
+ dollar $ character
+(1 row)
+
+SELECT jsonb '{ "a":  "null \u0000 escape" }' ->> 'a' AS not_unescaped;
+   not_unescaped    
+--------------------
+ null \u0000 escape
+(1 row)
+
+-- indexing
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ? 'public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
+ count 
+-------
+   337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+ count 
+-------
+    42
+(1 row)
+
+CREATE INDEX jidx ON testjsonb USING gin (j);
+SET enable_seqscan = off;
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}';
+ count 
+-------
+     3
+(1 row)
+
+-- excercise GIN_SEARCH_MODE_ALL
+SELECT count(*) FROM testjsonb WHERE j @> '{}';
+ count 
+-------
+  1009
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ? 'public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
+ count 
+-------
+   337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+ count 
+-------
+    42
+(1 row)
+
+-- array exists - array elements should behave as keys (for GIN index scans too)
+CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
+SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
+ count 
+-------
+     3
+(1 row)
+
+-- type sensitive array exists - should return no rows (since "exists" only
+-- matches strings that are either object keys or array elements)
+SELECT count(*) from testjsonb  WHERE j->'array' ? '5'::text;
+ count 
+-------
+     0
+(1 row)
+
+-- However, a raw scalar is *contained* within the array
+SELECT count(*) from testjsonb  WHERE j->'array' @> '5'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+RESET enable_seqscan;
+SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow;
+ count 
+-------
+  4788
+(1 row)
+
+SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key;
+    key    | count 
+-----------+-------
+ line      |   884
+ query     |   207
+ pos       |   203
+ node      |   202
+ space     |   197
+ status    |   195
+ public    |   194
+ title     |   190
+ wait      |   190
+ org       |   189
+ user      |   189
+ coauthors |   188
+ disabled  |   185
+ indexed   |   184
+ cleaned   |   180
+ bad       |   179
+ date      |   179
+ world     |   176
+ state     |   172
+ subtitle  |   169
+ auth      |   168
+ abstract  |   161
+ array     |     5
+ age       |     2
+(24 rows)
+
+-- sort/hash
+SELECT count(distinct j) FROM testjsonb;
+ count 
+-------
+   891
+(1 row)
+
+SET enable_hashagg = off;
+SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2;
+ count 
+-------
+   891
+(1 row)
+
+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;
+ count 
+-------
+   891
+(1 row)
+
+SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j);
+ j  
+----
+ {}
+(1 row)
+
+SET enable_sort = on;
+RESET enable_hashagg;
+RESET enable_sort;
+DROP INDEX jidx;
+DROP INDEX jidx_array;
+-- btree
+CREATE INDEX jidx ON testjsonb USING btree (j);
+SET enable_seqscan = off;
+SELECT count(*) FROM testjsonb WHERE j > '{"p":1}';
+ count 
+-------
+   884
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}';
+ count 
+-------
+     1
+(1 row)
+
+--gin hash
+DROP INDEX jidx;
+CREATE INDEX jidx ON testjsonb USING gin (j jsonb_hash_ops);
+SET enable_seqscan = off;
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+ count 
+-------
+     2
+(1 row)
+
+-- excercise GIN_SEARCH_MODE_ALL
+SELECT count(*) FROM testjsonb WHERE j @> '{}';
+ count 
+-------
+  1009
+(1 row)
+
+RESET enable_seqscan;
+DROP INDEX jidx;
+-- nested tests
+SELECT '{"ff":{"a":12,"b":16}}'::jsonb;
+           jsonb            
+----------------------------
+ {"ff": {"a": 12, "b": 16}}
+(1 row)
+
+SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb;
+                 jsonb                 
+---------------------------------------
+ {"ff": {"a": 12, "b": 16}, "qq": 123}
+(1 row)
+
+SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb;
+                                              jsonb                                               
+--------------------------------------------------------------------------------------------------
+ {"aa": ["a", "aaa"], "qq": {"a": 12, "b": 16, "c": ["c1", "c2"], "d": {"d1": "d3", "d2": "d2"}}}
+(1 row)
+
+SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb;
+                                                jsonb                                                 
+------------------------------------------------------------------------------------------------------
+ {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2"], "d": {"d1": "d1", "d2": "d2"}}}
+(1 row)
+
+SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb;
+                                                          jsonb                                                          
+-------------------------------------------------------------------------------------------------------------------------
+ {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2", ["c3"], {"c4": 4}], "d": {"d1": "d1", "d2": "d2"}}}
+(1 row)
+
+SELECT '{"ff":["a","aaa"]}'::jsonb;
+        jsonb         
+----------------------
+ {"ff": ["a", "aaa"]}
+(1 row)
+
+SELECT
+  '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff',
+  '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq',
+  ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f,
+  ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t,
+   '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x';
+      ?column?      | ?column? | f | t | ?column? 
+--------------------+----------+---+---+----------
+ {"a": 12, "b": 16} | 123      | f | t | [1, 2]
+(1 row)
+
+-- nested containment
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- nested object field / array index lookup
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n';
+ ?column? 
+----------
+ null
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b';
+ ?column? 
+----------
+ [1, 2]
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c';
+ ?column? 
+----------
+ {"1": 2}
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd';
+   ?column?    
+---------------
+ {"1": [2, 3]}
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1';
+ ?column? 
+----------
+ [2, 3]
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error
+ERROR:  cannot call jsonb_array_element (jsonb -> int operator) on an object
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 0;
+ ?column? 
+----------
+ "a"
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 1;
+ ?column? 
+----------
+ "b"
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 2;
+ ?column? 
+----------
+ "c"
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 3;
+ ?column? 
+----------
+ [1, 2]
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1;
+ ?column? 
+----------
+ 2
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 4;
+ ?column? 
+----------
+ null
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 5;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> -1;
+ ?column? 
+----------
+ 
+(1 row)
+
+--nested path extraction
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}';
+ ?column? 
+----------
+ "b"
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}';
+ ?column? 
+----------
+ 2
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}';
+ ?column? 
+----------
+ 3
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}';
+ ?column? 
+----------
+ 0
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}';
+ ?column? 
+----------
+ [3, 4]
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}';
+   ?column?    
+---------------
+ {"5": "five"}
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}';
+ ?column? 
+----------
+ "five"
+(1 row)
+
+--nested exists
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e';
+ ?column? 
+----------
+ f
+(1 row)
+
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 5627b4a426d1fc684dfbbe119a4859f7a5747589..bf765014357a352ba1ec53812d35edc75ba559c5 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1127,6 +1127,10 @@ ORDER BY 1, 2, 3;
        2742 |            2 | @@@
        2742 |            3 | <@
        2742 |            4 | =
+       2742 |            7 | @>
+       2742 |            9 | ?
+       2742 |           10 | ?|
+       2742 |           11 | ?&
        4000 |            1 | <<
        4000 |            1 | ~<~
        4000 |            2 | &<
@@ -1149,7 +1153,7 @@ ORDER BY 1, 2, 3;
        4000 |           15 | >
        4000 |           16 | @>
        4000 |           18 | =
-(67 rows)
+(71 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 2e3eba83deeb526d390e37e865e0b1a995939347..c62be2a023e09a1ab8165a2841aad89510901328 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -98,8 +98,7 @@ test: event_trigger
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json indirect_toast
-
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast
 # ----------
 # Another group of parallel tests
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 4f1dedec2b5999e0f83878bc2e09379b75dafb3d..885ca9aa5ddae3086db57a57aa6dda8f7c681ff9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -122,6 +122,7 @@ test: xmlmap
 test: functional_deps
 test: advisory_lock
 test: json
+test: jsonb
 test: indirect_toast
 test: plancache
 test: limit
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 2d3f0bcc612d69deb8f24047f688fa1839931174..2ae5b8279902e741c17ab673509c9e261662b63b 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -136,8 +136,8 @@ CREATE TEMP TABLE test_json (
 
 INSERT INTO test_json VALUES
 ('scalar','"a scalar"'),
-('array','["zero", "one","two",null,"four","five"]'),
-('object','{"field1":"val1","field2":"val2","field3":null}');
+('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'),
+('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}');
 
 SELECT test_json -> 'x'
 FROM test_json
@@ -175,6 +175,13 @@ SELECT test_json->>2
 FROM test_json
 WHERE json_type = 'array';
 
+SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array';
+SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array';
+
+SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object';
+SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object';
+SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object';
+
 SELECT json_object_keys(test_json)
 FROM test_json
 WHERE json_type = 'scalar';
@@ -187,6 +194,13 @@ SELECT json_object_keys(test_json)
 FROM test_json
 WHERE json_type = 'object';
 
+-- test extending object_keys resultset - initial resultset size is 256
+
+select count(*) from
+    (select json_object_keys(json_object(array_agg(g)))
+     from (select unnest(array['f'||n,n::text])as g
+           from generate_series(1,300) as n) x ) y;
+
 -- nulls
 
 select (test_json->'field3') is null as expect_false
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
new file mode 100644
index 0000000000000000000000000000000000000000..1ef49138bb8220a502df8c880ddebee98a55a1ce
--- /dev/null
+++ b/src/test/regress/sql/jsonb.sql
@@ -0,0 +1,479 @@
+-- Strings.
+SELECT '""'::jsonb;				-- OK.
+SELECT $$''$$::jsonb;			-- ERROR, single quotes are not allowed
+SELECT '"abc"'::jsonb;			-- OK
+SELECT '"abc'::jsonb;			-- ERROR, quotes not closed
+SELECT '"abc
+def"'::jsonb;					-- ERROR, unescaped newline in string constant
+SELECT '"\n\"\\"'::jsonb;		-- OK, legal escapes
+SELECT '"\v"'::jsonb;			-- ERROR, not a valid JSON escape
+SELECT '"\u"'::jsonb;			-- ERROR, incomplete escape
+SELECT '"\u00"'::jsonb;			-- ERROR, incomplete escape
+SELECT '"\u000g"'::jsonb;		-- ERROR, g is not a hex digit
+SELECT '"\u0000"'::jsonb;		-- OK, legal escape
+-- use octet_length here so we don't get an odd unicode char in the
+-- output
+SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK
+
+-- Numbers.
+SELECT '1'::jsonb;				-- OK
+SELECT '0'::jsonb;				-- OK
+SELECT '01'::jsonb;				-- ERROR, not valid according to JSON spec
+SELECT '0.1'::jsonb;				-- OK
+SELECT '9223372036854775808'::jsonb;	-- OK, even though it's too large for int8
+SELECT '1e100'::jsonb;			-- OK
+SELECT '1.3e100'::jsonb;			-- OK
+SELECT '1f2'::jsonb;				-- ERROR
+SELECT '0.x1'::jsonb;			-- ERROR
+SELECT '1.3ex100'::jsonb;		-- ERROR
+
+-- Arrays.
+SELECT '[]'::jsonb;				-- OK
+SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb;  -- OK
+SELECT '[1,2]'::jsonb;			-- OK
+SELECT '[1,2,]'::jsonb;			-- ERROR, trailing comma
+SELECT '[1,2'::jsonb;			-- ERROR, no closing bracket
+SELECT '[1,[2]'::jsonb;			-- ERROR, no closing bracket
+
+-- Objects.
+SELECT '{}'::jsonb;				-- OK
+SELECT '{"abc"}'::jsonb;			-- ERROR, no value
+SELECT '{"abc":1}'::jsonb;		-- OK
+SELECT '{1:"abc"}'::jsonb;		-- ERROR, keys must be strings
+SELECT '{"abc",1}'::jsonb;		-- ERROR, wrong separator
+SELECT '{"abc"=1}'::jsonb;		-- ERROR, totally wrong separator
+SELECT '{"abc"::1}'::jsonb;		-- ERROR, another wrong separator
+SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK
+SELECT '{"abc":1:2}'::jsonb;		-- ERROR, colon in wrong spot
+SELECT '{"abc":1,3}'::jsonb;		-- ERROR, no value
+
+-- Miscellaneous stuff.
+SELECT 'true'::jsonb;			-- OK
+SELECT 'false'::jsonb;			-- OK
+SELECT 'null'::jsonb;			-- OK
+SELECT ' true '::jsonb;			-- OK, even with extra whitespace
+SELECT 'true false'::jsonb;		-- ERROR, too many values
+SELECT 'true, false'::jsonb;		-- ERROR, too many values
+SELECT 'truf'::jsonb;			-- ERROR, not a keyword
+SELECT 'trues'::jsonb;			-- ERROR, not a keyword
+SELECT ''::jsonb;				-- ERROR, no value
+SELECT '    '::jsonb;			-- ERROR, no value
+
+-- make sure jsonb is passed through json generators without being escaped
+SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
+
+-- jsonb extraction functions
+CREATE TEMP TABLE test_jsonb (
+       json_type text,
+       test_json jsonb
+);
+
+INSERT INTO test_jsonb VALUES
+('scalar','"a scalar"'),
+('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'),
+('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}');
+
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar';
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array';
+SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object';
+SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object';
+
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar';
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array';
+SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object';
+
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar';
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array';
+SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array';
+SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object';
+
+SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array';
+SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array';
+
+SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object';
+SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object';
+SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object';
+
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar';
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array';
+SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object';
+
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar';
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array';
+SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object';
+
+-- nulls
+SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object';
+SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object';
+SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array';
+SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array';
+
+-- equality and inequality
+SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb;
+SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb;
+
+SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb;
+SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb;
+
+CREATE TABLE testjsonb (j jsonb);
+\copy testjsonb FROM 'data/jsonb.data'
+
+-- containment
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}');
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}');
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}');
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}');
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}');
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}');
+SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}');
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}';
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}';
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}';
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}';
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
+SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
+
+SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
+SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}');
+SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}');
+SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}');
+SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}');
+SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
+SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}');
+SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}';
+-- Raw scalar may contain another raw scalar, array may contain a raw scalar
+SELECT '[5]'::jsonb @> '[5]';
+SELECT '5'::jsonb @> '5';
+SELECT '[5]'::jsonb @> '5';
+-- But a raw scalar cannot contain an array
+SELECT '5'::jsonb @> '[5]';
+-- In general, one thing should always contain itself. Test array containment:
+SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb;
+SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb;
+-- array containment string matching confusion bug
+SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}';
+
+-- array length
+SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+SELECT jsonb_array_length('[]');
+SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}');
+SELECT jsonb_array_length('4');
+
+-- each
+SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+
+SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q;
+
+-- exists
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a');
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b');
+SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c');
+SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a');
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'a';
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'b';
+SELECT jsonb '{"a":null, "b":"qq"}' ? 'c';
+SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a';
+-- array exists - array elements should behave as keys
+SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
+-- type sensitive array exists - should return no rows (since "exists" only
+-- matches strings that are either object keys or array elements)
+SELECT count(*) from testjsonb  WHERE j->'array' ? '5'::text;
+-- However, a raw scalar is *contained* within the array
+SELECT count(*) from testjsonb  WHERE j->'array' @> '5'::jsonb;
+
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']);
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']);
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']);
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']);
+SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]);
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[];
+
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']);
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']);
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']);
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']);
+SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]);
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b'];
+SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[];
+
+-- typeof
+SELECT jsonb_typeof('{}') AS object;
+SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object;
+SELECT jsonb_typeof('[]') AS array;
+SELECT jsonb_typeof('["a", 1]') AS array;
+SELECT jsonb_typeof('null') AS "null";
+SELECT jsonb_typeof('1') AS number;
+SELECT jsonb_typeof('-1') AS number;
+SELECT jsonb_typeof('1.0') AS number;
+SELECT jsonb_typeof('1e2') AS number;
+SELECT jsonb_typeof('-1.0') AS number;
+SELECT jsonb_typeof('true') AS boolean;
+SELECT jsonb_typeof('false') AS boolean;
+SELECT jsonb_typeof('"hello"') AS string;
+SELECT jsonb_typeof('"true"') AS string;
+SELECT jsonb_typeof('"1.0"') AS string;
+
+-- extract_path, extract_path_as_text
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+
+-- extract_path nulls
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false;
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true;
+SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false;
+SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true;
+
+-- extract_path operators
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6'];
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2'];
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0'];
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1'];
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6'];
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2'];
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0'];
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1'];
+
+-- same using array literals
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f4,f6}';
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2}';
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,0}';
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,1}';
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f4,f6}';
+SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2}';
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,0}';
+SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,1}';
+
+-- same on jsonb scalars (expecting errors)
+SELECT '42'::jsonb#>array['f2'];
+SELECT '42'::jsonb#>array['0'];
+SELECT '42'::jsonb#>>array['f2'];
+SELECT '42'::jsonb#>>array['0'];
+
+-- array_elements
+SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
+
+-- populate_record
+CREATE TYPE jbpop AS (a text, b int, c timestamp);
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q;
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q;
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}', true) q;
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}', true) q;
+
+SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}', true) q;
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}', true) q;
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}', true) q;
+
+-- populate_recordset
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+
+-- using the default use_json_as_text argument
+SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+
+
+-- handling of unicode surrogate pairs
+SELECT octet_length((jsonb '{ "a":  "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8;
+SELECT jsonb '{ "a":  "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+SELECT jsonb '{ "a":  "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+SELECT jsonb '{ "a":  "\ud83dX" }' -> 'a'; -- orphan high surrogate
+SELECT jsonb '{ "a":  "\ude04X" }' -> 'a'; -- orphan low surrogate
+
+-- handling of simple unicode escapes
+SELECT jsonb '{ "a":  "the Copyright \u00a9 sign" }' ->> 'a' AS correct_in_utf8;
+SELECT jsonb '{ "a":  "dollar \u0024 character" }' ->> 'a' AS correct_everyWHERE;
+SELECT jsonb '{ "a":  "null \u0000 escape" }' ->> 'a' AS not_unescaped;
+
+-- indexing
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+SELECT count(*) FROM testjsonb WHERE j ? 'public';
+SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+
+CREATE INDEX jidx ON testjsonb USING gin (j);
+SET enable_seqscan = off;
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}';
+-- excercise GIN_SEARCH_MODE_ALL
+SELECT count(*) FROM testjsonb WHERE j @> '{}';
+SELECT count(*) FROM testjsonb WHERE j ? 'public';
+SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+
+-- array exists - array elements should behave as keys (for GIN index scans too)
+CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
+SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
+-- type sensitive array exists - should return no rows (since "exists" only
+-- matches strings that are either object keys or array elements)
+SELECT count(*) from testjsonb  WHERE j->'array' ? '5'::text;
+-- However, a raw scalar is *contained* within the array
+SELECT count(*) from testjsonb  WHERE j->'array' @> '5'::jsonb;
+
+RESET enable_seqscan;
+
+SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow;
+SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key;
+
+-- sort/hash
+SELECT count(distinct j) FROM testjsonb;
+SET enable_hashagg = off;
+SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2;
+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);
+SET enable_sort = on;
+
+RESET enable_hashagg;
+RESET enable_sort;
+
+DROP INDEX jidx;
+DROP INDEX jidx_array;
+-- btree
+CREATE INDEX jidx ON testjsonb USING btree (j);
+SET enable_seqscan = off;
+
+SELECT count(*) FROM testjsonb WHERE j > '{"p":1}';
+SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}';
+
+--gin hash
+DROP INDEX jidx;
+CREATE INDEX jidx ON testjsonb USING gin (j jsonb_hash_ops);
+SET enable_seqscan = off;
+
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}';
+SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
+-- excercise GIN_SEARCH_MODE_ALL
+SELECT count(*) FROM testjsonb WHERE j @> '{}';
+
+RESET enable_seqscan;
+DROP INDEX jidx;
+
+-- nested tests
+SELECT '{"ff":{"a":12,"b":16}}'::jsonb;
+SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb;
+SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb;
+SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb;
+SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb;
+SELECT '{"ff":["a","aaa"]}'::jsonb;
+
+SELECT
+  '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff',
+  '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq',
+  ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f,
+  ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t,
+   '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x';
+
+-- nested containment
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}';
+SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}';
+SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}';
+SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}';
+SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}';
+SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}';
+SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]';
+SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]';
+SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]';
+SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]';
+
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}';
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}';
+SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}';
+
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}';
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}';
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}';
+SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}';
+
+-- nested object field / array index lookup
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error
+
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 0;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 1;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 2;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 3;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 4;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> 5;
+SELECT '["a","b","c",[1,2],null]'::jsonb -> -1;
+
+--nested path extraction
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}';
+SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}';
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}';
+
+--nested exists
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd';
+SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e';