diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile
index 43b7e5f..2b60fbe 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 2114143..9749e45 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 0000000..0a70560
--- /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.2.sql
deleted file mode 100644
index f415a72..0000000
--- a/contrib/hstore/hstore--1.2.sql
+++ /dev/null
@@ -1,537 +0,0 @@
-/* contrib/hstore/hstore--1.1.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION hstore" to load this file. \quit
-
-CREATE TYPE hstore;
-
-CREATE FUNCTION hstore_in(cstring)
-RETURNS hstore
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_out(hstore)
-RETURNS cstring
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_recv(internal)
-RETURNS hstore
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_send(hstore)
-RETURNS bytea
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE TYPE hstore (
-        INTERNALLENGTH = -1,
-        INPUT = hstore_in,
-        OUTPUT = hstore_out,
-        RECEIVE = hstore_recv,
-        SEND = hstore_send,
-        STORAGE = extended
-);
-
-CREATE FUNCTION hstore_version_diag(hstore)
-RETURNS integer
-AS 'MODULE_PATHNAME','hstore_version_diag'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION fetchval(hstore,text)
-RETURNS text
-AS 'MODULE_PATHNAME','hstore_fetchval'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR -> (
-	LEFTARG = hstore,
-	RIGHTARG = text,
-	PROCEDURE = fetchval
-);
-
-CREATE FUNCTION slice_array(hstore,text[])
-RETURNS text[]
-AS 'MODULE_PATHNAME','hstore_slice_to_array'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR -> (
-	LEFTARG = hstore,
-	RIGHTARG = text[],
-	PROCEDURE = slice_array
-);
-
-CREATE FUNCTION slice(hstore,text[])
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_slice_to_hstore'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION isexists(hstore,text)
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_exists'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION exist(hstore,text)
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_exists'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR ? (
-	LEFTARG = hstore,
-	RIGHTARG = text,
-	PROCEDURE = exist,
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
-CREATE FUNCTION exists_any(hstore,text[])
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_exists_any'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR ?| (
-	LEFTARG = hstore,
-	RIGHTARG = text[],
-	PROCEDURE = exists_any,
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
-CREATE FUNCTION exists_all(hstore,text[])
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_exists_all'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR ?& (
-	LEFTARG = hstore,
-	RIGHTARG = text[],
-	PROCEDURE = exists_all,
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
-CREATE FUNCTION isdefined(hstore,text)
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_defined'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION defined(hstore,text)
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_defined'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION delete(hstore,text)
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_delete'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION delete(hstore,text[])
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_delete_array'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION delete(hstore,hstore)
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_delete_hstore'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR - (
-	LEFTARG = hstore,
-	RIGHTARG = text,
-	PROCEDURE = delete
-);
-
-CREATE OPERATOR - (
-	LEFTARG = hstore,
-	RIGHTARG = text[],
-	PROCEDURE = delete
-);
-
-CREATE OPERATOR - (
-	LEFTARG = hstore,
-	RIGHTARG = hstore,
-	PROCEDURE = delete
-);
-
-CREATE FUNCTION hs_concat(hstore,hstore)
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_concat'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR || (
-	LEFTARG = hstore,
-	RIGHTARG = hstore,
-	PROCEDURE = hs_concat
-);
-
-CREATE FUNCTION hs_contains(hstore,hstore)
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_contains'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hs_contained(hstore,hstore)
-RETURNS bool
-AS 'MODULE_PATHNAME','hstore_contained'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR @> (
-	LEFTARG = hstore,
-	RIGHTARG = hstore,
-	PROCEDURE = hs_contains,
-	COMMUTATOR = '<@',
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
-CREATE OPERATOR <@ (
-	LEFTARG = hstore,
-	RIGHTARG = hstore,
-	PROCEDURE = hs_contained,
-	COMMUTATOR = '@>',
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
--- obsolete:
-CREATE OPERATOR @ (
-	LEFTARG = hstore,
-	RIGHTARG = hstore,
-	PROCEDURE = hs_contains,
-	COMMUTATOR = '~',
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
-CREATE OPERATOR ~ (
-	LEFTARG = hstore,
-	RIGHTARG = hstore,
-	PROCEDURE = hs_contained,
-	COMMUTATOR = '@',
-	RESTRICT = contsel,
-	JOIN = contjoinsel
-);
-
-CREATE FUNCTION tconvert(text,text)
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_from_text'
-LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
-
-CREATE FUNCTION hstore(text,text)
-RETURNS hstore
-AS 'MODULE_PATHNAME','hstore_from_text'
-LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
-
-CREATE FUNCTION hstore(text[],text[])
-RETURNS hstore
-AS 'MODULE_PATHNAME', 'hstore_from_arrays'
-LANGUAGE C IMMUTABLE; -- not STRICT; allows (keys,null)
-
-CREATE FUNCTION hstore(text[])
-RETURNS hstore
-AS 'MODULE_PATHNAME', 'hstore_from_array'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE CAST (text[] AS hstore)
-  WITH FUNCTION hstore(text[]);
-
-CREATE FUNCTION hstore_to_json(hstore)
-RETURNS json
-AS 'MODULE_PATHNAME', 'hstore_to_json'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE CAST (hstore AS json)
-  WITH FUNCTION hstore_to_json(hstore);
-
-CREATE FUNCTION hstore_to_json_loose(hstore)
-RETURNS json
-AS 'MODULE_PATHNAME', 'hstore_to_json_loose'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION hstore(record)
-RETURNS hstore
-AS 'MODULE_PATHNAME', 'hstore_from_record'
-LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::recordtype)
-
-CREATE FUNCTION hstore_to_array(hstore)
-RETURNS text[]
-AS 'MODULE_PATHNAME','hstore_to_array'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR %% (
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_to_array
-);
-
-CREATE FUNCTION hstore_to_matrix(hstore)
-RETURNS text[]
-AS 'MODULE_PATHNAME','hstore_to_matrix'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR %# (
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_to_matrix
-);
-
-CREATE FUNCTION akeys(hstore)
-RETURNS text[]
-AS 'MODULE_PATHNAME','hstore_akeys'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION avals(hstore)
-RETURNS text[]
-AS 'MODULE_PATHNAME','hstore_avals'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION skeys(hstore)
-RETURNS setof text
-AS 'MODULE_PATHNAME','hstore_skeys'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION svals(hstore)
-RETURNS setof text
-AS 'MODULE_PATHNAME','hstore_svals'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION each(IN hs hstore,
-    OUT key text,
-    OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME','hstore_each'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION populate_record(anyelement,hstore)
-RETURNS anyelement
-AS 'MODULE_PATHNAME', 'hstore_populate_record'
-LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::rectype,hstore)
-
-CREATE OPERATOR #= (
-	LEFTARG = anyelement,
-	RIGHTARG = hstore,
-	PROCEDURE = populate_record
-);
-
--- btree support
-
-CREATE FUNCTION hstore_eq(hstore,hstore)
-RETURNS boolean
-AS 'MODULE_PATHNAME','hstore_eq'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_ne(hstore,hstore)
-RETURNS boolean
-AS 'MODULE_PATHNAME','hstore_ne'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_gt(hstore,hstore)
-RETURNS boolean
-AS 'MODULE_PATHNAME','hstore_gt'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_ge(hstore,hstore)
-RETURNS boolean
-AS 'MODULE_PATHNAME','hstore_ge'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_lt(hstore,hstore)
-RETURNS boolean
-AS 'MODULE_PATHNAME','hstore_lt'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_le(hstore,hstore)
-RETURNS boolean
-AS 'MODULE_PATHNAME','hstore_le'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION hstore_cmp(hstore,hstore)
-RETURNS integer
-AS 'MODULE_PATHNAME','hstore_cmp'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR = (
-       LEFTARG = hstore,
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_eq,
-       COMMUTATOR = =,
-       NEGATOR = <>,
-       RESTRICT = eqsel,
-       JOIN = eqjoinsel,
-       MERGES,
-       HASHES
-);
-CREATE OPERATOR <> (
-       LEFTARG = hstore,
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_ne,
-       COMMUTATOR = <>,
-       NEGATOR = =,
-       RESTRICT = neqsel,
-       JOIN = neqjoinsel
-);
-
--- the comparison operators have funky names (and are undocumented)
--- in an attempt to discourage anyone from actually using them. they
--- only exist to support the btree opclass
-
-CREATE OPERATOR #<# (
-       LEFTARG = hstore,
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_lt,
-       COMMUTATOR = #>#,
-       NEGATOR = #>=#,
-       RESTRICT = scalarltsel,
-       JOIN = scalarltjoinsel
-);
-CREATE OPERATOR #<=# (
-       LEFTARG = hstore,
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_le,
-       COMMUTATOR = #>=#,
-       NEGATOR = #>#,
-       RESTRICT = scalarltsel,
-       JOIN = scalarltjoinsel
-);
-CREATE OPERATOR #># (
-       LEFTARG = hstore,
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_gt,
-       COMMUTATOR = #<#,
-       NEGATOR = #<=#,
-       RESTRICT = scalargtsel,
-       JOIN = scalargtjoinsel
-);
-CREATE OPERATOR #>=# (
-       LEFTARG = hstore,
-       RIGHTARG = hstore,
-       PROCEDURE = hstore_ge,
-       COMMUTATOR = #<=#,
-       NEGATOR = #<#,
-       RESTRICT = scalargtsel,
-       JOIN = scalargtjoinsel
-);
-
-CREATE OPERATOR CLASS btree_hstore_ops
-DEFAULT FOR TYPE hstore USING btree
-AS
-	OPERATOR	1	#<# ,
-	OPERATOR	2	#<=# ,
-	OPERATOR	3	= ,
-	OPERATOR	4	#>=# ,
-	OPERATOR	5	#># ,
-	FUNCTION	1	hstore_cmp(hstore,hstore);
-
--- hash support
-
-CREATE FUNCTION hstore_hash(hstore)
-RETURNS integer
-AS 'MODULE_PATHNAME','hstore_hash'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE OPERATOR CLASS hash_hstore_ops
-DEFAULT FOR TYPE hstore USING hash
-AS
-	OPERATOR	1	= ,
-	FUNCTION	1	hstore_hash(hstore);
-
--- GiST support
-
-CREATE TYPE ghstore;
-
-CREATE FUNCTION ghstore_in(cstring)
-RETURNS ghstore
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE FUNCTION ghstore_out(ghstore)
-RETURNS cstring
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT IMMUTABLE;
-
-CREATE TYPE ghstore (
-        INTERNALLENGTH = -1,
-        INPUT = ghstore_in,
-        OUTPUT = ghstore_out
-);
-
-CREATE FUNCTION ghstore_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION ghstore_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION ghstore_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION ghstore_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION ghstore_union(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION ghstore_same(internal, internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION ghstore_consistent(internal,internal,int,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR CLASS gist_hstore_ops
-DEFAULT FOR TYPE hstore USING gist
-AS
-	OPERATOR        7       @> ,
-	OPERATOR        9       ?(hstore,text) ,
-	OPERATOR        10      ?|(hstore,text[]) ,
-	OPERATOR        11      ?&(hstore,text[]) ,
-        --OPERATOR        8       <@ ,
-        OPERATOR        13      @ ,
-        --OPERATOR        14      ~ ,
-        FUNCTION        1       ghstore_consistent (internal, internal, int, oid, internal),
-        FUNCTION        2       ghstore_union (internal, internal),
-        FUNCTION        3       ghstore_compress (internal),
-        FUNCTION        4       ghstore_decompress (internal),
-        FUNCTION        5       ghstore_penalty (internal, internal, internal),
-        FUNCTION        6       ghstore_picksplit (internal, internal),
-        FUNCTION        7       ghstore_same (internal, internal, internal),
-        STORAGE         ghstore;
-
--- GIN support
-
-CREATE FUNCTION gin_extract_hstore(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gin_extract_hstore_query(internal, internal, int2, internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gin_consistent_hstore(internal, int2, internal, int4, internal, internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR CLASS gin_hstore_ops
-DEFAULT FOR TYPE hstore USING gin
-AS
-	OPERATOR        7       @>,
-	OPERATOR        9       ?(hstore,text),
-	OPERATOR        10      ?|(hstore,text[]),
-	OPERATOR        11      ?&(hstore,text[]),
-	FUNCTION        1       bttextcmp(text,text),
-	FUNCTION        2       gin_extract_hstore(internal, internal),
-	FUNCTION        3       gin_extract_hstore_query(internal, internal, int2, internal, internal),
-	FUNCTION        4       gin_consistent_hstore(internal, int2, internal, int4, internal, internal),
-	STORAGE         text;
diff --git a/contrib/hstore/hstore--1.3.sql b/contrib/hstore/hstore--1.3.sql
new file mode 100644
index 0000000..995ade1
--- /dev/null
+++ b/contrib/hstore/hstore--1.3.sql
@@ -0,0 +1,550 @@
+/* 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
+
+CREATE TYPE hstore;
+
+CREATE FUNCTION hstore_in(cstring)
+RETURNS hstore
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_out(hstore)
+RETURNS cstring
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_recv(internal)
+RETURNS hstore
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_send(hstore)
+RETURNS bytea
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE TYPE hstore (
+        INTERNALLENGTH = -1,
+        INPUT = hstore_in,
+        OUTPUT = hstore_out,
+        RECEIVE = hstore_recv,
+        SEND = hstore_send,
+        STORAGE = extended
+);
+
+CREATE FUNCTION hstore_version_diag(hstore)
+RETURNS integer
+AS 'MODULE_PATHNAME','hstore_version_diag'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION fetchval(hstore,text)
+RETURNS text
+AS 'MODULE_PATHNAME','hstore_fetchval'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR -> (
+	LEFTARG = hstore,
+	RIGHTARG = text,
+	PROCEDURE = fetchval
+);
+
+CREATE FUNCTION slice_array(hstore,text[])
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_slice_to_array'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR -> (
+	LEFTARG = hstore,
+	RIGHTARG = text[],
+	PROCEDURE = slice_array
+);
+
+CREATE FUNCTION slice(hstore,text[])
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_slice_to_hstore'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION isexists(hstore,text)
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_exists'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION exist(hstore,text)
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_exists'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR ? (
+	LEFTARG = hstore,
+	RIGHTARG = text,
+	PROCEDURE = exist,
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+CREATE FUNCTION exists_any(hstore,text[])
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_exists_any'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR ?| (
+	LEFTARG = hstore,
+	RIGHTARG = text[],
+	PROCEDURE = exists_any,
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+CREATE FUNCTION exists_all(hstore,text[])
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_exists_all'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR ?& (
+	LEFTARG = hstore,
+	RIGHTARG = text[],
+	PROCEDURE = exists_all,
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+CREATE FUNCTION isdefined(hstore,text)
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_defined'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION defined(hstore,text)
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_defined'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION delete(hstore,text)
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_delete'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION delete(hstore,text[])
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_delete_array'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION delete(hstore,hstore)
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_delete_hstore'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR - (
+	LEFTARG = hstore,
+	RIGHTARG = text,
+	PROCEDURE = delete
+);
+
+CREATE OPERATOR - (
+	LEFTARG = hstore,
+	RIGHTARG = text[],
+	PROCEDURE = delete
+);
+
+CREATE OPERATOR - (
+	LEFTARG = hstore,
+	RIGHTARG = hstore,
+	PROCEDURE = delete
+);
+
+CREATE FUNCTION hs_concat(hstore,hstore)
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_concat'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR || (
+	LEFTARG = hstore,
+	RIGHTARG = hstore,
+	PROCEDURE = hs_concat
+);
+
+CREATE FUNCTION hs_contains(hstore,hstore)
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_contains'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hs_contained(hstore,hstore)
+RETURNS bool
+AS 'MODULE_PATHNAME','hstore_contained'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR @> (
+	LEFTARG = hstore,
+	RIGHTARG = hstore,
+	PROCEDURE = hs_contains,
+	COMMUTATOR = '<@',
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+CREATE OPERATOR <@ (
+	LEFTARG = hstore,
+	RIGHTARG = hstore,
+	PROCEDURE = hs_contained,
+	COMMUTATOR = '@>',
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+-- obsolete:
+CREATE OPERATOR @ (
+	LEFTARG = hstore,
+	RIGHTARG = hstore,
+	PROCEDURE = hs_contains,
+	COMMUTATOR = '~',
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+CREATE OPERATOR ~ (
+	LEFTARG = hstore,
+	RIGHTARG = hstore,
+	PROCEDURE = hs_contained,
+	COMMUTATOR = '@',
+	RESTRICT = contsel,
+	JOIN = contjoinsel
+);
+
+CREATE FUNCTION tconvert(text,text)
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_from_text'
+LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
+
+CREATE FUNCTION hstore(text,text)
+RETURNS hstore
+AS 'MODULE_PATHNAME','hstore_from_text'
+LANGUAGE C IMMUTABLE; -- not STRICT; needs to allow (key,NULL)
+
+CREATE FUNCTION hstore(text[],text[])
+RETURNS hstore
+AS 'MODULE_PATHNAME', 'hstore_from_arrays'
+LANGUAGE C IMMUTABLE; -- not STRICT; allows (keys,null)
+
+CREATE FUNCTION hstore(text[])
+RETURNS hstore
+AS 'MODULE_PATHNAME', 'hstore_from_array'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE CAST (text[] AS hstore)
+  WITH FUNCTION hstore(text[]);
+
+CREATE FUNCTION hstore_to_json(hstore)
+RETURNS json
+AS 'MODULE_PATHNAME', 'hstore_to_json'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE CAST (hstore AS json)
+  WITH FUNCTION hstore_to_json(hstore);
+
+CREATE FUNCTION hstore_to_json_loose(hstore)
+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'
+LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::recordtype)
+
+CREATE FUNCTION hstore_to_array(hstore)
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_to_array'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR %% (
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_to_array
+);
+
+CREATE FUNCTION hstore_to_matrix(hstore)
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_to_matrix'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR %# (
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_to_matrix
+);
+
+CREATE FUNCTION akeys(hstore)
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_akeys'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION avals(hstore)
+RETURNS text[]
+AS 'MODULE_PATHNAME','hstore_avals'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION skeys(hstore)
+RETURNS setof text
+AS 'MODULE_PATHNAME','hstore_skeys'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION svals(hstore)
+RETURNS setof text
+AS 'MODULE_PATHNAME','hstore_svals'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION each(IN hs hstore,
+    OUT key text,
+    OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME','hstore_each'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION populate_record(anyelement,hstore)
+RETURNS anyelement
+AS 'MODULE_PATHNAME', 'hstore_populate_record'
+LANGUAGE C IMMUTABLE; -- not STRICT; allows (null::rectype,hstore)
+
+CREATE OPERATOR #= (
+	LEFTARG = anyelement,
+	RIGHTARG = hstore,
+	PROCEDURE = populate_record
+);
+
+-- btree support
+
+CREATE FUNCTION hstore_eq(hstore,hstore)
+RETURNS boolean
+AS 'MODULE_PATHNAME','hstore_eq'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_ne(hstore,hstore)
+RETURNS boolean
+AS 'MODULE_PATHNAME','hstore_ne'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_gt(hstore,hstore)
+RETURNS boolean
+AS 'MODULE_PATHNAME','hstore_gt'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_ge(hstore,hstore)
+RETURNS boolean
+AS 'MODULE_PATHNAME','hstore_ge'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_lt(hstore,hstore)
+RETURNS boolean
+AS 'MODULE_PATHNAME','hstore_lt'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_le(hstore,hstore)
+RETURNS boolean
+AS 'MODULE_PATHNAME','hstore_le'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION hstore_cmp(hstore,hstore)
+RETURNS integer
+AS 'MODULE_PATHNAME','hstore_cmp'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR = (
+       LEFTARG = hstore,
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_eq,
+       COMMUTATOR = =,
+       NEGATOR = <>,
+       RESTRICT = eqsel,
+       JOIN = eqjoinsel,
+       MERGES,
+       HASHES
+);
+CREATE OPERATOR <> (
+       LEFTARG = hstore,
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_ne,
+       COMMUTATOR = <>,
+       NEGATOR = =,
+       RESTRICT = neqsel,
+       JOIN = neqjoinsel
+);
+
+-- the comparison operators have funky names (and are undocumented)
+-- in an attempt to discourage anyone from actually using them. they
+-- only exist to support the btree opclass
+
+CREATE OPERATOR #<# (
+       LEFTARG = hstore,
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_lt,
+       COMMUTATOR = #>#,
+       NEGATOR = #>=#,
+       RESTRICT = scalarltsel,
+       JOIN = scalarltjoinsel
+);
+CREATE OPERATOR #<=# (
+       LEFTARG = hstore,
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_le,
+       COMMUTATOR = #>=#,
+       NEGATOR = #>#,
+       RESTRICT = scalarltsel,
+       JOIN = scalarltjoinsel
+);
+CREATE OPERATOR #># (
+       LEFTARG = hstore,
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_gt,
+       COMMUTATOR = #<#,
+       NEGATOR = #<=#,
+       RESTRICT = scalargtsel,
+       JOIN = scalargtjoinsel
+);
+CREATE OPERATOR #>=# (
+       LEFTARG = hstore,
+       RIGHTARG = hstore,
+       PROCEDURE = hstore_ge,
+       COMMUTATOR = #<=#,
+       NEGATOR = #<#,
+       RESTRICT = scalargtsel,
+       JOIN = scalargtjoinsel
+);
+
+CREATE OPERATOR CLASS btree_hstore_ops
+DEFAULT FOR TYPE hstore USING btree
+AS
+	OPERATOR	1	#<# ,
+	OPERATOR	2	#<=# ,
+	OPERATOR	3	= ,
+	OPERATOR	4	#>=# ,
+	OPERATOR	5	#># ,
+	FUNCTION	1	hstore_cmp(hstore,hstore);
+
+-- hash support
+
+CREATE FUNCTION hstore_hash(hstore)
+RETURNS integer
+AS 'MODULE_PATHNAME','hstore_hash'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OPERATOR CLASS hash_hstore_ops
+DEFAULT FOR TYPE hstore USING hash
+AS
+	OPERATOR	1	= ,
+	FUNCTION	1	hstore_hash(hstore);
+
+-- GiST support
+
+CREATE TYPE ghstore;
+
+CREATE FUNCTION ghstore_in(cstring)
+RETURNS ghstore
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION ghstore_out(ghstore)
+RETURNS cstring
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE TYPE ghstore (
+        INTERNALLENGTH = -1,
+        INPUT = ghstore_in,
+        OUTPUT = ghstore_out
+);
+
+CREATE FUNCTION ghstore_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION ghstore_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION ghstore_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION ghstore_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION ghstore_union(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION ghstore_same(internal, internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION ghstore_consistent(internal,internal,int,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE OPERATOR CLASS gist_hstore_ops
+DEFAULT FOR TYPE hstore USING gist
+AS
+	OPERATOR        7       @> ,
+	OPERATOR        9       ?(hstore,text) ,
+	OPERATOR        10      ?|(hstore,text[]) ,
+	OPERATOR        11      ?&(hstore,text[]) ,
+        --OPERATOR        8       <@ ,
+        OPERATOR        13      @ ,
+        --OPERATOR        14      ~ ,
+        FUNCTION        1       ghstore_consistent (internal, internal, int, oid, internal),
+        FUNCTION        2       ghstore_union (internal, internal),
+        FUNCTION        3       ghstore_compress (internal),
+        FUNCTION        4       ghstore_decompress (internal),
+        FUNCTION        5       ghstore_penalty (internal, internal, internal),
+        FUNCTION        6       ghstore_picksplit (internal, internal),
+        FUNCTION        7       ghstore_same (internal, internal, internal),
+        STORAGE         ghstore;
+
+-- GIN support
+
+CREATE FUNCTION gin_extract_hstore(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gin_extract_hstore_query(internal, internal, int2, internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gin_consistent_hstore(internal, int2, internal, int4, internal, internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE OPERATOR CLASS gin_hstore_ops
+DEFAULT FOR TYPE hstore USING gin
+AS
+	OPERATOR        7       @>,
+	OPERATOR        9       ?(hstore,text),
+	OPERATOR        10      ?|(hstore,text[]),
+	OPERATOR        11      ?&(hstore,text[]),
+	FUNCTION        1       bttextcmp(text,text),
+	FUNCTION        2       gin_extract_hstore(internal, internal),
+	FUNCTION        3       gin_extract_hstore_query(internal, internal, int2, internal, internal),
+	FUNCTION        4       gin_consistent_hstore(internal, int2, internal, int4, internal, internal),
+	STORAGE         text;
diff --git a/contrib/hstore/hstore.control b/contrib/hstore/hstore.control
index 9daf5e2..dcc3b68 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 65e4438..0035a26 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);
+	ToJsonbState *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);
+	ToJsonbState *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 9518f56..5a9e9ee 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 ac285ce..cc458b4 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 0e863ee..ecd8060 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 080da43..1e25040 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,105 @@ 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.  <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 +10248,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 +10261,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 +10273,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 +10288,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 +10470,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 +10490,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 +10509,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 +10521,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 +10532,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 +10550,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 +10571,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 +10594,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 +10615,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 +10633,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 +10648,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 +10669,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 0000000..0c53ee4
--- /dev/null
+++ b/doc/src/sgml/json.sgml
@@ -0,0 +1,326 @@
+<!-- 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.  The danger of losing
+    numeric precision in respect of data originating in
+    <productname>PostgreSQL</productname> is such a system 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 documents 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-indexing">
+  <title><type>jsonb</> GIN Indexing</title>
+  <indexterm>
+    <primary>jsonb</primary>
+    <secondary>indexes on</secondary>
+  </indexterm>
+  <para>
+    The <type>jsonb</>-only indexable operators do not permit
+    searching for keys or elements that are not at the least-nested
+    level (except in the limited sense that JSON object key/value
+    <emphasis>pairs</emphasis> can be indexed at that same nesting
+    level).  However, they can be used to efficiently search among
+    more than one possible key, or more than one possible key/value
+    pair within a single <type>jsonb</> datum/document, among a large
+    number of such <quote>documents</> within a column in a table
+    (i.e. among many rows).
+  </para>
+  <para>
+    The nesting restriction is far less onerous than it might first
+    appear;  expressional indexes can be used to support indexing of
+    nested elements.
+  </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,
+    "gender": "female",
+    "company": "Magnafone",
+    "address": "178 Howard Place, Gulf, Washington, 702",
+    "registered": "2009-11-07T08:53:22 +08:00",
+    "latitude": 19.793713,
+    "longitude": 86.513373,
+    "tags": [
+        "aliquip",
+        "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 @> '{"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, and as a consequence 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>
+    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>
+  <para>
+   For full details of the semantics that these indexable operators
+   implement, see <xref linkend="functions-jsonb-op-table">.
+  </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> > <literal>Null</literal>
+
+      <replaceable>Object with n keys</replaceable> > <replaceable>object with n - 1 keys</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 keys are compared:
+    <synopsis>
+      <replaceable>key-1</replaceable>, <replaceable>value-1</replaceable>, <replaceable>key-2</replaceable> ...
+    </synopsis>
+      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 053d758..6620402 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 6446879..6b23069 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 f170661..5d44f52 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 0000000..fbe9493
--- /dev/null
+++ b/src/backend/utils/adt/jsonb.c
@@ -0,0 +1,467 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb.c
+ *		I/O routines for jsonb type
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ * src/backend/utils/adt/jsonb_support.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
+{
+	ToJsonbState *state;
+	JsonbValue *res;
+}	JsonbInState;
+
+static inline Datum cstring_with_len_to_jsonb(char *json, int len);
+static size_t checkStringLen(size_t len);
+static void jsonb_in_object_start(void *state);
+static void jsonb_in_object_end(void *state);
+static void jsonb_in_array_start(void *state);
+static void jsonb_in_array_end(void *state);
+static void jsonb_in_object_field_start(void *state, char *fname, bool isnull);
+static void jsonb_put_escaped_value(StringInfo out, JsonbValue * v);
+static void jsonb_in_scalar(void *state, 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 cstring_with_len_to_jsonb(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 cstring_with_len_to_jsonb(str, nbytes);
+}
+
+/*
+ * jsonb type output function
+ */
+Datum
+jsonb_out(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB(0);
+	char	   *out;
+
+	out = JsonbToCString(NULL, (JB_ISEMPTY(jb)) ? NULL : VARDATA(jb), VARSIZE(jb));
+
+	PG_RETURN_CSTRING(out);
+}
+
+/*
+ * jsonb type send function
+ *
+ * Just send jsonb as 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, (JB_ISEMPTY(jb)) ? NULL : 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);
+		(void) JsonbIteratorNext(&it, &v, true);
+		switch (v.type)
+		{
+			case jbvNull:
+				result = "null";
+				break;
+			case jbvString:
+				result = "string";
+				break;
+			case jbvBool:
+				result = "boolean";
+				break;
+			case jbvNumeric:
+				result = "number";
+				break;
+			default:
+				elog(ERROR, "unknown jsonb scalar type");
+		}
+	}
+
+	PG_RETURN_TEXT_P(cstring_to_text(result));
+}
+
+/*
+ * cstring_with_len_to_jsonb
+ *
+ * Turn json text into a jsonb Datum.
+ *
+ * Uses the json parser with hooks to contruct a jsonb.
+ */
+static inline Datum
+cstring_with_len_to_jsonb(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 *state)
+{
+	JsonbInState *_state = (JsonbInState *) state;
+
+	_state->res = pushJsonbValue(&_state->state, WJB_BEGIN_OBJECT, NULL);
+}
+
+static void
+jsonb_in_object_end(void *state)
+{
+	JsonbInState *_state = (JsonbInState *) state;
+
+	_state->res = pushJsonbValue(&_state->state, WJB_END_OBJECT, NULL);
+}
+
+static void
+jsonb_in_array_start(void *state)
+{
+	JsonbInState *_state = (JsonbInState *) state;
+
+	_state->res = pushJsonbValue(&_state->state, WJB_BEGIN_ARRAY, NULL);
+}
+
+static void
+jsonb_in_array_end(void *state)
+{
+	JsonbInState *_state = (JsonbInState *) state;
+
+	_state->res = pushJsonbValue(&_state->state, WJB_END_ARRAY, NULL);
+}
+
+static void
+jsonb_in_object_field_start(void *state, char *fname, bool isnull)
+{
+	JsonbInState *_state = (JsonbInState *) state;
+	JsonbValue	v;
+
+	v.type = jbvString;
+	v.string.len = fname ? checkStringLen(strlen(fname)) : 0;
+	v.string.val = fname ? pnstrdup(fname, v.string.len) : NULL;
+	v.estSize = sizeof(JEntry) + v.string.len;
+
+	_state->res = pushJsonbValue(&_state->state, WJB_KEY, &v);
+}
+
+static void
+jsonb_put_escaped_value(StringInfo out, JsonbValue * v)
+{
+	switch (v->type)
+	{
+		case jbvNull:
+			appendBinaryStringInfo(out, "null", 4);
+			break;
+		case jbvString:
+			escape_json(out, pnstrdup(v->string.val, v->string.len));
+			break;
+		case jbvBool:
+			if (v->boolean)
+				appendBinaryStringInfo(out, "true", 4);
+			else
+				appendBinaryStringInfo(out, "false", 5);
+			break;
+		case jbvNumeric:
+			appendStringInfoString(out,
+								   DatumGetCString(DirectFunctionCall1(numeric_out,
+																	   PointerGetDatum(v->numeric))));
+			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 *state, char *token, JsonTokenType tokentype)
+{
+	JsonbInState *_state = (JsonbInState *) state;
+	JsonbValue	v;
+
+	v.estSize = sizeof(JEntry);
+
+	switch (tokentype)
+	{
+
+		case JSON_TOKEN_STRING:
+			v.type = jbvString;
+			v.string.len = token ? checkStringLen(strlen(token)) : 0;
+			v.string.val = token ? pnstrdup(token, v.string.len) : NULL;
+			v.estSize += v.string.len;
+			break;
+		case JSON_TOKEN_NUMBER:
+			/*
+			 * No need to check size of numeric values, because maximum numeric
+			 * size is well in excess of the restriction we separately impose
+			 * of the size of JsonbValues
+			 */
+			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:
+			elog(ERROR, "invalid json token type");
+			break;
+	}
+
+	if (_state->state == NULL)
+	{
+		/* single scalar */
+		JsonbValue	va;
+
+		va.type = jbvArray;
+		va.array.scalar = true;
+		va.array.nElems = 1;
+
+		_state->res = pushJsonbValue(&_state->state, WJB_BEGIN_ARRAY, &va);
+		_state->res = pushJsonbValue(&_state->state, WJB_ELEM, &v);
+		_state->res = pushJsonbValue(&_state->state, WJB_END_ARRAY, NULL);
+	}
+	else
+	{
+		JsonbValue *o = &_state->state->v;
+
+		switch (o->type)
+		{
+			case jbvArray:
+				_state->res = pushJsonbValue(&_state->state, WJB_ELEM, &v);
+				break;
+			case jbvObject:
+				_state->res = pushJsonbValue(&_state->state, WJB_VALUE, &v);
+				break;
+			default:
+				elog(ERROR, "unexpected parent of nested structure");
+		}
+	}
+}
+
+/*
+ * JsonbToCString
+ *	   Converts jsonb value in C-string. If out argument is not null
+ * then resulting C-string is placed in it. Return pointer to string.
+ * 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, char *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();
+
+	if (in == NULL)
+	{
+		appendStringInfoString(out, "");
+		return out->data;
+	}
+
+	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.scalar)
+					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.scalar)
+					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 0000000..77f7b77
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_gin.c
@@ -0,0 +1,473 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_state;
+	struct PathHashStack *next;
+}	PathHashStack;
+
+static text *make_text_key(const char *str, int len, char flag);
+static text *make_scalar_text_key(const JsonbValue * v, 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 as one.  Array elements are indexed as
+		 * keys, for the benefit of JsonbContainsStrategyNumber (i.e. so that
+		 * the structure of the index comports with the general Jsonb notion of
+		 * containment).
+		 *
+		 * See remarks above findJsonbValueFromSuperHeader() for information on
+		 * our definition of containment as it relates to elements and
+		 * key/value pairs.  Note also that the recheck flag is set for
+		 * JsonbContainsStrategyNumber.
+		 */
+		switch (r)
+		{
+			case WJB_KEY:
+			case WJB_ELEM:
+				entries[i++] = PointerGetDatum(make_scalar_text_key(&v, KEYELEMFLAG));
+				break;
+			case WJB_VALUE:
+				entries[i++] = PointerGetDatum(make_scalar_text_key(&v, VALFLAG));
+				break;
+			default:
+				break;
+		}
+	}
+
+	*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),
+							 KEYELEMFLAG);
+		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,
+								 KEYELEMFLAG);
+			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 both are stored as), so
+		 * invariably we recheck.  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);
+}
+
+/*
+ *
+ * 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)
+	{
+		/*
+		 * jsonb_hash_ops index doesn't have information about correspondence
+		 * of Jsonb keys and values (as distinct from GIN keys, which both are
+		 * stored as), so invariably we recheck.  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
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+gin_extract_jsonb_hash(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = 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;
+	PathHashStack tail;
+	PathHashStack *stack;
+
+	if (total == 0)
+	{
+		*nentries = 0;
+		PG_RETURN_POINTER(NULL);
+	}
+
+	entries = (Datum *) palloc(sizeof(Datum) * total);
+
+	it = JsonbIteratorInit(VARDATA(jb));
+
+	tail.next = NULL;
+	tail.hash_state = 0;
+	stack = &tail;
+
+	while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+	{
+		uint32			temphash;
+		PathHashStack  *tmp;
+
+		if (i >= total)
+		{
+			total *= 2;
+			entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
+		}
+
+		/*
+		 * Keys and values hashed as one.
+		 *
+		 * No particular effort is made to mix in the structure of the Jsonb
+		 * itself (number of keys, values and elements notwithstanding), on the
+		 * assumption that it's very homogeneous among Json datums in the same
+		 * column.
+		 */
+		switch (r)
+		{
+			case WJB_BEGIN_ARRAY:
+			case WJB_BEGIN_OBJECT:
+				/* Preserve stack item for key */
+				tmp = stack;
+				stack = (PathHashStack *) palloc(sizeof(PathHashStack));
+				stack->next = tmp;
+				break;
+			case WJB_KEY:
+				/* Calc hash of key and separated into preserved stack item */
+				stack->hash_state = stack->next->hash_state;
+				hash_scalar_value(&v, &stack->hash_state);
+				break;
+			case WJB_VALUE:
+			case WJB_ELEM:
+				stack->hash_state = stack->next->hash_state;
+				hash_scalar_value(&v, &stack->hash_state);
+				temphash = stack->hash_state;
+				entries[i++] = temphash;
+				break;
+			case WJB_END_ARRAY:
+			case WJB_END_OBJECT:
+				/* Pop the stack */
+				tmp = stack->next;
+				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)
+	{
+		/* 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;
+	}
+	else
+	{
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+		entries = NULL;			/* keep compiler quiet */
+	}
+
+	PG_RETURN_POINTER(entries);
+}
+
+/*
+ * Build an indexable text value from a cstring and flag
+ */
+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;
+
+	if (str && len > 0)
+		memcpy(VARDATA(item) + 1, str, len);
+
+	return item;
+}
+
+/*
+ * Create a textual representation of a jsonbValue for GIN storage.
+ */
+static text *
+make_scalar_text_key(const JsonbValue * v, char flag)
+{
+	text	   *item;
+	char	   *cstr;
+
+	switch (v->type)
+	{
+		case jbvNull:
+			item = make_text_key(NULL, 0, NULLFLAG);
+			break;
+		case jbvBool:
+			item = make_text_key(v->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(v->numeric);
+			item = make_text_key(cstr, strlen(cstr), flag);
+			pfree(cstr);
+			break;
+		case jbvString:
+			item = make_text_key(v->string.val, v->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 0000000..7087521
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_op.c
@@ -0,0 +1,289 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/hash.h"
+#include "miscadmin.h"
+#include "utils/builtins.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 *v = NULL;
+
+	JsonbValue	kval;
+
+	kval.type = jbvString;
+	kval.string.val = VARDATA_ANY(key);
+	kval.string.len = VARSIZE_ANY_EXHDR(key);
+
+	if (!JB_ISEMPTY(jb))
+		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 *v = arrayToJsonbSortedArray(keys);
+	int			i;
+	uint32	   *plowbound = NULL,
+				lowbound = 0;
+
+	if (JB_ISEMPTY(jb) || v == NULL || v->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 < v->array.nElems; i++)
+	{
+		if (findJsonbValueFromSuperHeader(VARDATA(jb),
+										  JB_FOBJECT | JB_FARRAY,
+										  plowbound,
+										  v->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 *v = arrayToJsonbSortedArray(keys);
+	uint32	   *plowbound = NULL;
+	uint32		lowbound = 0;
+	int			i;
+
+	if (v == NULL || v->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 < v->array.nElems; i++)
+	{
+		if (findJsonbValueFromSuperHeader(VARDATA(jb),
+										  JB_FOBJECT | JB_FARRAY,
+										  plowbound,
+										  v->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(deepContains(&it1, &it2));
+}
+
+Datum
+jsonb_contained(PG_FUNCTION_ARGS)
+{
+	/* Just invert "contains" */
+	PG_RETURN_DATUM(DirectFunctionCall2(jsonb_contains,
+										PG_GETARG_DATUM(1),
+										PG_GETARG_DATUM(0)));
+}
+
+Datum
+jsonb_ne(PG_FUNCTION_ARGS)
+{
+	int			res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp,
+														PG_GETARG_DATUM(0),
+														PG_GETARG_DATUM(1)));
+
+	PG_RETURN_BOOL(res != 0);
+}
+
+/*
+ * B-Tree operator class operators, support function
+ */
+Datum
+jsonb_lt(PG_FUNCTION_ARGS)
+{
+	int			res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp,
+														PG_GETARG_DATUM(0),
+														PG_GETARG_DATUM(1)));
+
+	PG_RETURN_BOOL(res < 0);
+}
+
+Datum
+jsonb_gt(PG_FUNCTION_ARGS)
+{
+	int			res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp,
+														PG_GETARG_DATUM(0),
+														PG_GETARG_DATUM(1)));
+
+	PG_RETURN_BOOL(res > 0);
+}
+
+Datum
+jsonb_le(PG_FUNCTION_ARGS)
+{
+	int			res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp,
+														PG_GETARG_DATUM(0),
+														PG_GETARG_DATUM(1)));
+
+	PG_RETURN_BOOL(res <= 0);
+}
+
+Datum
+jsonb_ge(PG_FUNCTION_ARGS)
+{
+	int			res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp,
+														PG_GETARG_DATUM(0),
+														PG_GETARG_DATUM(1)));
+
+	PG_RETURN_BOOL(res >= 0);
+}
+
+Datum
+jsonb_eq(PG_FUNCTION_ARGS)
+{
+	int			res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp,
+														PG_GETARG_DATUM(0),
+														PG_GETARG_DATUM(1)));
+
+	PG_RETURN_BOOL(res == 0);
+}
+
+Datum
+jsonb_cmp(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb1 = PG_GETARG_JSONB(0);
+	Jsonb	   *jb2 = PG_GETARG_JSONB(1);
+
+	int			res;
+
+	if (JB_ISEMPTY(jb1) || JB_ISEMPTY(jb2))
+	{
+		if (JB_ISEMPTY(jb1))
+		{
+			if (JB_ISEMPTY(jb2))
+				res = 0;
+			else
+				res = -1;
+		}
+		else
+		{
+			res = 1;
+		}
+	}
+	else if (JB_ROOT_IS_SCALAR(jb1) && !JB_ROOT_IS_SCALAR(jb2))
+	{
+		res = -1;
+	}
+	else if (JB_ROOT_IS_SCALAR(jb2) && !JB_ROOT_IS_SCALAR(jb1))
+	{
+		res = 1;
+	}
+	else
+	{
+		res = compareJsonbSuperHeaderValue(VARDATA(jb1), VARDATA(jb2));
+	}
+
+	/*
+	 * This is a btree support function; this is one of the few places where
+	 * memory needs to be explicitly freed.
+	 */
+	PG_FREE_IF_COPY(jb1, 0);
+	PG_FREE_IF_COPY(jb2, 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)
+		{
+			case WJB_BEGIN_ARRAY:
+			case WJB_BEGIN_OBJECT:
+			case WJB_END_ARRAY:
+			case WJB_END_OBJECT:
+				/*
+				 * No particular effort is made to mix in the structure of the
+				 * Jsonb itself (number of keys, values and elements
+				 * notwithstanding), on the assumption that it's very
+				 * homogeneous among Json datums in the same column.
+				 */
+				break;
+			case WJB_KEY:
+			case WJB_VALUE:
+			case WJB_ELEM:
+				hash_scalar_value(&v, &hash);
+				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 0000000..ea6bea0
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -0,0 +1,1771 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_support.c
+ *	  Support functions for jsonb
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ * src/backend/utils/adt/jsonb_util.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "miscadmin.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+#include "utils/jsonb.h"
+
+/*
+ * State used while converting an arbitrary JsonbValue into a Jsonb value
+ * (4-byte varlena uncompressed representation of a Jsonb)
+ *
+ * ConvertLevel: Bookkeeping around current level when converting.
+ */
+typedef struct convertLevel
+{
+	uint32		i;
+	uint32	   *header;
+	JEntry	   *meta;
+	char	   *begin;
+} convertLevel;
+
+typedef struct convertState
+{
+	/* Preallocated buffer in which to form varlena/Jsonb value */
+	Jsonb			   *buffer;
+	/* Pointer into buffer */
+	char			   *ptr;
+
+	convertLevel	   *levelstate,
+					   *curlptr,
+					   *prvlptr;
+
+	/* Current size of buffer holding levelstate array */
+	Size		levelSz;
+
+}	convertState;
+
+static bool compareJsonbScalarValue(JsonbValue * a, JsonbValue * b);
+static int lexicalCompareJsonbStringValue(const void *a, const void *b);
+static Size convertJsonb(JsonbValue * v, Jsonb* buffer);
+static void walkJsonbValueConversion(JsonbValue * value, convertState * state,
+									 uint32 nestlevel);
+static void putJsonbValueConversion(convertState * state, JsonbValue * value,
+									uint32 flags, uint32 level);
+static void putStringConversion(convertState * state, JsonbValue * value,
+								uint32 level, uint32 i);
+static void iteratorFromContainerBuf(JsonbIterator * it, char *buffer);
+static bool formIterIsContainer(JsonbIterator ** it, JsonbValue * v,
+								JEntry * e, bool skipNested);
+static JsonbIterator *freeAndGetParent(JsonbIterator * it);
+static ToJsonbState *pushState(ToJsonbState ** state);
+static void appendKey(ToJsonbState * state, JsonbValue * v);
+static void appendValue(ToJsonbState * state, JsonbValue * v);
+static void appendElement(ToJsonbState * state, JsonbValue * v);
+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 * v);
+static void uniqueifyJsonbArray(JsonbValue * v);
+
+/*
+ * Turn a JsonbValue into a Jsonb
+ */
+Jsonb *
+JsonbValueToJsonb(JsonbValue * v)
+{
+	Jsonb	   *out;
+
+	if (v->type >= jbvNull && v->type < jbvArray)
+	{
+		/* Scalar value */
+		ToJsonbState *state = NULL;
+		JsonbValue *res;
+		Size		sz;
+		JsonbValue	scalarArray;
+
+		scalarArray.type = jbvArray;
+		scalarArray.array.scalar = true;
+		scalarArray.array.nElems = 1;
+
+		pushJsonbValue(&state, WJB_BEGIN_ARRAY, &scalarArray);
+		pushJsonbValue(&state, WJB_ELEM, v);
+		res = pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+		out = palloc(VARHDRSZ + res->estSize);
+		sz = convertJsonb(res, out);
+		Assert(sz <= res->estSize);
+		SET_VARSIZE(out, sz + VARHDRSZ);
+	}
+	else if (v->type == jbvObject || v->type == jbvArray)
+	{
+		uint32		sz;
+
+		out = palloc(VARHDRSZ + v->estSize);
+		sz = convertJsonb(v, out);
+		Assert(sz <= v->estSize);
+		SET_VARSIZE(out, VARHDRSZ + sz);
+	}
+	else
+	{
+		Assert(v->type == jbvBinary);
+
+		out = palloc(VARHDRSZ + v->binary.len);
+		SET_VARSIZE(out, VARHDRSZ + v->binary.len);
+		memcpy(VARDATA(out), v->binary.data, v->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 *it1,
+				  *it2;
+	int			res = 0;
+
+	it1 = JsonbIteratorInit(a);
+	it2 = JsonbIteratorInit(b);
+
+	do
+	{
+		JsonbValue	v1,
+					v2;
+		int			r1,
+					r2;
+
+		r1 = JsonbIteratorNext(&it1, &v1, false);
+		r2 = JsonbIteratorNext(&it2, &v2, 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 (r1 == r2)
+		{
+			if (r1 == WJB_DONE)
+			{
+				/* Decisively equal */
+				break;
+			}
+
+			if (v1.type == v2.type)
+			{
+				switch (v1.type)
+				{
+					case jbvNull:
+						res = 0;
+						break;
+					case jbvString:
+						res = lexicalCompareJsonbStringValue(&v1, &v2);
+						break;
+					case jbvNumeric:
+						res = DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+																PointerGetDatum(v1.numeric),
+																PointerGetDatum(v2.numeric)));
+						break;
+					case jbvBool:
+						if (v1.boolean != v2.boolean)
+							res = (v1.boolean > v2.boolean) ? 1 : -1;
+						break;
+					case jbvArray:
+						if (v1.array.nElems != v2.array.nElems)
+							res = (v1.array.nElems > v2.array.nElems) ? 1 : -1;
+						break;
+					case jbvObject:
+						if (v1.object.nPairs != v2.object.nPairs)
+							res = (v1.object.nPairs > v2.object.nPairs) ? 1 : -1;
+						break;
+					case jbvBinary:
+						/*
+						 * Do nothing.  We can rely on next iterator to have
+						 * details of underlying type.
+						 */
+						break;
+				}
+			}
+			else
+			{
+				res = (v1.type > v2.type) ? 1 : -1;		/* Type-defined order */
+			}
+		}
+		else
+		{
+			if (v1.type == v2.type)
+			{
+				Assert(r1 != WJB_DONE && r2 != WJB_DONE);
+
+				/*
+				 * Types were the same, and yet since iterator return codes
+				 * differed, one must have finished before the other (and thus
+				 * there must be a variation in the number of pairs/elements).
+				 */
+				if (v1.type == jbvArray)
+				{
+					Assert(v1.array.nElems != v2.array.nElems);
+					res = (v1.array.nElems > v2.array.nElems) ? 1 : -1;
+				}
+				else if (v1.type == jbvObject)
+				{
+					Assert(v1.object.nPairs != v2.object.nPairs);
+					res = (v1.object.nPairs > v2.object.nPairs) ? 1 : -1;
+				}
+				else
+				{
+					elog(ERROR, "unexpected non-container: %d", v1.type);
+				}
+			}
+			else
+			{
+				/* Type-defined order */
+				res = (v1.type > v2.type) ? 1 : -1;
+			}
+		}
+	}
+	while (res == 0);
+
+	while (it1 != NULL)
+	{
+		JsonbIterator *i = it1->parent;
+		pfree(it1);
+		it1 = i;
+	}
+	while (it2 != NULL)
+	{
+		JsonbIterator *i = it2->parent;
+		pfree(it2);
+		it2 = 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".  "flags" argument
+ * allows 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.  That's because for the purposes of
+ * containment we bunch together keys and values in objects, whereas we just
+ * look at array elements when looking at arrays (They're the only "keys"
+ * there).  Arrays may contain heterogeneous values, whereas objects must
+ * contain only pairs.  That may seem like a very fine distinction when
+ * considering their layout, but it is quite salient to the user-visible
+ * definition of containment.
+ *
+ * If the caller asks to look through a container type that is not of the type
+ * pointer 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.
+ */
+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	   *r = 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 = (lowbound ? *lowbound : 0); i < count; i++)
+		{
+			JEntry	   *e = array + i;
+
+			if (JBE_ISNULL(*e) && key->type == jbvNull)
+			{
+				r->type = jbvNull;
+				if (lowbound)
+					*lowbound = i;
+				r->estSize = sizeof(JEntry);
+
+				return r;
+			}
+			else if (JBE_ISSTRING(*e) && key->type == jbvString)
+			{
+				/* Equivalent to lengthCompareJsonbStringValue() */
+				if (key->string.len == JBE_LEN(*e) &&
+					memcmp(key->string.val, data + JBE_OFF(*e),
+						   key->string.len) == 0)
+				{
+					r->type = jbvString;
+					r->string.val = data + JBE_OFF(*e);
+					r->string.len = key->string.len;
+					r->estSize = sizeof(JEntry) + r->string.len;
+					if (lowbound)
+						*lowbound = i;
+
+					return r;
+				}
+			}
+			else if (JBE_ISBOOL(*e) && key->type == jbvBool)
+			{
+				if ((JBE_ISBOOL_TRUE(*e) && key->boolean) ||
+					(JBE_ISBOOL_FALSE(*e) && !key->boolean))
+				{
+					/* Deep copy */
+					*r = *key;
+					r->estSize = sizeof(JEntry);
+					if (lowbound)
+						*lowbound = i;
+
+					return r;
+				}
+			}
+			else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric)
+			{
+				Numeric entry = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+
+				if (DatumGetBool(DirectFunctionCall2(numeric_eq,
+													 PointerGetDatum(entry),
+													 PointerGetDatum(key->numeric))))
+				{
+					r->type = jbvNumeric;
+					r->numeric = entry;
+
+					if (lowbound)
+						*lowbound = i;
+
+					return r;
+				}
+			}
+		}
+	}
+	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 for matching jsonb value using "inner comparator"
+		 * logic (i.e. not doing lexical comparisons).
+		 *
+		 * Searching through object's pair "keys" *only*.
+		 */
+		while (stopLow < count)
+		{
+			JEntry	   *entry;
+			int			difference;
+
+			/*
+			 * 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;
+
+			/* Equivalent to lengthCompareJsonbStringValue() */
+			if (key->string.len == JBE_LEN(*entry))
+				difference = memcmp(data + JBE_OFF(*entry), key->string.val,
+									key->string.len);
+			else
+				difference = (JBE_LEN(*entry) > key->string.len) ? 1 : -1;
+
+			if (difference == 0)
+			{
+				/* Found our value (from key/value pair) */
+				JEntry	   *v = entry + 1;
+
+				if (lowbound)
+					*lowbound = stopMiddle + 1;
+
+				if (JBE_ISSTRING(*v))
+				{
+					r->type = jbvString;
+					r->string.val = data + JBE_OFF(*v);
+					r->string.len = JBE_LEN(*v);
+					r->estSize = sizeof(JEntry) + r->string.len;
+				}
+				else if (JBE_ISBOOL(*v))
+				{
+					r->type = jbvBool;
+					r->boolean = JBE_ISBOOL_TRUE(*v) != 0;
+					r->estSize = sizeof(JEntry);
+				}
+				else if (JBE_ISNUMERIC(*v))
+				{
+					r->type = jbvNumeric;
+					r->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v)));
+
+					r->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(r->numeric);
+				}
+				else if (JBE_ISNULL(*v))
+				{
+					r->type = jbvNull;
+					r->estSize = sizeof(JEntry);
+				}
+				else
+				{
+					/*
+					 * See header comments to understand why this function
+					 * never does this for arrays
+					 */
+					r->type = jbvBinary;
+					r->binary.data = data + INTALIGN(JBE_OFF(*v));
+					r->binary.len = JBE_LEN(*v) -
+						(INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v));
+					r->estSize = 2 * sizeof(JEntry) + r->binary.len;
+				}
+
+				return r;
+			}
+			else
+			{
+				if (difference < 0)
+					stopLow = stopMiddle + 1;
+				else
+					count = stopMiddle;
+			}
+		}
+
+		if (lowbound)
+			*lowbound = stopLow;
+	}
+
+	/* Not found */
+	return NULL;
+}
+
+/*
+ * Get i-th value of array or object.
+ *
+ * Returns palloc()'d copy of value, or NULL if it cannot be found.  "flags"
+ * allows caller to specify which container types are of interest.
+ */
+JsonbValue *
+getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
+								uint32 i)
+{
+	uint32		superheader = *(uint32 *) sheader;
+	JsonbValue *r;
+	JEntry	   *array,
+			   *e;
+	char	   *data;
+
+	Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
+
+	r = palloc(sizeof(JsonbValue));
+
+	if (i >= (superheader & JB_CMASK))
+		return NULL;
+
+	array = (JEntry *) (sheader + sizeof(uint32));
+
+	if (flags & JB_FARRAY & superheader)
+	{
+		e = array + i;
+		data = (char *) (array + (superheader & JB_CMASK));
+	}
+	else if (flags & JB_FOBJECT & superheader)
+	{
+		e = array + i * 2 + 1;
+		data = (char *) (array + (superheader & JB_CMASK) * 2);
+	}
+	else
+	{
+		return NULL;
+	}
+
+	if (JBE_ISSTRING(*e))
+	{
+		r->type = jbvString;
+		r->string.val = data + JBE_OFF(*e);
+		r->string.len = JBE_LEN(*e);
+		r->estSize = sizeof(JEntry) + r->string.len;
+	}
+	else if (JBE_ISBOOL(*e))
+	{
+		r->type = jbvBool;
+		r->boolean = JBE_ISBOOL_TRUE(*e) != 0;
+		r->estSize = sizeof(JEntry);
+	}
+	else if (JBE_ISNUMERIC(*e))
+	{
+		r->type = jbvNumeric;
+		r->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+		r->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(r->numeric);
+	}
+	else if (JBE_ISNULL(*e))
+	{
+		r->type = jbvNull;
+		r->estSize = sizeof(JEntry);
+	}
+	else
+	{
+		r->type = jbvBinary;
+		r->binary.data = data + INTALIGN(JBE_OFF(*e));
+		r->binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e));
+		r->estSize = r->binary.len + 2 * sizeof(JEntry);
+	}
+
+	return r;
+}
+
+/*
+ * Push JsonbValue into ToJsonbState.
+ *
+ * With r = WJB_END_OBJECT and v = NULL, this function sorts and unique-ifys
+ * the passed object's key values.  Otherwise, they are assumed to already be
+ * sorted and unique.
+ *
+ * Initial state of *ToJsonbState is NULL.
+ */
+JsonbValue *
+pushJsonbValue(ToJsonbState ** state, int r, JsonbValue * v)
+{
+	JsonbValue *result = NULL;
+
+	switch (r)
+	{
+		case WJB_BEGIN_ARRAY:
+			*state = pushState(state);
+			result = &(*state)->v;
+			(*state)->v.type = jbvArray;
+			(*state)->v.estSize = 3 * sizeof(JEntry);
+			(*state)->v.array.nElems = 0;
+			(*state)->v.array.scalar = (v && v->array.scalar) != 0;
+			(*state)->size = (v && v->type == jbvArray && v->array.nElems > 0)
+				? v->array.nElems : 4;
+			(*state)->v.array.elems = palloc(sizeof(JsonbValue) *
+											 (*state)->size);
+			break;
+		case WJB_BEGIN_OBJECT:
+			*state = pushState(state);
+			result = &(*state)->v;
+			(*state)->v.type = jbvObject;
+			(*state)->v.estSize = 3 * sizeof(JEntry);
+			(*state)->v.object.nPairs = 0;
+			(*state)->size = (v && v->type == jbvObject && v->object.nPairs > 0) ?
+				v->object.nPairs : 4;
+			(*state)->v.object.pairs = palloc(sizeof(JsonbPair) *
+											  (*state)->size);
+			break;
+		case WJB_KEY:
+			Assert(v->type == jbvString);
+			appendKey(*state, v);
+			break;
+		case WJB_VALUE:
+			Assert((v->type >= jbvNull && v->type < jbvArray) || v->type == jbvBinary);
+			appendValue(*state, v);
+			break;
+		case WJB_ELEM:
+			Assert((v->type >= jbvNull && v->type < jbvArray) || v->type == jbvBinary);
+			appendElement(*state, v);
+			break;
+		case WJB_END_OBJECT:
+			result = &(*state)->v;
+			/*
+			 * When v != NULL and control reaches here, keys should already be
+			 * sorted
+			 */
+			if (v == NULL)
+				uniqueifyJsonbObject(result);
+
+			/*
+			 * No break statement here - fall through and perform those steps
+			 * required for the WJB_END_ARRAY case too.  The end of a jsonb
+			 * "object" associative structure may require us to first
+			 * unique-ify its values, but values must then be appended to state
+			 * in the same fashion as arrays.
+			 */
+		case WJB_END_ARRAY:
+			result = &(*state)->v;
+
+			/*
+			 * Pop stack and push current array/"object" as value in parent
+			 * array/"object"
+			 */
+			*state = (*state)->next;
+			if (*state)
+			{
+				switch ((*state)->v.type)
+				{
+					case jbvArray:
+						appendElement(*state, result);
+						break;
+					case jbvObject:
+						appendValue(*state, result);
+						break;
+					default:
+						elog(ERROR, "invalid jsonb container type");
+				}
+			}
+			break;
+		default:
+			elog(ERROR, "invalid jsonb container type");
+	}
+
+	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 can depend on having any other memory previously allocated for
+ * iterators but not in that line having already been freed here.
+ *
+ * Returns "Jsonb binary 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.
+ */
+int
+JsonbIteratorNext(JsonbIterator ** it, JsonbValue * v, bool skipNested)
+{
+	iterState	state;
+
+	/* Guard against stack overflow due to overly complex Jsonb values */
+	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 */
+			v->type = jbvArray;
+			v->array.nElems = (*it)->nElems;
+			v->array.scalar = (*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, v, &(*it)->meta[(*it)->i++],
+										 skipNested))
+			{
+				/*
+				 * New child iterator acquired within formIterIsContainer.
+				 * Recurse into container.
+				 */
+				return JsonbIteratorNext(it, v, 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 */
+			v->type = jbvObject;
+			v->object.nPairs = (*it)->nElems;
+			(*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 (by setting skipNested to false).  No
+				 * child iterator, no recursion.
+				 */
+				if (formIterIsContainer(it, v, &(*it)->meta[(*it)->i * 2], false))
+					elog(ERROR, "unexpected container as object key");
+
+				Assert(v->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 (formIterIsContainer(it, v, &(*it)->meta[((*it)->i++) * 2 + 1],
+									skipNested))
+				return JsonbIteratorNext(it, v, skipNested);
+			else
+				return WJB_VALUE;
+		}
+	}
+
+	elog(ERROR, "invalid iterator state");
+}
+
+/*
+ * Worker for "contains" operator's function
+ *
+ * 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.
+ *
+ * Containment means that all of the values in the rhs "mContained" iterator's
+ * datum exist within the lhs "val" datum at the same nesting level (and, by
+ * corollary, all sub-nesting levels on the rhs).
+ */
+bool
+deepContains(JsonbIterator ** val, JsonbIterator ** mContained)
+{
+	uint32		rval,
+				rcont;
+	JsonbValue	vval,
+				vcontained;
+	/*
+	 * Guard against stack overflow due to overly complex Jsonb values.
+	 *
+	 * 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)
+	{
+		/* Work through rhs "is it contained within?" object */
+		uint32		lowbound = 0;
+
+		/* lhsVal is from pair in lhs object */
+		JsonbValue *lhsVal;
+
+		for (;;)
+		{
+			rcont = JsonbIteratorNext(mContained, &vcontained, false);
+
+			/*
+			 * When we get through caller's rhs "is it contained within?"
+			 * object without failing to find some value, we're done.
+			 */
+			if (rcont == WJB_END_OBJECT)
+				return true;
+
+			Assert(rcont == WJB_KEY);
+
+			/* First, find value by key... */
+			lhsVal = findJsonbValueFromSuperHeader((*val)->buffer,
+												   JB_FOBJECT,
+												   &lowbound,
+												   &vcontained);
+
+			if (!lhsVal)
+				return false;
+
+			/* ...at this stage we know that there is at least a key match */
+			rcont = JsonbIteratorNext(mContained, &vcontained, true);
+
+			Assert(rcont == WJB_VALUE);
+
+			if (lhsVal->type != vcontained.type)
+			{
+				return false;
+			}
+			else if (lhsVal->type >= jbvNull && lhsVal->type < jbvArray)
+			{
+				if (!compareJsonbScalarValue(lhsVal, &vcontained))
+					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);
+
+				if (!deepContains(&nestval, &nestContained))
+					return false;
+			}
+		}
+	}
+	else if (rcont == WJB_BEGIN_ARRAY)
+	{
+		/*
+		 * Work through rhs "is it contained within?" array.
+		 *
+		 * Note that since scalars appear only within pseudo arrays, user might
+		 * be inquiring if a scalar is contained within the array, which is
+		 * fully equivalent to inquiring if a single-element array of the same
+		 * scalar is contained within lhs val.  When that occurs, there will
+		 * just be one scalar to consider here.  Note that that does not imply
+		 * that a scalar array is equal to an array of the same scalar value
+		 * according to the default B-Tree operator class.
+		 */
+		JsonbValue *rhsVals = NULL;
+		uint32		nElems = vval.array.nElems;
+
+		for (;;)
+		{
+			rcont = JsonbIteratorNext(mContained, &vcontained, true);
+			if (rcont == WJB_END_ARRAY)
+				return true;
+
+			Assert(rcont == WJB_ELEM);
+
+			if (vcontained.type >= jbvNull && vcontained.type < jbvArray)
+			{
+				if (!findJsonbValueFromSuperHeader((*val)->buffer,
+												   JB_FARRAY,
+												   NULL,
+												   &vcontained))
+					return false;
+			}
+			else
+			{
+				uint32		i;
+
+				if (rhsVals == NULL) /* ...this is first container found in rhs array? */
+				{
+					uint32		j = 0;
+
+					/* Make room for all possible values */
+					rhsVals = palloc(sizeof(JsonbValue) * nElems);
+
+					for (i = 0; i < nElems; i++)
+					{
+						/* Store all lhs elements in temp array*/
+						rcont = JsonbIteratorNext(val, &vval, true);
+						Assert(rcont == WJB_ELEM);
+
+						if (vval.type == jbvBinary)
+							rhsVals[j++] = vval;
+					}
+
+					if (j == 0)
+						return false;
+
+					/* We may have only partially filled array */
+					nElems = j;
+				}
+
+				/* Iterate through temp array */
+				for (i = 0; i < nElems; i++)
+				{
+					/* Nested container value (object or array) */
+					JsonbIterator *nestval, *nestContained;
+
+					nestval = JsonbIteratorInit(rhsVals[i].binary.data);
+					nestContained = JsonbIteratorInit(vcontained.binary.data);
+
+					if (!deepContains(&nestval, &nestContained))
+						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 for items in the
+ * array.
+ */
+JsonbValue *
+arrayToJsonbSortedArray(ArrayType *a)
+{
+	Datum	   *key_datums;
+	bool	   *key_nulls;
+	int			key_count;
+	JsonbValue *result;
+	int			i,
+				j;
+	Size		maxn = Min(MaxAllocSize / sizeof(JsonbPair),
+						   JENTRY_POSMASK / sizeof(JsonbPair));
+
+	/* Extract data for sorting */
+	deconstruct_array(a, TEXTOID, -1, false, 'i', &key_datums, &key_nulls,
+					  &key_count);
+
+	if (key_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 (key_count > maxn)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of pairs (%d) exceeds the maximum allowed (%zu)",
+						key_count, maxn)));
+
+	result = palloc(sizeof(JsonbValue));
+	result->type = jbvArray;
+	result->array.scalar = false;
+	result->array.elems = palloc(sizeof(JsonbPair) * key_count);
+
+	for (i = 0, j = 0; i < key_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
+ */
+void
+hash_scalar_value(const JsonbValue * v, uint32 * hash_state)
+{
+	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 element's hash
+	 * value.
+	 */
+	*hash_state = (*hash_state << 1) | (*hash_state >> 31);
+	switch (v->type)
+	{
+		case jbvNull:
+			*hash_state ^= 0x01;
+			break;
+		case jbvBool:
+			*hash_state ^= v->boolean? 0x02:0x04;
+			break;
+		case jbvNumeric:
+			/* Must be unaffected by trailing zeroes */
+			tmp = DatumGetInt32(DirectFunctionCall1(hash_numeric,
+													NumericGetDatum(v->numeric)));
+			*hash_state ^= tmp;
+			break;
+		case jbvString:
+			tmp = hash_any((unsigned char *) v->string.val, v->string.len);
+			*hash_state ^= tmp;
+			break;
+		default:
+			elog(ERROR, "invalid jsonb scalar type");
+			break;
+	}
+}
+
+/*
+ * Are two scalar JsonbValues a and b equal?
+ *
+ * Does not use lexical comparisons.  Therefore, it is essentially that this
+ * never be used for anything other than searching for values within a single
+ * jsonb.
+ *
+ * We return bool because we don't want to give anyone any ideas about using
+ * this for sorting.  This is just for "contains" style searching.
+ */
+static bool
+compareJsonbScalarValue(JsonbValue * a, JsonbValue * b)
+{
+	if (a->type == b->type)
+	{
+		switch (a->type)
+		{
+			case jbvNull:
+				return true;
+			case jbvString:
+				return lengthCompareJsonbStringValue(a, b, NULL) == 0;
+			case jbvBool:
+				return a->boolean == b->boolean;
+			case jbvNumeric:
+				return DatumGetBool(DirectFunctionCall2(numeric_eq,
+														PointerGetDatum(a->numeric),
+														PointerGetDatum(b->numeric)));
+			default:
+				elog(ERROR, "invalid jsonb scalar type");
+		}
+	}
+
+	return false;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Put JsonbValue tree into a preallocated Jsonb buffer
+ */
+static Size
+convertJsonb(JsonbValue * v, Jsonb *buffer)
+{
+	convertState	state;
+	Size			len = 0;
+
+	/* Should not already have binary representation */
+	Assert(v->type != jbvBinary);
+
+	state.buffer = buffer;
+	/* Start from superheader */
+	state.ptr = VARDATA(state.buffer);
+	state.levelSz = 8;
+	state.levelstate = palloc(sizeof(convertLevel) * state.levelSz);
+
+	walkJsonbValueConversion(v, &state, 0);
+
+	len = state.ptr - VARDATA(state.buffer);
+
+	Assert(len <= v->estSize);
+	return len;
+}
+
+/*
+ * Walk the tree representation of Jsonb, as part of the process of converting
+ * a JsonbValue to a Jsonb
+ */
+static void
+walkJsonbValueConversion(JsonbValue * value, convertState * state,
+						 uint32 nestlevel)
+{
+	int			i;
+
+	check_stack_depth();
+
+	if (!value)
+		return;
+
+	switch (value->type)
+	{
+		case jbvArray:
+			putJsonbValueConversion(state, value, WJB_BEGIN_ARRAY, nestlevel);
+			for (i = 0; i < value->array.nElems; i++)
+			{
+				if ((value->array.elems[i].type >= jbvNull &&
+					value->array.elems[i].type < jbvArray) ||
+					value->array.elems[i].type == jbvBinary)
+					putJsonbValueConversion(state, value->array.elems + i,
+											WJB_ELEM, nestlevel);
+				else
+					walkJsonbValueConversion(value->array.elems + i, state,
+											 nestlevel + 1);
+			}
+			putJsonbValueConversion(state, value, WJB_END_ARRAY, nestlevel);
+			break;
+		case jbvObject:
+			putJsonbValueConversion(state, value, WJB_BEGIN_OBJECT, nestlevel);
+
+			for (i = 0; i < value->object.nPairs; i++)
+			{
+				putJsonbValueConversion(state, &value->object.pairs[i].key,
+										WJB_KEY, nestlevel);
+
+				if ((value->object.pairs[i].value.type >= jbvNull &&
+					value->object.pairs[i].value.type < jbvArray) ||
+					value->object.pairs[i].value.type == jbvBinary)
+					putJsonbValueConversion(state,
+											&value->object.pairs[i].value,
+											WJB_VALUE, nestlevel);
+				else
+					walkJsonbValueConversion(&value->object.pairs[i].value,
+											 state, nestlevel + 1);
+			}
+
+			putJsonbValueConversion(state, value, WJB_END_OBJECT, nestlevel);
+			break;
+		default:
+			elog(ERROR, "unknown type of jsonb container");
+	}
+}
+
+/*
+ * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
+ * copy an arbitrary individual JsonbValue.  This function may copy over 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 touched.  The function is called separately for the
+ * start of an Object/Array, and the end.
+ *
+ * This is a worker function for walkJsonbValueConversion().
+ */
+static void
+putJsonbValueConversion(convertState * state, JsonbValue * value, uint32 flags,
+						uint32 level)
+{
+	if (level == state->levelSz)
+	{
+		state->levelSz *= 2;
+		state->levelstate = repalloc(state->levelstate,
+									 sizeof(convertLevel) * state->levelSz);
+	}
+
+	state->curlptr = state->levelstate + level;
+
+	if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT))
+	{
+		short		padlen, p;
+
+		Assert(((flags & WJB_BEGIN_ARRAY) && value->type == jbvArray) ||
+			   ((flags & WJB_BEGIN_OBJECT) && value->type == jbvObject));
+
+		state->curlptr->begin = state->ptr;
+
+		padlen = INTALIGN(state->ptr - VARDATA(state->buffer)) -
+			(state->ptr - VARDATA(state->buffer));
+
+		/*
+		 * Add padding as necessary
+		 */
+		for (p = padlen; p > 0; p--)
+		{
+			*state->ptr = '\0';
+			state->ptr++;
+		}
+
+		state->curlptr->header = (uint32 *) state->ptr;
+		/* Advance past header */
+		state->ptr += sizeof(uint32);
+
+		state->curlptr->meta = (JEntry *) state->ptr;
+		state->curlptr->i = 0;
+
+		if (value->type == jbvArray)
+		{
+			*state->curlptr->header = value->array.nElems | JB_FARRAY;
+			state->ptr += sizeof(JEntry) * value->array.nElems;
+
+			if (value->array.scalar)
+			{
+				Assert(value->array.nElems == 1);
+				Assert(level == 0);
+				*state->curlptr->header |= JB_FSCALAR;
+			}
+		}
+		else
+		{
+			*state->curlptr->header = value->object.nPairs | JB_FOBJECT;
+			state->ptr += sizeof(JEntry) * value->object.nPairs * 2;
+		}
+	}
+	else if (flags & WJB_ELEM)
+	{
+		putStringConversion(state, value, level, state->curlptr->i);
+		state->curlptr->i++;
+	}
+	else if (flags & WJB_KEY)
+	{
+		Assert(value->type == jbvString);
+
+		putStringConversion(state, value, level, state->curlptr->i * 2);
+	}
+	else if (flags & WJB_VALUE)
+	{
+		putStringConversion(state, value, level, state->curlptr->i * 2 + 1);
+		state->curlptr->i++;
+	}
+	else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT))
+	{
+		uint32		len,
+					i;
+
+		Assert(((flags & WJB_END_ARRAY) && value->type == jbvArray) ||
+			   ((flags & WJB_END_OBJECT) && value->type == jbvObject));
+
+		if (level == 0)
+			return;
+
+		len = state->ptr - (char *) state->curlptr->begin;
+
+		state->prvlptr = state->curlptr - 1;
+
+		if (*state->prvlptr->header & JB_FARRAY)
+		{
+			i = state->prvlptr->i;
+
+			state->prvlptr->meta[i].header = JENTRY_ISNEST;
+
+			if (i == 0)
+				state->prvlptr->meta[0].header |= JENTRY_ISFIRST | len;
+			else
+				state->prvlptr->meta[i].header |=
+					(state->prvlptr->meta[i - 1].header & JENTRY_POSMASK) + len;
+		}
+		else if (*state->prvlptr->header & JB_FOBJECT)
+		{
+			i = 2 * state->prvlptr->i + 1;		/* Value, not key */
+
+			state->prvlptr->meta[i].header = JENTRY_ISNEST;
+
+			state->prvlptr->meta[i].header |=
+				(state->prvlptr->meta[i - 1].header & JENTRY_POSMASK) + len;
+		}
+		else
+		{
+			elog(ERROR, "invalid jsonb container type");
+		}
+
+		Assert(state->ptr - state->curlptr->begin <= value->estSize);
+		state->prvlptr->i++;
+	}
+	else
+	{
+		elog(ERROR, "unknown flag encountered during jsonb tree walk");
+	}
+}
+
+/*
+ * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
+ * copy a string associated with a scalar value.
+ *
+ * This is a worker function for putJsonbValueConversion() (itself a worker for
+ * walkJsonbValueConversion()), handling aspects of copying strings in respect
+ * of all scalar values.  It handles the details with regard to Jentry metadata
+ * within convert state.
+ */
+static void
+putStringConversion(convertState * state, JsonbValue * value,
+					uint32 level, uint32 i)
+{
+	short		p, padlen;
+
+	state->curlptr = state->levelstate + level;
+
+	if (i == 0)
+		state->curlptr->meta[0].header = JENTRY_ISFIRST;
+	else
+		state->curlptr->meta[i].header = 0;
+
+	switch (value->type)
+	{
+		case jbvNull:
+			state->curlptr->meta[i].header |= JENTRY_ISNULL;
+
+			if (i > 0)
+				state->curlptr->meta[i].header |=
+					state->curlptr->meta[i - 1].header & JENTRY_POSMASK;
+			break;
+		case jbvString:
+			memcpy(state->ptr, value->string.val, value->string.len);
+			state->ptr += value->string.len;
+
+			if (i == 0)
+				state->curlptr->meta[i].header |= value->string.len;
+			else
+				state->curlptr->meta[i].header |=
+					(state->curlptr->meta[i - 1].header & JENTRY_POSMASK) +
+					value->string.len;
+			break;
+		case jbvBool:
+			state->curlptr->meta[i].header |= (value->boolean) ?
+				JENTRY_ISTRUE : JENTRY_ISFALSE;
+
+			if (i > 0)
+				state->curlptr->meta[i].header |=
+					state->curlptr->meta[i - 1].header & JENTRY_POSMASK;
+			break;
+		case jbvNumeric:
+			{
+				int numlen = VARSIZE_ANY(value->numeric);
+
+				padlen = INTALIGN(state->ptr - VARDATA(state->buffer)) -
+					(state->ptr - VARDATA(state->buffer));
+
+				/*
+				 * Add padding as necessary
+				 */
+				for (p = padlen; p > 0; p--)
+				{
+					*state->ptr = '\0';
+					state->ptr++;
+				}
+
+				memcpy(state->ptr, value->numeric, numlen);
+				state->ptr += numlen;
+
+				state->curlptr->meta[i].header |= JENTRY_ISNUMERIC;
+				if (i == 0)
+					state->curlptr->meta[i].header |= padlen + numlen;
+				else
+					state->curlptr->meta[i].header |=
+						(state->curlptr->meta[i - 1].header & JENTRY_POSMASK) +
+						padlen + numlen;
+				break;
+			}
+		default:
+			elog(ERROR, "invalid jsonb scalar type");
+	}
+}
+
+/*
+ * Initialize iterator from superheader pointer into buffer.  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.
+ */
+static bool
+formIterIsContainer(JsonbIterator ** it, JsonbValue * v, JEntry * e,
+					bool skipNested)
+{
+	if (JBE_ISSTRING(*e))
+	{
+		v->type = jbvString;
+		v->string.val = (*it)->dataProper + JBE_OFF(*e);
+		v->string.len = JBE_LEN(*e);
+		v->estSize = sizeof(JEntry) + v->string.len;
+
+		return false;
+	}
+	else if (JBE_ISBOOL(*e))
+	{
+		v->type = jbvBool;
+		v->boolean = JBE_ISBOOL_TRUE(*e) != 0;
+		v->estSize = sizeof(JEntry);
+
+		return false;
+	}
+	else if (JBE_ISNUMERIC(*e))
+	{
+		v->type = jbvNumeric;
+		v->numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*e)));
+		v->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(v->numeric);
+
+		return false;
+	}
+	else if (JBE_ISNULL(*e))
+	{
+		v->type = jbvNull;
+		v->estSize = sizeof(JEntry);
+
+		return false;
+	}
+	else if (skipNested)
+	{
+		v->type = jbvBinary;
+		v->binary.data = (*it)->dataProper + INTALIGN(JBE_OFF(*e));
+		v->binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e));
+		v->estSize = v->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(*e)));
+
+		child->parent = *it;
+		*it = child;
+
+		return true;
+	}
+}
+
+/*
+ * Return parent, while freeing memory for current iterator
+ */
+static JsonbIterator *
+freeAndGetParent(JsonbIterator * it)
+{
+	JsonbIterator *v = it->parent;
+
+	pfree(it);
+	return v;
+}
+
+/*
+ * Iteration-like forming jsonb
+ */
+static ToJsonbState *
+pushState(ToJsonbState ** state)
+{
+	ToJsonbState *ns = palloc(sizeof(ToJsonbState));
+
+	ns->next = *state;
+	return ns;
+}
+
+static void
+appendKey(ToJsonbState * state, JsonbValue * v)
+{
+	JsonbValue *a = &state->v;
+
+	Assert(a->type == jbvObject);
+
+	if (a->object.nPairs >= state->size)
+	{
+		state->size *= 2;
+		a->object.pairs = repalloc(a->object.pairs,
+								   sizeof(JsonbPair) * state->size);
+	}
+
+	a->object.pairs[a->object.nPairs].key = *v;
+	a->object.pairs[a->object.nPairs].order = a->object.nPairs;
+
+	a->estSize += v->estSize;
+}
+
+static void
+appendValue(ToJsonbState * state, JsonbValue * v)
+{
+	JsonbValue *a = &state->v;
+
+	Assert(a->type == jbvObject);
+
+	a->object.pairs[a->object.nPairs++].value = *v;
+	a->estSize += v->estSize;
+}
+
+static void
+appendElement(ToJsonbState * state, JsonbValue * v)
+{
+	JsonbValue *a = &state->v;
+
+	Assert(a->type == jbvArray);
+
+	if (a->array.nElems >= state->size)
+	{
+		state->size *= 2;
+		a->array.elems = repalloc(a->array.elems,
+								  sizeof(JsonbValue) * state->size);
+	}
+
+	a->array.elems[a->array.nElems++] = *v;
+	a->estSize += v->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, objects 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 (associative "object" data
+ * structure)
+ */
+static void
+uniqueifyJsonbObject(JsonbValue * v)
+{
+	bool		hasNonUniq = false;
+
+	Assert(v->type == jbvObject);
+
+	if (v->object.nPairs > 1)
+		qsort_arg(v->object.pairs, v->object.nPairs, sizeof(JsonbPair),
+				  lengthCompareJsonbPair, &hasNonUniq);
+
+	if (hasNonUniq)
+	{
+		JsonbPair  *ptr = v->object.pairs + 1,
+				   *res = v->object.pairs;
+
+		while (ptr - v->object.pairs < v->object.nPairs)
+		{
+			/* Avoid copying over duplicate */
+			if (lengthCompareJsonbStringValue(ptr, res, NULL) == 0)
+			{
+				v->estSize -= ptr->key.estSize + ptr->value.estSize;
+			}
+			else
+			{
+				res++;
+				if (ptr != res)
+					memcpy(res, ptr, sizeof(JsonbPair));
+			}
+			ptr++;
+		}
+
+		v->object.nPairs = res + 1 - v->object.pairs;
+	}
+}
+
+/*
+ * Sort and unique-ify JsonbArray
+ */
+static void
+uniqueifyJsonbArray(JsonbValue * v)
+{
+	bool hasNonUniq = false;
+
+	Assert(v->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 (v->array.nElems > 1)
+		qsort_arg(v->array.elems, v->array.nElems,
+				  sizeof(JsonbValue), lengthCompareJsonbStringValue,
+				  &hasNonUniq);
+
+	if (hasNonUniq)
+	{
+		JsonbValue *ptr = v->array.elems + 1,
+				   *res = v->array.elems;
+
+		while (ptr - v->array.elems < v->array.nElems)
+		{
+			/* Avoid copying over duplicate */
+			if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0)
+			{
+				res++;
+				*res = *ptr;
+			}
+
+			ptr++;
+		}
+
+		v->array.nElems = res + 1 - v->array.elems;
+	}
+}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 2320305..73d7d96 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -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,14 @@ 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,
+													uint32 *lowbound,
+													char *key,
+													uint32 keylen);
 
 /* search type classification for json_get* functions */
 typedef enum
@@ -225,18 +235,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 +440,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
  */
 
 
@@ -373,6 +463,51 @@ json_object_field(PG_FUNCTION_ARGS)
 }
 
 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)
 {
 	text	   *json = PG_GETARG_TEXT_P(0);
@@ -389,6 +524,74 @@ json_object_field_text(PG_FUNCTION_ARGS)
 }
 
 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)
 {
 	text	   *json = PG_GETARG_TEXT_P(0);
@@ -404,6 +607,44 @@ json_array_element(PG_FUNCTION_ARGS)
 }
 
 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)
 {
 	text	   *json = PG_GETARG_TEXT_P(0);
@@ -419,6 +660,69 @@ json_array_element_text(PG_FUNCTION_ARGS)
 }
 
 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)
 {
 	return get_path_all(fcinfo, false);
@@ -434,9 +738,10 @@ 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);
+	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
+	text	   *json;
 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
 	text	   *result;
 	Datum	   *pathtext;
@@ -448,6 +753,19 @@ get_path_all(PG_FUNCTION_ARGS, bool as_text)
 	long		ind;
 	char	   *endptr;
 
+	Assert(val_type == JSONOID || val_type == JSONBOID);
+	if (val_type == JSONOID)
+	{
+		/* just get the text */
+		json = PG_GETARG_TEXT_P(0);
+	}
+	else
+	{
+		Jsonb	   *jb = PG_GETARG_JSONB(0);
+
+		json = cstring_to_text(JsonbToCString(NULL, (JB_ISEMPTY(jb)) ? NULL : VARDATA(jb), VARSIZE(jb)));
+	}
+
 	if (array_contains_nulls(path))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -486,9 +804,17 @@ 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);
+	{
+		if (val_type == JSONOID || as_text)
+			PG_RETURN_TEXT_P(result);
+		else
+			PG_RETURN_JSONB(DirectFunctionCall1(jsonb_in, CStringGetDatum(text_to_cstring(result))));
+	}
 	else
+	{
+		/* null is NULL, regardless */
 		PG_RETURN_NULL();
+	}
 }
 
 /*
@@ -668,7 +994,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 +1148,138 @@ 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;
+	JsonbValue	tv;
+
+	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;
+
+	jbvp = (JsonbValue *) VARDATA(jb);
+
+	for (i = 0; i < npath; i++)
+	{
+		if (have_object)
+		{
+			jbvp = findJsonbValueFromSuperHeaderLen((JsonbSuperHeader) jbvp,
+													JB_FOBJECT, NULL,
+													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((JsonbSuperHeader) jbvp,
+												   JB_FARRAY, 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();
+		if (i == npath - 1)
+			break;
+		if (jbvp->type == jbvBinary)
+		{
+			JsonbIterator *it = JsonbIteratorInit(jbvp->binary.data);
+			int			r;
+
+			r = JsonbIteratorNext(&it, &tv, true);
+			jbvp = (JsonbValue *) 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,
+														(JB_ISEMPTY(res))?
+														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 +1299,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).
@@ -909,22 +1372,177 @@ json_each(PG_FUNCTION_ARGS)
 }
 
 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 +1559,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);
@@ -1047,46 +1661,186 @@ each_object_field_end(void *state, char *fname, bool isnull)
 
 	tuplestore_puttuple(_state->tuple_store, tuple);
 
-	/* clean up and switch back */
-	MemoryContextSwitchTo(old_cxt);
-	MemoryContextReset(_state->tmp_cxt);
-}
+	/* 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};
+
+			/* use the tmp context so we can clean up after each tuple is done */
+			old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
+			if (!as_text)
+			{
+				Jsonb	   *val = JsonbValueToJsonb(&v);
+
+				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);
+				}
+			}
+
+			tuple = heap_form_tuple(ret_tdesc, values, nulls);
 
-static void
-each_array_start(void *state)
-{
-	EachState  *_state = (EachState *) state;
+			tuplestore_puttuple(tuple_store, tuple);
 
-	/* json structure check */
-	if (_state->lex->lex_level == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("cannot deconstruct an array as an object")));
-}
+			/* clean up and switch back */
+			MemoryContextSwitchTo(old_cxt);
+			MemoryContextReset(tmp_cxt);
+		}
+	}
 
-static void
-each_scalar(void *state, char *token, JsonTokenType tokentype)
-{
-	EachState  *_state = (EachState *) state;
+	MemoryContextDelete(tmp_cxt);
 
-	/* json structure check */
-	if (_state->lex->lex_level == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("cannot deconstruct a scalar")));
+	rsi->setResult = tuple_store;
+	rsi->setDesc = ret_tdesc;
 
-	/* supply de-escaped value if required */
-	if (_state->next_scalar)
-		_state->normalized_scalar = token;
+	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 +1854,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,9 +2024,16 @@ 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)
 {
 	return populate_record_worker(fcinfo, true);
@@ -1285,11 +2046,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 +2065,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 +2105,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 +2124,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 +2139,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 (JB_ISEMPTY(jb) && rec)
+			PG_RETURN_POINTER(rec);
 	}
 
 	ncolumns = tupdesc->natts;
@@ -1439,7 +2209,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 +2220,24 @@ 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
+		{
+			if (!JB_ISEMPTY(jb))
+			{
+				char	   *key = NameStr(tupdesc->attrs[i]->attname);
+
+				v = findJsonbValueFromSuperHeaderLen(VARDATA(jb),
+													 JB_FOBJECT, NULL, key,
+													 strlen(key));
+			}
+		}
 
 		/*
 		 * we can't just skip here if the key wasn't found since we might have
@@ -1460,7 +2247,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 +2263,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 +2277,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;
@@ -1656,6 +2469,137 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype)
  * 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;
+
+		/* Ignore dropped columns in datatype */
+		if (tupdesc->attrs[i]->attisdropped)
+		{
+			nulls[i] = true;
+			continue;
+		}
+
+		if (!JB_ISEMPTY(element))
+		{
+			char	   *key = NameStr(tupdesc->attrs[i]->attname);
+
+			v = findJsonbValueFromSuperHeaderLen(VARDATA(element),
+												 JB_FOBJECT, NULL, 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)
 {
 	return populate_recordset_worker(fcinfo, true);
@@ -1671,10 +2615,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 +2628,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 +2663,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 +2672,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 +2685,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 +2695,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 +2723,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 +3046,27 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull)
 		hashentry->val = _state->saved_scalar;
 	}
 }
+
+/*
+ * findJsonbValueFromSuperHeader() wrapper that sets up JsonbValue key
+ * according to our frequent requirements.
+ */
+static JsonbValue *
+findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader, uint32 flags,
+								 uint32 *lowbound, char *key, uint32 keylen)
+{
+	JsonbValue	k;
+
+	if (key == NULL)
+	{
+		k.type = jbvNull;
+	}
+	else
+	{
+		k.type = jbvString;
+		k.string.val = key;
+		k.string.len = keylen;
+	}
+
+	return findJsonbValueFromSuperHeader(sheader, flags, lowbound, &k);
+}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..07c9593 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -627,6 +627,44 @@ numeric_out_sci(Numeric num, int scale)
 }
 
 /*
+ * numeric_normalize() -
+ *
+ *	Output function for numeric data type without trailing zeroes.
+ */
+char *
+numeric_normalize(Numeric num)
+{
+	NumericVar	x;
+	char	   *str;
+	Size		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
  *
  * External format is a sequence of int16's:
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 7c1bc1d..2623113 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -778,6 +778,33 @@ 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
  */
 DATA(insert (	3474   3831 3831 1 s	3893 4000 0 ));
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index e3773e9..04edd98 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,14 @@ 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 (	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 ));
 
 /* 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 3544d0a..e037957 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 6860637..63a40a8 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 e07d6d9..fd19ffa 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 0 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 0 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 229dcb1..775be86 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 4bd23fc..610f0a1 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,79 @@ 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 = 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");
+
 /* 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 4c060f5..92d50bb 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 9982e59..3610fc8 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 baf751e..b5e947b 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 32fb0f7..7a4fbfe 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 0000000..04a74a2
--- /dev/null
+++ b/src/include/utils/jsonb.h
@@ -0,0 +1,316 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_ISEMPTY(jbp_)		(VARSIZE(jbp_) == 0)
+#define JB_ROOT_COUNT(jbp_)		(JB_ISEMPTY(jbp_) ? \
+								 0: ( *(uint32*) VARDATA(jbp_) & JB_CMASK))
+#define JB_ROOT_IS_SCALAR(jbp_) (JB_ISEMPTY(jbp_) ? \
+								 0: ( *(uint32*) VARDATA(jbp_) & JB_FSCALAR))
+#define JB_ROOT_IS_OBJECT(jbp_) (JB_ISEMPTY(jbp_) ? \
+								 0: ( *(uint32*) VARDATA(jbp_) & JB_FOBJECT))
+#define JB_ROOT_IS_ARRAY(jbp_)	(JB_ISEMPTY(jbp_) ? \
+								 0: ( *(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" values, with K, V, or N prepended to the string
+ * to indicate key/element, value, or SQL NULL value.
+ *
+ * Jsonb Keys and elements are treated equivalently when serialized to text
+ * index storage.  One day we may wish to create an opclass that only indexes
+ * keys (or only values), but for now it's always keys and values together, or
+ * just array elements.
+ */
+#define KEYELEMFLAG 'K'
+#define VALFLAG     'V'
+#define NULLFLAG    'N'
+
+#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!
+ *
+ * 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 Jentry.
+ * Either way, the type punning works and the superheader/header metadata is
+ * used to operate on an underlying JsonbValue.
+ *
+ * In a few contexts, when passing a superheader, there may be an assumption
+ * that it really does point to just past vl_len_ in a Jsonb, but that has
+ * nothing to do with layout.  Care has been taken to make the nested layout
+ * consistent (to the extent that it matters) between the least nested level
+ * (Jsonb superheaders), and deeper nesting levels (Jentry headers).
+ */
+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 in a jsonb object.
+ *
+ * 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;			/* May be accessed as superheader */
+}	JEntry;
+
+struct JsonbValue
+{
+	enum
+	{
+		/* Scalar types */
+		jbvNull = 0x0,
+		jbvString,
+		jbvNumeric,
+		jbvBool,
+		/* Composite types */
+		jbvArray = 0x10,
+		jbvObject,
+		/* Binary form of jbvArray/jbvObject/scalar */
+		jbvBinary
+	} type;		/* Influences sort order */
+
+	Size		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		scalar; /* Scalar actually shares representation with
+								 * array */
+		} array;		/* Array container type */
+
+		struct
+		{
+			int			nPairs;
+			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 stored 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 ToJsonbState
+{
+	JsonbValue	v;
+	Size		size;
+	struct ToJsonbState *next;
+} ToJsonbState;
+
+/*
+ * JsonbIterator holds details of the type for each iteration. It also stores a
+ * Jsonb varlena buffer, which can be directly accessed without deserialization
+ * in some contexts.
+ */
+typedef enum
+{
+	jbi_start = 0x0,
+	jbi_key,
+	jbi_value,
+	jbi_elem
+} iterState;
+
+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
+								* (we * 2 for pairs within objects) */
+	bool		isScalar;	   /* Pseudo-array scalar value? */
+	JEntry	   *meta;
+
+	/* Current item in buffer ("nElems-wise" count) */
+	int			i;
+
+	/*
+	 * Data proper.  Note that this points just past end of meta array.  We use
+	 * "meta" metadata (Jentrys) with JBE_OFF() macro to find appropriate
+	 * offsets into this array.
+	 */
+	char	   *dataProper;
+
+	/* Private state */
+	iterState 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);
+/* 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);
+
+/* 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 flags, uint32 i);
+extern JsonbValue *pushJsonbValue(ToJsonbState ** state, int r, JsonbValue *v);
+extern JsonbIterator *JsonbIteratorInit(JsonbSuperHeader buffer);
+extern int JsonbIteratorNext(JsonbIterator **it, JsonbValue *v,
+							 bool skipNested);
+extern Jsonb *JsonbValueToJsonb(JsonbValue *v);
+extern bool deepContains(JsonbIterator ** val, JsonbIterator ** mContained);
+extern JsonbValue *arrayToJsonbSortedArray(ArrayType *a);
+extern void hash_scalar_value(const JsonbValue * v, uint32 * hash_state);
+
+/* jsonb.c support function */
+extern char *JsonbToCString(StringInfo out, char *in, int estimated_len);
+
+#endif   /* __JSONB_H__ */
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index c230e0f..d298718 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 0000000..90bdc6c
--- /dev/null
+++ b/src/test/regress/data/jsonb.data
@@ -0,0 +1,1008 @@
+{"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":["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/jsonb.out b/src/test/regress/expected/jsonb.out
new file mode 100644
index 0000000..18c7028
--- /dev/null
+++ b/src/test/regress/expected/jsonb.out
@@ -0,0 +1,1941 @@
+-- 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"]'),
+('object','{"field1":"val1","field2":"val2","field3":null}');
+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 ->> 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
+(3 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)
+
+-- 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)
+
+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"}' ?& '{}'::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 
+-------
+  1008
+(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)
+
+RESET enable_seqscan;
+SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow;
+ count 
+-------
+  4787
+(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     |     4
+ age       |     2
+(24 rows)
+
+-- sort/hash
+SELECT count(distinct j) FROM testjsonb;
+ count 
+-------
+   890
+(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 
+-------
+   890
+(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 
+-------
+   890
+(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 
+-------
+  1008
+(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 0000000..7cd34db
--- /dev/null
+++ b/src/test/regress/expected/jsonb_1.out
@@ -0,0 +1,1941 @@
+-- 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"]'),
+('object','{"field1":"val1","field2":"val2","field3":null}');
+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 ->> 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
+(3 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)
+
+-- 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)
+
+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"}' ?& '{}'::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 
+-------
+  1008
+(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)
+
+RESET enable_seqscan;
+SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow;
+ count 
+-------
+  4787
+(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     |     4
+ age       |     2
+(24 rows)
+
+-- sort/hash
+SELECT count(distinct j) FROM testjsonb;
+ count 
+-------
+   890
+(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 
+-------
+   890
+(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 
+-------
+   890
+(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 
+-------
+  1008
+(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 5627b4a..bf76501 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 2e3eba8..c62be2a 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 4f1dede..885ca9a 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/jsonb.sql b/src/test/regress/sql/jsonb.sql
new file mode 100644
index 0000000..3910b4a
--- /dev/null
+++ b/src/test/regress/sql/jsonb.sql
@@ -0,0 +1,450 @@
+-- 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"]'),
+('object','{"field1":"val1","field2":"val2","field3":null}');
+
+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 ->> 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}';
+
+-- 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';
+
+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"}' ?& '{}'::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';
+
+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';
