json-datatype-wip-02.diff

application/octet-stream

Filename: json-datatype-wip-02.diff
Type: application/octet-stream
Part: 0
Message: Re: patch: Add JSON datatype to PostgreSQL (GSoC, WIP)

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
contrib/json/expected/array_to_json.out 244 0
contrib/json/expected/condense.out 59 0
contrib/json/expected/init.out 3 0
contrib/json/expected/json_get.out 59 0
contrib/json/expected/json.out 623 0
contrib/json/expected/json_path.out 1049 0
contrib/json/expected/json_set.out 164 0
contrib/json/expected/orig.out 14 0
contrib/json/expected/validate.out 220 0
contrib/json/json.c 1202 0
contrib/json/json.h 220 0
contrib/json/json_io.c 465 0
contrib/json/json_op.c 253 0
contrib/json/jsonpath.c 596 0
contrib/json/jsonpath.h 103 0
contrib/json/json.sql.in 85 0
contrib/json/Makefile 17 0
contrib/json/sql/array_to_json.sql 45 0
contrib/json/sql/condense.sql 14 0
contrib/json/sql/init.sql 10 0
contrib/json/sql/json_get.sql 18 0
contrib/json/sql/json_path.sql 173 0
contrib/json/sql/json_set.sql 31 0
contrib/json/sql/json.sql 159 0
contrib/json/sql/orig.sql 10 0
contrib/json/sql/test_strings.sql 216 0
contrib/json/sql/validate.sql 1 0
contrib/json/uninstall_json.sql 8 0
contrib/json/util.c 351 0
contrib/json/util.h 115 0
doc/src/sgml/contrib.sgml 1 0
doc/src/sgml/filelist.sgml 1 0
doc/src/sgml/json.sgml 177 0
commit 4dec7762583cdbe5ad10ce683e7b16d38f1129f7
Author: Joey Adams <joeyadams3.14159@gmail.com>
Date:   Tue Aug 10 02:20:38 2010 -0400

    Added json contrib module.

diff --git a/contrib/json/Makefile b/contrib/json/Makefile
new file mode 100644
index 0000000..65e8b12
--- /dev/null
+++ b/contrib/json/Makefile
@@ -0,0 +1,17 @@
+MODULE_big = json
+OBJS = json.o jsonpath.o json_io.o json_op.o util.o
+
+DATA_built = json.sql
+DATA = uninstall_json.sql
+REGRESS = init json validate condense orig json_path json_get json_set array_to_json
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/json
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/json/expected/array_to_json.out b/contrib/json/expected/array_to_json.out
new file mode 100644
index 0000000..be408f3
--- /dev/null
+++ b/contrib/json/expected/array_to_json.out
@@ -0,0 +1,244 @@
+SELECT to_json(ARRAY[1,2,3]);
+ to_json 
+---------
+ [1,2,3]
+(1 row)
+
+SELECT to_json(ARRAY[[1],[2],[3]]);
+    to_json    
+---------------
+ [[1],[2],[3]]
+(1 row)
+
+SELECT to_json(ARRAY[[1,2],[2],[3]]);
+ERROR:  multidimensional arrays must have array expressions with matching dimensions
+SELECT to_json(ARRAY[[1,2],[3,4],[5,6]]);
+       to_json       
+---------------------
+ [[1,2],[3,4],[5,6]]
+(1 row)
+
+SELECT to_json(ARRAY[[],[],[]]);
+ERROR:  cannot determine type of empty array
+LINE 1: SELECT to_json(ARRAY[[],[],[]]);
+                             ^
+HINT:  Explicitly cast to the desired type, for example ARRAY[]::integer[].
+SELECT to_json(ARRAY[[],[],[]]::integer[][]);
+ to_json 
+---------
+ []
+(1 row)
+
+SELECT to_json(ARRAY['hello']);
+  to_json  
+-----------
+ ["hello"]
+(1 row)
+
+SELECT to_json(ARRAY['hello', 'world']);
+      to_json      
+-------------------
+ ["hello","world"]
+(1 row)
+
+SELECT to_json(ARRAY['hello', 'world', 5]);
+ERROR:  invalid input syntax for integer: "hello"
+LINE 1: SELECT to_json(ARRAY['hello', 'world', 5]);
+                             ^
+SELECT to_json(ARRAY['hello', 'world', 5]::text[]);
+        to_json        
+-----------------------
+ ["hello","world","5"]
+(1 row)
+
+SELECT to_json(ARRAY[ARRAY['hello', 'world', 5]]::text[][]);
+         to_json         
+-------------------------
+ [["hello","world","5"]]
+(1 row)
+
+SELECT to_json(ARRAY[]);
+ERROR:  cannot determine type of empty array
+LINE 1: SELECT to_json(ARRAY[]);
+                       ^
+HINT:  Explicitly cast to the desired type, for example ARRAY[]::integer[].
+SELECT to_json(ARRAY[]::text[]);
+ to_json 
+---------
+ []
+(1 row)
+
+SELECT to_json(ARRAY['hello', 'world', null]);
+        to_json         
+------------------------
+ ["hello","world",null]
+(1 row)
+
+SELECT to_json(ARRAY['hello', 'world', null, 5]);
+ERROR:  invalid input syntax for integer: "hello"
+LINE 1: SELECT to_json(ARRAY['hello', 'world', null, 5]);
+                             ^
+SELECT to_json(ARRAY['hello', 'world', null, '5']);
+          to_json           
+----------------------------
+ ["hello","world",null,"5"]
+(1 row)
+
+SELECT to_json(ARRAY[null]);
+ to_json 
+---------
+ [null]
+(1 row)
+
+SELECT to_json(ARRAY[null, null]);
+   to_json   
+-------------
+ [null,null]
+(1 row)
+
+SELECT to_json(ARRAY[[null],[null],[null]]);
+        to_json         
+------------------------
+ [[null],[null],[null]]
+(1 row)
+
+SELECT to_json(ARRAY[[null,null],[null,null],[null,null]]);
+                to_json                
+---------------------------------------
+ [[null,null],[null,null],[null,null]]
+(1 row)
+
+SELECT to_json(ARRAY[[[null,null]],[[null,null]],[[null,null]]]);
+                   to_json                   
+---------------------------------------------
+ [[[null,null]],[[null,null]],[[null,null]]]
+(1 row)
+
+SELECT to_json(ARRAY[[[null,null],[0,1]],[[null,null],[2,3]],[[null,null],[4,5]]]);
+ERROR:  ARRAY could not convert type integer[] to text[]
+LINE 1: SELECT to_json(ARRAY[[[null,null],[0,1]],[[null,null],[2,3]]...
+                                          ^
+SELECT to_json(ARRAY[[[null,null],[0,1]],[[null,null],[2,3]],[[null,null],[4,5]]]::int[][][]);
+                            to_json                            
+---------------------------------------------------------------
+ [[[null,null],[0,1]],[[null,null],[2,3]],[[null,null],[4,5]]]
+(1 row)
+
+SELECT to_json(array[1,2,3,4,5]::int2vector);
+   to_json   
+-------------
+ [1,2,3,4,5]
+(1 row)
+
+SELECT to_json(array[1,2,3,4,5]::oidvector);
+   to_json   
+-------------
+ [1,2,3,4,5]
+(1 row)
+
+SELECT to_json(array[[1,2],[3,4],5]::oidvector);
+ERROR:  syntax error at or near "5"
+LINE 1: SELECT to_json(array[[1,2],[3,4],5]::oidvector);
+                                         ^
+SELECT to_json(array[[1,2],[3,4],[5]]::oidvector);
+ERROR:  multidimensional arrays must have array expressions with matching dimensions
+SELECT to_json(array[[1,2],[3,4],[5,5]]::oidvector);
+       to_json       
+---------------------
+ [[1,2],[3,4],[5,5]]
+(1 row)
+
+SELECT to_json(array[[1,2],[3,4],[5,6]]::oidvector);
+       to_json       
+---------------------
+ [[1,2],[3,4],[5,6]]
+(1 row)
+
+SELECT to_json(array[[1,2],[3,4],[5,6]]::int2vector);
+       to_json       
+---------------------
+ [[1,2],[3,4],[5,6]]
+(1 row)
+
+SELECT to_json(array[[1,2],[3,4],[5,6000]]::int2vector);
+        to_json         
+------------------------
+ [[1,2],[3,4],[5,6000]]
+(1 row)
+
+SELECT to_json(array[[1,2],[3,4],[5,60000]]::int2vector);
+ERROR:  smallint out of range
+SELECT to_json(array[[1,2],[3,4],[5,null]]::int2vector);
+        to_json         
+------------------------
+ [[1,2],[3,4],[5,null]]
+(1 row)
+
+SELECT to_json(array[[1,2],[null,4],[5,null]]::int2vector);
+          to_json          
+---------------------------
+ [[1,2],[null,4],[5,null]]
+(1 row)
+
+SELECT to_json(array[[1,2],[null,4],[5,null]]::oidvector);
+          to_json          
+---------------------------
+ [[1,2],[null,4],[5,null]]
+(1 row)
+
+SELECT to_json(array[[true,false],[false,true]]::bool[][]);
+           to_json           
+-----------------------------
+ [[true,false],[false,true]]
+(1 row)
+
+SELECT to_json(array[[true,false],[false,true],[null,null]]::bool[][]);
+                 to_json                 
+-----------------------------------------
+ [[true,false],[false,true],[null,null]]
+(1 row)
+
+SELECT to_json('{1,2,3}');
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT to_json('{1,2,3}'::text); -- should form "{1,2,3}", not build an array
+  to_json  
+-----------
+ "{1,2,3}"
+(1 row)
+
+SELECT to_json('{1,2,3}'::int[]);
+ to_json 
+---------
+ [1,2,3]
+(1 row)
+
+SELECT to_json('{one,two,three}'::text[]);
+        to_json        
+-----------------------
+ ["one","two","three"]
+(1 row)
+
+SELECT to_json('{one,two,three,null}'::text[]);
+          to_json           
+----------------------------
+ ["one","two","three",null]
+(1 row)
+
+SELECT to_json('{one,two,three,null,five}'::text[]);
+              to_json              
+-----------------------------------
+ ["one","two","three",null,"five"]
+(1 row)
+
+SELECT to_json('{null}'::text[]);
+ to_json 
+---------
+ [null]
+(1 row)
+
+SELECT to_json('{{null}}'::text[][]);
+ to_json  
+----------
+ [[null]]
+(1 row)
+
diff --git a/contrib/json/expected/condense.out b/contrib/json/expected/condense.out
new file mode 100644
index 0000000..8ff95f6
--- /dev/null
+++ b/contrib/json/expected/condense.out
@@ -0,0 +1,59 @@
+SELECT json_condense('"hello"');
+ json_condense 
+---------------
+ "hello"
+(1 row)
+
+SELECT json_condense($$"hello\u266Bworld"$$);
+ json_condense 
+---------------
+ "hello♫world"
+(1 row)
+
+SELECT json_condense($$"hello\u266bworld"$$);
+ json_condense 
+---------------
+ "hello♫world"
+(1 row)
+
+SELECT json_condense($$"hello♫world"$$);
+ json_condense 
+---------------
+ "hello♫world"
+(1 row)
+
+SELECT json_condense($$      "hello world"    $$);
+ json_condense 
+---------------
+ "hello world"
+(1 row)
+
+SELECT json_condense($$    {  "hello" : "world"}    $$);
+   json_condense   
+-------------------
+ {"hello":"world"}
+(1 row)
+
+SELECT json_condense($$    {  "hello" : "world", "bye": 0.0001  }    $$);
+         json_condense          
+--------------------------------
+ {"hello":"world","bye":0.0001}
+(1 row)
+
+SELECT json_condense($$    {  "hello" : "world",
+								"bye": 0.0000001
+}    $$);
+           json_condense           
+-----------------------------------
+ {"hello":"world","bye":0.0000001}
+(1 row)
+
+SELECT json_condense($$    {  "hello" : "world"
+,
+"bye"
+: [-0.1234e1, 12345e0]		}    $$);
+                json_condense                
+---------------------------------------------
+ {"hello":"world","bye":[-0.1234e1,12345e0]}
+(1 row)
+
diff --git a/contrib/json/expected/init.out b/contrib/json/expected/init.out
new file mode 100644
index 0000000..43f11fa
--- /dev/null
+++ b/contrib/json/expected/init.out
@@ -0,0 +1,3 @@
+SET client_min_messages = warning;
+\set ECHO none
+RESET client_min_messages;
diff --git a/contrib/json/expected/json.out b/contrib/json/expected/json.out
new file mode 100644
index 0000000..63e7633
--- /dev/null
+++ b/contrib/json/expected/json.out
@@ -0,0 +1,623 @@
+SELECT '[]'::JSON;
+ json 
+------
+ []
+(1 row)
+
+SELECT '['::JSON;
+ERROR:  could not parse JSON value
+LINE 1: SELECT '['::JSON;
+               ^
+SELECT '[1,2,3]'::JSON;
+  json   
+---------
+ [1,2,3]
+(1 row)
+
+SELECT '[1,2,3]'::JSON::TEXT;
+  text   
+---------
+ [1,2,3]
+(1 row)
+
+SELECT '[1,2,3  ]'::JSON;
+   json    
+-----------
+ [1,2,3  ]
+(1 row)
+
+SELECT '[1,2,3  ,4]'::JSON;
+    json     
+-------------
+ [1,2,3  ,4]
+(1 row)
+
+SELECT '[1,2,3  ,4.0]'::JSON;
+     json      
+---------------
+ [1,2,3  ,4.0]
+(1 row)
+
+SELECT '[1,2,3  ,4]'::JSON;
+    json     
+-------------
+ [1,2,3  ,4]
+(1 row)
+
+SELECT 'true'::JSON;
+ json 
+------
+ true
+(1 row)
+
+SELECT 'true'::TEXT::JSON;
+ json 
+------
+ true
+(1 row)
+
+SELECT 'false'::JSON;
+ json  
+-------
+ false
+(1 row)
+
+SELECT 'null'::JSON;
+ json 
+------
+ null
+(1 row)
+
+SELECT '1.1'::JSON;
+ json 
+------
+ 1.1
+(1 row)
+
+SELECT '"string"'::JSON;
+   json   
+----------
+ "string"
+(1 row)
+
+SELECT '{"key1":"value1", "key2":"value2"}'::JSON;
+                json                
+------------------------------------
+ {"key1":"value1", "key2":"value2"}
+(1 row)
+
+SELECT '{"key1":"value1", "key2":"value2"}'::JSON;
+                json                
+------------------------------------
+ {"key1":"value1", "key2":"value2"}
+(1 row)
+
+SELECT 15::JSON;
+ json 
+------
+ 15
+(1 row)
+
+SELECT json_type('[]');
+ json_type 
+-----------
+ array
+(1 row)
+
+SELECT json_type('{}');
+ json_type 
+-----------
+ object
+(1 row)
+
+SELECT json_type('true');
+ json_type 
+-----------
+ bool
+(1 row)
+
+SELECT json_type('false');
+ json_type 
+-----------
+ bool
+(1 row)
+
+SELECT json_type('null');
+ json_type 
+-----------
+ null
+(1 row)
+
+CREATE TABLE testjson (j JSON);
+INSERT INTO testjson VALUES ('[1,2,3,4]');
+INSERT INTO testjson VALUES ('{"key":"value"}');
+INSERT INTO testjson VALUES ('{"key":"value"');
+ERROR:  could not parse JSON value
+LINE 1: INSERT INTO testjson VALUES ('{"key":"value"');
+                                     ^
+INSERT INTO testjson VALUES ('');
+ERROR:  could not parse JSON value
+LINE 1: INSERT INTO testjson VALUES ('');
+                                     ^
+INSERT INTO testjson VALUES ('""');
+INSERT INTO testjson VALUES ('true');
+INSERT INTO testjson VALUES ('false');
+INSERT INTO testjson VALUES ('null');
+INSERT INTO testjson VALUES ('[]');
+INSERT INTO testjson VALUES ('{}');
+SELECT * FROM testjson;
+        j        
+-----------------
+ [1,2,3,4]
+ {"key":"value"}
+ ""
+ true
+ false
+ null
+ []
+ {}
+(8 rows)
+
+SELECT json_type(j) FROM testjson;
+ json_type 
+-----------
+ array
+ object
+ string
+ bool
+ bool
+ null
+ array
+ object
+(8 rows)
+
+-- to_json: null
+SELECT to_json(NULL);
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT to_json(NULL::INT);
+ to_json 
+---------
+ null
+(1 row)
+
+SELECT to_json(NULL::INT[]);
+ to_json 
+---------
+ null
+(1 row)
+
+SELECT to_json(NULL::VOID);
+ERROR:  to_json cannot convert void to JSON
+SELECT to_json(NULL::MONEY);
+ERROR:  to_json cannot convert money to JSON
+SELECT to_json('null'::text); -- should yield '"null"', not 'null'
+ to_json 
+---------
+ "null"
+(1 row)
+
+SELECT json_type(to_json(NULL));
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT json_type(to_json(NULL::INT));
+ json_type 
+-----------
+ null
+(1 row)
+
+SELECT json_type(to_json(NULL::VOID));
+ERROR:  to_json cannot convert void to JSON
+SELECT json_type(to_json(NULL::MONEY));
+ERROR:  to_json cannot convert money to JSON
+-- to_json: string
+SELECT to_json('');
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT json_type(to_json(''));
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT to_json('string');
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT json_type(to_json('string'));
+ERROR:  could not determine polymorphic type because input has type "unknown"
+SELECT to_json('string'::VARCHAR);
+ to_json  
+----------
+ "string"
+(1 row)
+
+SELECT json_type(to_json('string'::VARCHAR));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('string'::VARCHAR(3));
+ to_json 
+---------
+ "str"
+(1 row)
+
+SELECT json_type(to_json('string'::VARCHAR(3)));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('{1,2,3}'::TEXT);
+  to_json  
+-----------
+ "{1,2,3}"
+(1 row)
+
+SELECT json_type(to_json('{1,2,3}'::TEXT));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('"doubly-encoded"'::JSON);
+       to_json        
+----------------------
+ "\"doubly-encoded\""
+(1 row)
+
+SELECT json_type(to_json('"doubly-encoded"'::JSON));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('"nested quotes"'::TEXT);
+       to_json       
+---------------------
+ "\"nested quotes\""
+(1 row)
+
+SELECT json_type(to_json('"nested quotes"'::TEXT));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('"nested quotes"'::TEXT)::TEXT::JSON;
+       to_json       
+---------------------
+ "\"nested quotes\""
+(1 row)
+
+SELECT json_type(to_json('"nested quotes"'::TEXT)::TEXT::JSON);
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('h'::CHAR);
+ to_json 
+---------
+ "h"
+(1 row)
+
+SELECT json_type(to_json('h'::CHAR));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('hello world'::CHAR);
+ to_json 
+---------
+ "h"
+(1 row)
+
+SELECT json_type(to_json('hello world'::CHAR));
+ json_type 
+-----------
+ string
+(1 row)
+
+SELECT to_json('hello world!'::CHAR(11));
+    to_json    
+---------------
+ "hello world"
+(1 row)
+
+SELECT json_type(to_json('hello world!'::CHAR(11)));
+ json_type 
+-----------
+ string
+(1 row)
+
+-- to_json: number
+SELECT to_json(12345);
+ to_json 
+---------
+ 12345
+(1 row)
+
+SELECT to_json(12345.678);
+  to_json  
+-----------
+ 12345.678
+(1 row)
+
+SELECT json_type(to_json(12345));
+ json_type 
+-----------
+ number
+(1 row)
+
+SELECT json_type(to_json(12345.678));
+ json_type 
+-----------
+ number
+(1 row)
+
+SELECT to_json(+1.23e100::FLOAT);
+  to_json  
+-----------
+ 1.23e+100
+(1 row)
+
+SELECT to_json('+1.23e100'::FLOAT);
+  to_json  
+-----------
+ 1.23e+100
+(1 row)
+
+SELECT to_json(123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678901234567890);
+                                                          to_json                                                          
+---------------------------------------------------------------------------------------------------------------------------
+ 123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678901234567890
+(1 row)
+
+SELECT json_type(to_json(123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678901234567890));
+ json_type 
+-----------
+ number
+(1 row)
+
+SELECT to_json('100'::MONEY);
+ERROR:  to_json cannot convert money to JSON
+-- to_json: bool
+SELECT to_json(TRUE);
+ to_json 
+---------
+ true
+(1 row)
+
+SELECT to_json(FALSE);
+ to_json 
+---------
+ false
+(1 row)
+
+SELECT to_json(1=1);
+ to_json 
+---------
+ true
+(1 row)
+
+SELECT to_json(1=2);
+ to_json 
+---------
+ false
+(1 row)
+
+SELECT json_type(to_json(TRUE));
+ json_type 
+-----------
+ bool
+(1 row)
+
+SELECT json_type(to_json(FALSE));
+ json_type 
+-----------
+ bool
+(1 row)
+
+SELECT json_type(to_json(1=1));
+ json_type 
+-----------
+ bool
+(1 row)
+
+SELECT json_type(to_json(1=2));
+ json_type 
+-----------
+ bool
+(1 row)
+
+SELECT to_json(TRUE::TEXT)::TEXT = '"' || TRUE || '"';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT to_json(FALSE::TEXT)::TEXT = '"' || FALSE || '"';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- to_json: array
+SELECT to_json(ARRAY[1,2,3]);
+ to_json 
+---------
+ [1,2,3]
+(1 row)
+
+-- more tests are in array_to_json.sql
+-- to_json: invalid types
+SELECT to_json(row(1,2));
+ERROR:  to_json cannot convert record to JSON
+SELECT to_json('127.0.0.1'::INET);
+ERROR:  to_json cannot convert inet to JSON
+-- from_json: null
+SELECT from_json(null);
+ from_json 
+-----------
+ 
+(1 row)
+
+SELECT from_json(NULL::TEXT);
+ERROR:  function from_json(text) does not exist
+LINE 1: SELECT from_json(NULL::TEXT);
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT from_json(NULL::JSON);
+ from_json 
+-----------
+ 
+(1 row)
+
+-- from_json: string
+SELECT from_json('"valid string"');
+  from_json   
+--------------
+ valid string
+(1 row)
+
+SELECT from_json($$ "hello\nworld" $$);
+ from_json 
+-----------
+ hello    +
+ world
+(1 row)
+
+SELECT from_json($$ "hello\u0000world" $$);
+ERROR:  could not convert JSON-encoded string to TEXT: contains \u0000
+DETAIL:  the string was:  "hello\u0000world" 
+-- from_json: number
+SELECT from_json('123');
+ from_json 
+-----------
+ 123
+(1 row)
+
+SELECT from_json('123')::INT;
+ from_json 
+-----------
+       123
+(1 row)
+
+SELECT from_json('123.456')::INT;
+ERROR:  invalid input syntax for integer: "123.456"
+SELECT from_json('123.456')::FLOAT;
+ from_json 
+-----------
+   123.456
+(1 row)
+
+SELECT from_json('123e-38');
+ from_json 
+-----------
+ 123e-38
+(1 row)
+
+SELECT from_json('123e-38')::FLOAT;
+ from_json 
+-----------
+  1.23e-36
+(1 row)
+
+SELECT from_json('1.23e-38')::FLOAT;
+ from_json 
+-----------
+  1.23e-38
+(1 row)
+
+SELECT from_json('1.23e-38');
+ from_json 
+-----------
+ 1.23e-38
+(1 row)
+
+SELECT from_json('1.23e-38')::NUMERIC;
+                 from_json                  
+--------------------------------------------
+ 0.0000000000000000000000000000000000000123
+(1 row)
+
+-- from_json: bool
+SELECT from_json('true')::JSON;
+ from_json 
+-----------
+ true
+(1 row)
+
+SELECT from_json('true');
+ from_json 
+-----------
+ true
+(1 row)
+
+SELECT from_json('true')::BOOLEAN;
+ from_json 
+-----------
+ t
+(1 row)
+
+SELECT from_json('true')::BOOLEAN::JSON;
+ERROR:  could not parse JSON value
+SELECT from_json('true')::BOOLEAN::TEXT::JSON;
+ from_json 
+-----------
+ true
+(1 row)
+
+SELECT from_json('false')::BOOLEAN::TEXT::JSON;
+ from_json 
+-----------
+ false
+(1 row)
+
+SELECT from_json('false');
+ from_json 
+-----------
+ false
+(1 row)
+
+SELECT from_json('f');
+ERROR:  could not parse JSON value
+LINE 1: SELECT from_json('f');
+                         ^
+SELECT from_json('t');
+ERROR:  could not parse JSON value
+LINE 1: SELECT from_json('t');
+                         ^
+SELECT from_json('f'::BOOLEAN::TEXT);
+ERROR:  function from_json(text) does not exist
+LINE 1: SELECT from_json('f'::BOOLEAN::TEXT);
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT from_json('f'::BOOLEAN::TEXT::JSON);
+ from_json 
+-----------
+ false
+(1 row)
+
+SELECT from_json('t'::BOOLEAN::TEXT::JSON);
+ from_json 
+-----------
+ true
+(1 row)
+
+-- from_json: object
+SELECT from_json('{"key": "value"}');
+ERROR:  from_json cannot be applied to JSON objects
+-- from_json: array
+SELECT from_json('[1,2,3]');
+ERROR:  from_json cannot be applied to JSON arrays
+-- from_json: invalid
+SELECT from_json('invalid');
+ERROR:  could not parse JSON value
+LINE 1: SELECT from_json('invalid');
+                         ^
+CREATE TABLE sample_query_text (json JSON);
+INSERT INTO sample_query_text VALUES ($$
+"SELECT pg_catalog.quote_ident(c.relname) FROM pg_catalog.pg_class c WHERE c.relkind IN ('i') AND substring(pg_catalog.quote_ident(c.relname),1,0)='' AND pg_catalog.pg_table_is_visible(c.oid) AND c.relnamespace <> (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')\nUNION\nSELECT pg_catalog.quote_ident(n.nspname) || '.' FROM pg_catalog.pg_namespace n WHERE substring(pg_catalog.quote_ident(n.nspname) || '.',1,0)='' AND (SELECT pg_catalog.count(*) FROM pg_catalog.pg_namespace WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,0) = substring('',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) > 1\nUNION\nSELECT pg_catalog.quote_ident(n.nspname) || '.' || pg_catalog.quote_ident(c.relname) FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE c.relnamespace = n.oid AND c.relkind IN ('i') AND substring(pg_catalog.quote_ident(n.nspname) || '.' || pg_catalog.quote_ident(c.relname),1,0)='' AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,0) = substring('',1,pg_catalog.length(pg_catalog.quote_ident(n.nspname))+1) AND (SELECT pg_catalog.count(*) FROM pg_catalog.pg_namespace WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,0) = substring('',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n UNION SELECT 'ON' UNION SELECT 'CONCURRENTLY'\nLIMIT 1000"
+$$);
+SELECT md5(from_json(json)) FROM sample_query_text;
+               md5                
+----------------------------------
+ 1c0d28af72da21149acbdf305c197141
+(1 row)
+
diff --git a/contrib/json/expected/json_get.out b/contrib/json/expected/json_get.out
new file mode 100644
index 0000000..1fc6344
--- /dev/null
+++ b/contrib/json/expected/json_get.out
@@ -0,0 +1,59 @@
+SELECT json_get('{"key": "value", "key2": "value2"}', 'key');
+ json_get 
+----------
+ "value"
+(1 row)
+
+SELECT json_get('{"key": "value", "key": "value2"}', 'key');
+ERROR:  JSONPath expression matched multiple results
+/* These each return no result because 0 is a number
+   (indicating a numeric subscript),
+   not a string (indicating an object subscript). */
+SELECT json_get('{"0": "value", "key": "value"}', '0');
+ json_get 
+----------
+ 
+(1 row)
+
+SELECT json_get('{"0": "value", "0": "value"}', '[0]');
+ json_get 
+----------
+ 
+(1 row)
+
+SELECT json_get('{"0": "value", "0": "value"}', '.0');
+ json_get 
+----------
+ 
+(1 row)
+
+SELECT json_get('{"0": "value", "1": "value"}', '["0"]');
+ json_get 
+----------
+ "value"
+(1 row)
+
+SELECT json_get('{"0": "value", "0": "value"}', '["0"]');
+ERROR:  JSONPath expression matched multiple results
+SELECT json_get('[0,1,2,3]', '0');
+ json_get 
+----------
+ 0
+(1 row)
+
+SELECT json_get('[0,1,2,3]', '"0"');
+ json_get 
+----------
+ 
+(1 row)
+
+SELECT json_get('[0,1,2,3]', '*');
+ERROR:  JSONPath expression matched multiple results
+SELECT json_get('[0]', '*');
+ json_get 
+----------
+ 0
+(1 row)
+
+SELECT json_get('[[0]]', '..*');
+ERROR:  JSONPath expression matched multiple results
diff --git a/contrib/json/expected/json_path.out b/contrib/json/expected/json_path.out
new file mode 100644
index 0000000..22c0f10
--- /dev/null
+++ b/contrib/json/expected/json_path.out
@@ -0,0 +1,1049 @@
+SELECT parse_json_path('..');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path('..*');
+ parse_json_path 
+-----------------
+ $..[*]
+(1 row)
+
+SELECT parse_json_path('.*');
+ parse_json_path 
+-----------------
+ $[*]
+(1 row)
+
+SELECT parse_json_path('');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path('$.*');
+ parse_json_path 
+-----------------
+ $[*]
+(1 row)
+
+SELECT parse_json_path('$');
+ parse_json_path 
+-----------------
+ $
+(1 row)
+
+SELECT parse_json_path('$*');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path('*');
+ parse_json_path 
+-----------------
+ $[*]
+(1 row)
+
+SELECT parse_json_path($$ .. [ 025  ] $$);
+ parse_json_path 
+-----------------
+ $..[25]
+(1 row)
+
+SELECT parse_json_path($$ $ . 0 . 3 $$);
+ parse_json_path 
+-----------------
+ $[0][3]
+(1 row)
+
+SELECT parse_json_path($$ $ . 0_3 $$);
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$ $ . 0.3 $$);
+ parse_json_path 
+-----------------
+ $[0][3]
+(1 row)
+
+SELECT parse_json_path($$ .. [ 031  ] $$);
+ parse_json_path 
+-----------------
+ $..[31]
+(1 row)
+
+SELECT parse_json_path($$ $ . 0 . 3 . hello $$);
+ parse_json_path  
+------------------
+ $[0][3]["hello"]
+(1 row)
+
+SELECT parse_json_path($$ $ . 0 . 3 . hello . "back\\slash" $$);
+         parse_json_path         
+---------------------------------
+ $[0][3]["hello"]["back\\slash"]
+(1 row)
+
+SELECT parse_json_path($$ $ . 0 . 3 . hello . ["back\\slash"] $$);
+         parse_json_path         
+---------------------------------
+ $[0][3]["hello"]["back\\slash"]
+(1 row)
+
+SELECT parse_json_path($$ $ . 0 . 3 . hello . back\slash $$);
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$ $ . 0 . 3 . hello . backslash $$);
+        parse_json_path        
+-------------------------------
+ $[0][3]["hello"]["backslash"]
+(1 row)
+
+SELECT parse_json_path($$ .. [ 0x31  ] $$);
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$ [ 0x31  ] $$);
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$ _3 $$);
+ parse_json_path 
+-----------------
+ $["_3"]
+(1 row)
+
+SELECT parse_json_path($$ _3_ $$);
+ parse_json_path 
+-----------------
+ $["_3_"]
+(1 row)
+
+SELECT parse_json_path($$ [ _3 ] $$);
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$ $ _3 $$);
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$ $ . _3 $$);
+ parse_json_path 
+-----------------
+ $["_3"]
+(1 row)
+
+SELECT parse_json_path('..["5"]');
+ parse_json_path 
+-----------------
+ $..["5"]
+(1 row)
+
+SELECT parse_json_path('..[5]');
+ parse_json_path 
+-----------------
+ $..[5]
+(1 row)
+
+SELECT parse_json_path('..5');
+ parse_json_path 
+-----------------
+ $..[5]
+(1 row)
+
+SELECT parse_json_path($$ .. [ -5  ] $$);
+ parse_json_path 
+-----------------
+ $..[-5]
+(1 row)
+
+SELECT parse_json_path($$ .. [ "5"  ] $$);
+ parse_json_path 
+-----------------
+ $..["5"]
+(1 row)
+
+SELECT parse_json_path($$ .. [ +5  ] $$);
+ parse_json_path 
+-----------------
+ $..[5]
+(1 row)
+
+SELECT parse_json_path($$ .. [ 5  ] $$);
+ parse_json_path 
+-----------------
+ $..[5]
+(1 row)
+
+SELECT parse_json_path($$ .. ["5"] $$);
+ parse_json_path 
+-----------------
+ $..["5"]
+(1 row)
+
+SELECT parse_json_path($$ ..["5"] $$);
+ parse_json_path 
+-----------------
+ $..["5"]
+(1 row)
+
+SELECT parse_json_path($$ [ "5" ] $$);
+ parse_json_path 
+-----------------
+ $["5"]
+(1 row)
+
+SELECT parse_json_path($$ [ 5 ] $$);
+ parse_json_path 
+-----------------
+ $[5]
+(1 row)
+
+SELECT parse_json_path('."hello"');
+ parse_json_path 
+-----------------
+ $["hello"]
+(1 row)
+
+SELECT parse_json_path('.hello');
+ parse_json_path 
+-----------------
+ $["hello"]
+(1 row)
+
+SELECT parse_json_path('...["hello world"]');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path('..["hello world"]');
+  parse_json_path   
+--------------------
+ $..["hello world"]
+(1 row)
+
+SELECT parse_json_path('."hello world"');
+ parse_json_path  
+------------------
+ $["hello world"]
+(1 row)
+
+SELECT parse_json_path('.["hello world"]');
+ parse_json_path  
+------------------
+ $["hello world"]
+(1 row)
+
+SELECT parse_json_path('.hello world');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path('..["hello world"]["5"]');
+     parse_json_path     
+-------------------------
+ $..["hello world"]["5"]
+(1 row)
+
+SELECT parse_json_path('..["hello world"][5]');
+    parse_json_path    
+-----------------------
+ $..["hello world"][5]
+(1 row)
+
+SELECT parse_json_path('..["hello world"][5]..[3]..*.[*]');
+          parse_json_path           
+------------------------------------
+ $..["hello world"][5]..[3]..[*][*]
+(1 row)
+
+SELECT parse_json_path('..["hello world"][5]..[3]..*');
+         parse_json_path         
+---------------------------------
+ $..["hello world"][5]..[3]..[*]
+(1 row)
+
+SELECT parse_json_path('..["hello world"][5]..[3]');
+      parse_json_path       
+----------------------------
+ $..["hello world"][5]..[3]
+(1 row)
+
+SELECT parse_json_path('..["hello world"][5]..[3]*');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT parse_json_path($$
+	.. [ '"hello"\\"world"' ]
+	[ 5 ] .. [3] . * [ * ]
+$$);
+              parse_json_path              
+-------------------------------------------
+ $..["\"hello\"\\\"world\""][5]..[3][*][*]
+(1 row)
+
+select parse_json_path('$char');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+select parse_json_path('$.char');
+ parse_json_path 
+-----------------
+ $["char"]
+(1 row)
+
+select parse_json_path('$.char()');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+select parse_json_path('$.char(5)');
+ parse_json_path 
+-----------------
+ $.char(5)
+(1 row)
+
+select parse_json_path('$.char(  -1  )');
+ parse_json_path 
+-----------------
+ $.char(-1)
+(1 row)
+
+select parse_json_path('$.char(  -1  )  . char(0)');
+  parse_json_path   
+--------------------
+ $.char(-1).char(0)
+(1 row)
+
+select parse_json_path('$.char(  -1  )  . char  ( 0 )  ');
+  parse_json_path   
+--------------------
+ $.char(-1).char(0)
+(1 row)
+
+select parse_json_path('$.char(  +1  )  . char  ( 0 )  ');
+  parse_json_path  
+-------------------
+ $.char(1).char(0)
+(1 row)
+
+select parse_json_path('$ . char(  +1  )  . char  ( 0 )  ');
+  parse_json_path  
+-------------------
+ $.char(1).char(0)
+(1 row)
+
+select parse_json_path('$ .. char(  +1  )  . char  ( 0 )  ');
+  parse_json_path   
+--------------------
+ $..char(1).char(0)
+(1 row)
+
+select parse_json_path('$ .. char(  +1  )  .. char  ( 0 )  ');
+   parse_json_path   
+---------------------
+ $..char(1)..char(0)
+(1 row)
+
+select parse_json_path('$ . char(  +1  )  .. char  ( 0 )  ');
+  parse_json_path   
+--------------------
+ $.char(1)..char(0)
+(1 row)
+
+select parse_json_path('$ . char(  +1  )  char  ( 0 )  ');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+select parse_json_path('$ char(  +1  )  char  ( 0 )  ');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+select parse_json_path('char(  +1  )  . char  ( 0 )  ');
+  parse_json_path  
+-------------------
+ $.char(1).char(0)
+(1 row)
+
+select parse_json_path('.char(  +1  )  . char  ( 0 )  ');
+  parse_json_path  
+-------------------
+ $.char(1).char(0)
+(1 row)
+
+select parse_json_path('..char(  +1  )  . char  ( 0 )  ');
+  parse_json_path   
+--------------------
+ $..char(1).char(0)
+(1 row)
+
+select parse_json_path('...char(  +1  )  . char  ( 0 )  ');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+select parse_json_path('[char(5)]');
+ parse_json_path 
+-----------------
+ 
+(1 row)
+
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(0)');
+ json_path 
+-----------
+ "⁰"
+(1 row)
+
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(1)');
+ json_path 
+-----------
+ "¹"
+(1 row)
+
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(2)');
+ json_path 
+-----------
+ "\u0000"
+(1 row)
+
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(3)');
+ json_path 
+-----------
+ "³"
+(1 row)
+
+SELECT json_path('"0123"', 'char(-1)');
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path('"0123"', 'char(0)');
+ json_path 
+-----------
+ "0"
+(1 row)
+
+SELECT json_path('"0123"', 'char(1)');
+ json_path 
+-----------
+ "1"
+(1 row)
+
+SELECT json_path('"0123"', 'char(2)');
+ json_path 
+-----------
+ "2"
+(1 row)
+
+SELECT json_path('"0123"', 'char(3)');
+ json_path 
+-----------
+ "3"
+(1 row)
+
+SELECT json_path('"0123"', 'char(4)');
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path('"⁰¹²³"', 'char(0)');
+ json_path 
+-----------
+ "⁰"
+(1 row)
+
+SELECT json_path('"⁰¹²³"', 'char(1)');
+ json_path 
+-----------
+ "¹"
+(1 row)
+
+SELECT json_path('"⁰¹²³"', 'char(2)');
+ json_path 
+-----------
+ "²"
+(1 row)
+
+SELECT json_path('"⁰¹²³"', 'char(3)');
+ json_path 
+-----------
+ "³"
+(1 row)
+
+SELECT json_path('[1,2,3]', '$');
+ json_path 
+-----------
+ [1,2,3]
+(1 row)
+
+SELECT json_path('[1,2,3]', '$.+1');
+ json_path 
+-----------
+ 2
+(1 row)
+
+SELECT json_path('[1,2,3]', '$.-1');
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path('[1,2,3]', '$.0');
+ json_path 
+-----------
+ 1
+(1 row)
+
+SELECT json_path('[1,2,3]', '$.1');
+ json_path 
+-----------
+ 2
+(1 row)
+
+SELECT json_path('[1,2,3]', '$.2');
+ json_path 
+-----------
+ 3
+(1 row)
+
+SELECT json_path('[1,2,3]', '$.3');
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path('[1,2,3]', '*');
+ json_path 
+-----------
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT json_path('[1,2,3]', '[0]');
+ json_path 
+-----------
+ 1
+(1 row)
+
+SELECT json_path('[1,2,3]', '[1]');
+ json_path 
+-----------
+ 2
+(1 row)
+
+SELECT json_path('[1,2,3]', '[2]');
+ json_path 
+-----------
+ 3
+(1 row)
+
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', $$['2']$$);
+ json_path 
+-----------
+ "two"
+(1 row)
+
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '["0"]');
+ json_path 
+-----------
+ "zero"
+(1 row)
+
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '["1"]');
+ json_path 
+-----------
+ "one"
+(1 row)
+
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '["2"]');
+ json_path 
+-----------
+ "two"
+(1 row)
+
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '[0]');
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '[1]');
+ json_path 
+-----------
+(0 rows)
+
+-- Multiple JSONPath results are listed in BFS order
+-- (just because it's easier to implement efficiently than DFS)
+SELECT json_path('[0,1,[2, [3,4 , 5],6,[7,8],9],10]', '$..*');
+        json_path         
+--------------------------
+ 0
+ 1
+ [2, [3,4 , 5],6,[7,8],9]
+ 10
+ 2
+ [3,4 , 5]
+ 6
+ [7,8]
+ 9
+ 3
+ 4
+ 5
+ 7
+ 8
+(14 rows)
+
+CREATE TABLE sample (json JSON);
+INSERT INTO sample VALUES ($$
+{
+	"store": {
+		"books": [
+			{
+				"title": "book 0",
+				"author": "author 0",
+				"prices": [1,2,3]
+			}, {
+				"title": "book 1",
+				"author": "author 1",
+				"prices": [4,5,6]
+			}, {
+				"title": "book 2",
+				"author": "author 2",
+				"prices": [7,8,9]
+			}
+		],
+		"toys": [
+			"Yo-yo",
+			"Boomerang",
+			"Basketball"
+		]
+	}
+}
+$$);
+SELECT json_path(json, 'store.books[*]') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }
+ {                                                    +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }
+ {                                                    +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }
+(3 rows)
+
+SELECT json_path(json, 'store.books[*].title') FROM sample;
+ json_path 
+-----------
+ "book 0"
+ "book 1"
+ "book 2"
+(3 rows)
+
+SELECT json_path(json, 'store.books[*].author') FROM sample;
+ json_path  
+------------
+ "author 0"
+ "author 1"
+ "author 2"
+(3 rows)
+
+SELECT json_path(json, 'store.books[*].prices') FROM sample;
+ json_path 
+-----------
+ [1,2,3]
+ [4,5,6]
+ [7,8,9]
+(3 rows)
+
+SELECT json_path(json, 'store.books[*].prices[*]') FROM sample;
+ json_path 
+-----------
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(9 rows)
+
+SELECT json_path(json, 'store.toys[*]') FROM sample;
+  json_path   
+--------------
+ "Yo-yo"
+ "Boomerang"
+ "Basketball"
+(3 rows)
+
+SELECT json_path(json, 'store.toys[*][0]') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, 'store.toys[*][1]') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, 'store.toys[*][0][0]') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, 'store.toys[*][0][1]') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, '..books') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ [                                                    +
+                         {                            +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }, {                         +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }, {                         +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }                            +
+                 ]
+(1 row)
+
+SELECT json_path(json, '..books[*]') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }
+ {                                                    +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }
+ {                                                    +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }
+(3 rows)
+
+SELECT json_path(json, '..title') FROM sample;
+ json_path 
+-----------
+ "book 0"
+ "book 1"
+ "book 2"
+(3 rows)
+
+SELECT json_path(json, '..author') FROM sample;
+ json_path  
+------------
+ "author 0"
+ "author 1"
+ "author 2"
+(3 rows)
+
+SELECT json_path(json, '..prices[*]') FROM sample;
+ json_path 
+-----------
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(9 rows)
+
+SELECT json_path(json, '..toys[*]') FROM sample;
+  json_path   
+--------------
+ "Yo-yo"
+ "Boomerang"
+ "Basketball"
+(3 rows)
+
+SELECT json_path(json, '..toys..[*]') FROM sample;
+  json_path   
+--------------
+ "Yo-yo"
+ "Boomerang"
+ "Basketball"
+(3 rows)
+
+SELECT json_path(json, '..[-1]') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, '..[0]') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }
+ 1
+ 4
+ 7
+ "Yo-yo"
+(5 rows)
+
+SELECT json_path(json, '..[1]') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }
+ 2
+ 5
+ 8
+ "Boomerang"
+(5 rows)
+
+SELECT json_path(json, '..[2]') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }
+ 3
+ 6
+ 9
+ "Basketball"
+(5 rows)
+
+SELECT json_path(json, '..[3]') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, '$') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+         "store": {                                   +
+                 "books": [                           +
+                         {                            +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }, {                         +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }, {                         +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }                            +
+                 ],                                   +
+                 "toys": [                            +
+                         "Yo-yo",                     +
+                         "Boomerang",                 +
+                         "Basketball"                 +
+                 ]                                    +
+         }                                            +
+ }
+(1 row)
+
+SELECT json_path(json, '..*') FROM sample;
+                       json_path                       
+-------------------------------------------------------
+ {                                                    +
+                 "books": [                           +
+                         {                            +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }, {                         +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }, {                         +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }                            +
+                 ],                                   +
+                 "toys": [                            +
+                         "Yo-yo",                     +
+                         "Boomerang",                 +
+                         "Basketball"                 +
+                 ]                                    +
+         }
+ [                                                    +
+                         {                            +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }, {                         +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }, {                         +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }                            +
+                 ]
+ [                                                    +
+                         "Yo-yo",                     +
+                         "Boomerang",                 +
+                         "Basketball"                 +
+                 ]
+ {                                                    +
+                                 "title": "book 0",   +
+                                 "author": "author 0",+
+                                 "prices": [1,2,3]    +
+                         }
+ {                                                    +
+                                 "title": "book 1",   +
+                                 "author": "author 1",+
+                                 "prices": [4,5,6]    +
+                         }
+ {                                                    +
+                                 "title": "book 2",   +
+                                 "author": "author 2",+
+                                 "prices": [7,8,9]    +
+                         }
+ "book 0"
+ "author 0"
+ [1,2,3]
+ 1
+ 2
+ 3
+ "book 1"
+ "author 1"
+ [4,5,6]
+ 4
+ 5
+ 6
+ "book 2"
+ "author 2"
+ [7,8,9]
+ 7
+ 8
+ 9
+ "Yo-yo"
+ "Boomerang"
+ "Basketball"
+(27 rows)
+
+SELECT json_path(json, '..char(-1)') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
+SELECT json_path(json, '..char(0)') FROM sample;
+ json_path 
+-----------
+ "b"
+ "a"
+ "b"
+ "a"
+ "b"
+ "a"
+ "Y"
+ "B"
+ "B"
+(9 rows)
+
+SELECT json_path(json, '..char(1)') FROM sample;
+ json_path 
+-----------
+ "o"
+ "u"
+ "o"
+ "u"
+ "o"
+ "u"
+ "o"
+ "o"
+ "a"
+(9 rows)
+
+SELECT json_path(json, '..char(2)') FROM sample;
+ json_path 
+-----------
+ "o"
+ "t"
+ "o"
+ "t"
+ "o"
+ "t"
+ "-"
+ "o"
+ "s"
+(9 rows)
+
+SELECT json_path(json, '..char(3)') FROM sample;
+ json_path 
+-----------
+ "k"
+ "h"
+ "k"
+ "h"
+ "k"
+ "h"
+ "y"
+ "m"
+ "k"
+(9 rows)
+
+SELECT json_path(json, '..char(100)') FROM sample;
+ json_path 
+-----------
+(0 rows)
+
diff --git a/contrib/json/expected/json_set.out b/contrib/json/expected/json_set.out
new file mode 100644
index 0000000..cc14f80
--- /dev/null
+++ b/contrib/json/expected/json_set.out
@@ -0,0 +1,164 @@
+SELECT json_set('[1,2,3]', '[1]', 5::json);
+ json_set 
+----------
+ [1,5,3]
+(1 row)
+
+SELECT json_set('[1,2,3]', '[3]', 5::json);
+ json_set 
+----------
+ [1,2,3]
+(1 row)
+
+SELECT json_set('[1,2,3]', '[2]', 5::json);
+ json_set 
+----------
+ [1,2,5]
+(1 row)
+
+SELECT json_set('[1,2,3]', '[2]', '[0,1]'::json);
+  json_set   
+-------------
+ [1,2,[0,1]]
+(1 row)
+
+SELECT json_set('  [ 1 ,   2,3]', '1', '5');
+    json_set    
+----------------
+   [ 1 ,   5,3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [2,3],3]', '1', '5');
+    json_set    
+----------------
+   [ 1 ,   5,3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[1][0]', '5');
+           json_set            
+-------------------------------
+   [ 1 ,   [  5 ,   3     ],3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[5][0]', '5');
+           json_set            
+-------------------------------
+   [ 1 ,   [  2 ,   3     ],3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[4][0]', '5');
+           json_set            
+-------------------------------
+   [ 1 ,   [  2 ,   3     ],3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[3][0]', '5');
+           json_set            
+-------------------------------
+   [ 1 ,   [  2 ,   3     ],3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[2][0]', '5');
+           json_set            
+-------------------------------
+   [ 1 ,   [  2 ,   3     ],3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[1][0]', '   5  ');
+           json_set            
+-------------------------------
+   [ 1 ,   [  5 ,   3     ],3]
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1][0]', '   5  ');
+              json_set               
+-------------------------------------
+   [ 1 ,   [  5 ,   3     ]  , 3  ] 
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', '   5  ');
+              json_set               
+-------------------------------------
+   [ 1 ,   [  5 ,   3     ]  , 3  ] 
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', '     5  ');
+              json_set               
+-------------------------------------
+   [ 1 ,   [  5 ,   3     ]  , 3  ] 
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', $$     "hello\tworld"  $$);
+                     json_set                     
+--------------------------------------------------
+   [ 1 ,   [  "hello\tworld" ,   3     ]  , 3  ] 
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', $$     "hello\u0009world"  $$);
+                       json_set                       
+------------------------------------------------------
+   [ 1 ,   [  "hello\u0009world" ,   3     ]  , 3  ] 
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][*]', $$     "hello\u0009world"  $$);
+                               json_set                                
+-----------------------------------------------------------------------
+   [ 1 ,   [  "hello\u0009world" ,   "hello\u0009world"     ]  , 3  ] 
+(1 row)
+
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '$', $$     "hello\u0009world"  $$);
+       json_set        
+-----------------------
+   "hello\u0009world" 
+(1 row)
+
+SELECT json_set('       [ 1 ,   [  2 ,   3     ]  , 3  ] ', '$', $$     "hello\u0009world"  $$);
+          json_set          
+----------------------------
+        "hello\u0009world" 
+(1 row)
+
+-- Since JavaScript doesn't do anything when you assign to a character subscript,
+-- neither will json_set.
+SELECT json_set('"hello"', '[0]', '"H"');
+ json_set 
+----------
+ "hello"
+(1 row)
+
+SELECT json_set('"hello"', '[0][0]', '"H"');
+ json_set 
+----------
+ "hello"
+(1 row)
+
+SELECT json_set('["hello"]', '[0][0]', '"H"');
+ json_set  
+-----------
+ ["hello"]
+(1 row)
+
+SELECT json_set('["hello"]', '[0][0][0]', '"H"');
+ json_set  
+-----------
+ ["hello"]
+(1 row)
+
+SELECT json_set('[0,1,2,[3,4,5],4]', '$[*]', '["set"]');
+                 json_set                  
+-------------------------------------------
+ [["set"],["set"],["set"],["set"],["set"]]
+(1 row)
+
+SELECT json_set('[0,1,2,[3,4,5],4]', '$[*][*]', '["set"]');
+              json_set               
+-------------------------------------
+ [0,1,2,[["set"],["set"],["set"]],4]
+(1 row)
+
+SELECT json_set('[0,1,2,[3,4,5],4]', '$..[*]', '["set"]');
+                 json_set                  
+-------------------------------------------
+ [["set"],["set"],["set"],["set"],["set"]]
+(1 row)
+
diff --git a/contrib/json/expected/orig.out b/contrib/json/expected/orig.out
new file mode 100644
index 0000000..95a768f
--- /dev/null
+++ b/contrib/json/expected/orig.out
@@ -0,0 +1,14 @@
+/* Make sure creating an array literal and subscripting
+ * preserves the original string exactly
+ * (except for trimming surrounding whitespace). */
+SELECT bool_and(btrim(before, E'\t\n\r ') = after) FROM (
+	SELECT
+		string AS before,
+		json_path(('  [ ' || string || '  ] ')::json, '$.[0]')::text AS after
+	FROM valid_test_strings
+) AS subquery;
+ bool_and 
+----------
+ t
+(1 row)
+
diff --git a/contrib/json/expected/validate.out b/contrib/json/expected/validate.out
new file mode 100644
index 0000000..3ff190b
--- /dev/null
+++ b/contrib/json/expected/validate.out
@@ -0,0 +1,220 @@
+SELECT json_validate(string), string FROM test_strings;
+ json_validate |                          string                          
+---------------+----------------------------------------------------------
+ f             | 
+ f             |   
+ f             | "
+ f             | [,]
+ f             | [)
+ f             | []]
+ f             | [}
+ f             | {,}
+ f             | {]
+ f             | ["1":2]
+ f             | [1,2,]
+ f             | [1:2}
+ f             | {"1":2,}
+ f             | {1:2}
+ f             | {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], [7 ]}]}
+ f             | {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], [7]}]}
+ f             | {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]
+ f             | {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]]
+ f             | {"1":2, "3":4
+ f             | "1\u2"
+ f             | [,2]
+ f             | "3
+ f             | "3" "4"
+ f             | [3[4]
+ f             | [3[4]]
+ f             | [3, [4, [5], 6] 7, 8 9]
+ f             | [3, [4, [5], 6] 7, 8, 9]
+ f             | [3, [4, [5], 6], 7, 8 9]
+ f             | {"hello":true, "bye":false, null}
+ f             | {"hello":true, "bye":false, null:null}
+ f             | "hi
+ f             | "hi"""
+ f             | {"hi": "bye"]
+ f             | "\uD800\uD800"
+ f             | "\uD800\uDBFF"
+ f             | "\UD834\UDD1E"
+ f             | "\uDB00"
+ f             | "\uDB00\uDBFF"
+ t             | "\uFFFE"
+ t             | "\uFFFF"
+ f             | .
+ t             | ""
+ t             | []
+ t             | {}
+ f             | +.
+ t             | 0.5
+ f             | 0.e1
+ t             | {"1":{}}
+ t             | {"1":2}
+ t             | {"1":2, "2.5" : [3, 4, {}, {"5": ["6"]}]}
+ t             | {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]}
+ t             | 1234
+ t             | -1234
+ t             | {"1":2, "3":4}
+ f             | +1234
+ f             | ++1234
+ t             | 123.456e14234
+ t             | 123.456e-14234
+ t             | 123.456e+14234
+ f             | 123.e-14234
+ t             | "1\u2000"
+ t             | "1\u20001"
+ t             | 2
+ f             | .246e-14234
+ f             | .2e-14234
+ t             | 3
+ f             | .3
+ t             | "3"
+ t             | [3]
+ f             | +3.
+ t             | 3.2e+1
+ t             | [3, [4]]
+ t             | [3, [4, [5]]]
+ t             | [3, [4, [5], 6]]
+ t             | [3, [4, [5], 6], 7]
+ t             | [3, [4, [5], 6], 7, 8]
+ t             | [3, [4, [5], 6], 7, 8, 9]
+ f             | +3.5
+ f             | .3e
+ f             | .3e1
+ f             | .3e-1
+ f             | .3e+1
+ f             | 3.e1
+ f             | 3.e+1
+ t             | 3e+1
+ f             | .5
+ f             | +.5
+ f             | .5e+1
+ t             | [ 7]
+ t             | [7 ]
+ t             | [7]
+ f             | .e-14234
+ t             | "hello"
+ t             | ["hello"]
+ t             | ["hello", "bye"]
+ t             | ["hello", "bye\n"]
+ t             | ["hello", "bye\n\r\t"]
+ t             | ["hello", "bye\n\r\t\b"]
+ t             | ["hello", "bye\n\r\t\b",true]
+ t             | ["hello", "bye\n\r\t\b",true , false]
+ t             | ["hello", "bye\n\r\t\b",true , false,    null]
+ f             | ["hello", "bye\n\r\t\v"]
+ t             | {"hello":true}
+ t             | {"hello":true, "bye":false}
+ t             | {"hello":true, "bye":false, "foo":["one","two","three"]}
+ t             | "hi"
+ t             | ["hi"]
+ t             | ["hi", "bye"]
+ t             | {"hi": "bye"}
+ t             | ["hi", "bye", 3]
+ t             | ["hi", "bye[", 3]
+ t             | "\u0007"
+ t             | "\u0008"
+ t             | "\u0009"
+ t             | "\u0010"
+ t             | "\u0020"
+ t             | "\u10000"
+ t             | "\u1234"
+ t             | "\u99999"
+ t             | "\ud800\udc00"
+ t             | "\uD800\uDC00"
+ t             | "\uD834\uDD1E"
+ t             | "\uDBFF\uDFFF"
+ t             | "\uFFFD"
+ t             | "\uFFFF"
+ f             | hello
+ t             | [32, 1]
+ f             | [32, 
+ t             | "\uD800\uDC00"
+ t             | "\n"
+ t             | "hello"
+ t             | "hello\u0009world"
+ t             | "hello"
+ t             | "hello\n"
+ t             | "hello"
+ t             | 3
+ f             | 3.
+ f             | .3
+ t             | 0.3
+ f             | 0.3e
+ f             | 0.3e+
+ t             | 0.3e+5
+ t             | 0.3e-5
+ t             | 0.3e5
+ t             | "hello"
+ f             | +3
+ t             | -3
+ f             | -3.
+ t             | -3.1
+ f             | .5
+ f             | 5.
+ f             | 5.e1
+ t             | 0.5
+ f             | .3e1
+ f             | .3e+1
+ f             | .3e-1
+ f             | .3e-1 .5
+ f             | .3e-1.5
+ f             | .3e+1.5
+ f             | .3e+.
+ f             | .3e+.5
+ f             | .3e+1.5
+ f             | 9.3e+1.5
+ f             | 9.e+1.5
+ f             | 9.e+
+ f             | 9.e+1
+ t             | "\""
+ t             | "\"3.5"
+ t             | "\"."
+ f             | "\".".
+ t             | "\"....."
+ f             | "\"\"\"\"""
+ f             | ["\"\"\"\"", .5]
+ f             | [.5]
+ t             | ["\"\"\"\"", 0.5]
+ f             | ["\"\"\"\"", .5]
+ f             | ["\"\"\"\"",.5]
+ f             | ["\"",.5]
+ f             | ["\".5",.5]
+ f             | ["\".5",".5\"".5]
+ f             | ["\".5",".5\"", .5]
+ f             | ["\".5",".5\"",.5]
+ t             | ["\".5",".5\"",0.5]
+ f             | {"key":/*comment*/"value"}
+ f             | {"key":/*comment"value"}
+ f             | {"key":"value"}/*
+ f             | {"key":"value"}/**/
+ f             | {"key":"value"}/***/
+ f             | {"key":"value"}/**//
+ f             | {"key":"value"}/**///
+ f             | {"key":"value"}/**///----
+ f             | {"key":"value"}#
+ f             | {"key":"value"}#{
+ f             | {"key":"value"}#{}
+ f             | {"key":"value"}#,
+ f             | {"key":"value"/**/, "k2":"v2"}
+ t             | "\u0027"
+ f             | "hello\'"
+ f             | 'hello\''
+ f             | 'hello'
+ f             | 'hell\'o'
+ f             | '\'hello'
+ f             | '\'hello\''
+ f             | \'hello\'
+ f             | 'hello\'
+ f             | ['hello\']
+ f             | ['hello\'']
+ f             | ['hello"']
+ f             | ['hello\"']
+ f             | ['hello"o']
+ f             | ['"']
+ f             | '"'
+ f             | '"hello"'
+ f             | '"hello'
+ f             | '"hi"'
+(215 rows)
+
diff --git a/contrib/json/json.c b/contrib/json/json.c
new file mode 100644
index 0000000..3aa6ddd
--- /dev/null
+++ b/contrib/json/json.c
@@ -0,0 +1,1202 @@
+/*-------------------------------------------------------------------------
+ *
+ * json.c
+ *	  Core JSON manipulation routines used by JSON data type support.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "json.h"
+#include "util.h"
+
+#include <ctype.h>
+
+#include "mb/pg_wchar.h"
+
+#define is_internal(node) ((node)->type == JSON_ARRAY || (node)->type == JSON_OBJECT)
+
+/* We can't use isspace() because it also accepts \v and \f, which
+   aren't legal whitespace characters in strict JSON. */
+#define is_whitespace(c) ((c)==' ' || (c)=='\t' || (c)=='\n' || (c)=='\r')
+
+static void
+skip_whitespace(const char **sp)
+{
+	const char *s = *sp;
+
+	while (is_whitespace(*s))
+		s++;
+	*sp = s;
+}
+
+static char
+end_parenthesis(JSON * node)
+{
+	Assert(node != NULL);
+
+	switch (node->type)
+	{
+		case JSON_ARRAY:
+			return ']';
+		case JSON_OBJECT:
+			return '}';
+		default:
+			Assert(false);
+			return '\0';
+	}
+}
+
+/*
+ * Reads exactly 4 hex characters (capital or lowercase).
+ * Writes the result to *out .
+ * Returns true on success, false if any input chars are not [0-9A-Fa-f] .
+ */
+static bool
+read_hex16(const char *in, unsigned int *out)
+{
+	unsigned int i;
+	unsigned int tmp;
+	char		c;
+
+	*out = 0;
+
+	for (i = 0; i < 4; i++)
+	{
+		c = *in++;
+		if (c >= '0' && c <= '9')
+			tmp = c - '0';
+		else if (c >= 'A' && c <= 'F')
+			tmp = c - 'A' + 10;
+		else if (c >= 'a' && c <= 'f')
+			tmp = c - 'a' + 10;
+		else
+			return false;
+
+		*out <<= 4;
+		*out += tmp;
+	}
+
+	return true;
+}
+
+/*
+ * Encodes a 16-bit number into hexadecimal,
+ * writing exactly 4 hex chars.
+ */
+static void
+write_hex16(char *out, unsigned int val)
+{
+	const char *hex = "0123456789ABCDEF";
+
+	*out++ = hex[(val >> 12) & 0xF];
+	*out++ = hex[(val >> 8) & 0xF];
+	*out++ = hex[(val >> 4) & 0xF];
+	*out++ = hex[val & 0xF];
+}
+
+
+/*********** JSON creation, manipulation, and deletion **********/
+
+JSON *
+json_mknode(json_type type)
+{
+	JSON	   *node = palloc(sizeof(*node));
+
+	memset(node, 0, sizeof(*node));
+	node->type = type;
+	return node;
+}
+
+JSON *
+json_mkbool(bool v_bool)
+{
+	JSON	   *node = json_mknode(JSON_BOOL);
+
+	node->v.v_bool = v_bool;
+	return node;
+}
+
+JSON *
+json_mkstring(const char *str, size_t length)
+{
+	JSON	   *node = json_mknode(JSON_STRING);
+
+	if (str)
+	{
+		node->v.string.str = pnstrdup(str, length);
+		node->v.string.length = length;
+	}
+	return node;
+}
+
+JSON *
+json_mknumber(const char *number, size_t length)
+{
+	JSON	   *node = json_mknode(JSON_NUMBER);
+
+	if (number)
+		node->v.number = pnstrdup(number, length);
+	return node;
+}
+
+/*
+ * Indicate that the node's value has changed,
+ * marking ancestors as necessary.
+ *
+ * Call json_touch_value so that json_encode(..., JSONOPT_ORIG)
+ * will encode the new value rather than using original text.
+ */
+void
+json_touch_value(JSON * node)
+{
+	while (node && node->orig.value.start)
+	{
+		node->orig.value.start = NULL;
+		node = node->parent;
+	}
+}
+
+/*
+ * Add child to parent, but don't clear orig pointers of ancestors.
+ *
+ * This is used by json_decode to ensure that original text segments
+ * are preserved while building the JSON tree.
+ */
+static void
+json_append_notouch(JSON * parent, JSON * child)
+{
+	Assert(parent->type == JSON_ARRAY || parent->type == JSON_OBJECT);
+	Assert(child->parent == NULL);
+
+	parent->v.children.count++;
+	child->parent = parent;
+	child->prev = parent->v.children.tail;
+	child->next = NULL;
+
+	if (parent->v.children.tail)
+	{
+		parent->v.children.tail->next = child;
+		parent->v.children.tail = child;
+	}
+	else
+	{
+		parent->v.children.head = parent->v.children.tail = child;
+	}
+}
+
+/*
+ * json_append
+ *	  Add child to parent, putting it at the end of its child list.
+ *
+ *	  Child must not already have another parent.
+ */
+void
+json_append(JSON * parent, JSON * child)
+{
+	json_append_notouch(parent, child);
+	json_touch_value(parent);
+}
+
+/*
+ * json_remove
+ *	  Remove node from its parent, but do not delete it.
+ */
+void
+json_remove(JSON * node)
+{
+	JSON	   *parent = node->parent;
+
+	if (parent == NULL)
+		return;
+	Assert(parent->type == JSON_ARRAY || parent->type == JSON_OBJECT);
+	Assert(parent->v.children.count > 0);
+
+	if (node->prev)
+		node->prev->next = node->next;
+	else
+		parent->v.children.head = node->next;
+	if (node->next)
+		node->next->prev = node->prev;
+	else
+		parent->v.children.tail = node->prev;
+
+	parent->v.children.count--;
+	node->parent = NULL;
+	node->prev = NULL;
+	node->next = NULL;
+
+	json_touch_value(parent);
+}
+
+/*
+ * Update the value of a node, preserving position and key information.
+ *
+ * Note well: If replacement is an array or object with children, the parent
+ *			  pointers of those children will be incorrect
+ *			  (they'll still refer to their original parent).
+ *
+ *			  Untrustworthy parent pointers is the price to pay for
+ *			  being able to copy JSON values by reference.
+ */
+void
+json_replace_value(JSON * node, JSON * replacement)
+{
+	node->type = replacement->type;
+	node->v = replacement->v;
+	node->orig.value = replacement->orig.value;
+
+	if (node->parent)
+		json_touch_value(node->parent);
+}
+
+const char *
+json_get_string(JSON * node, size_t *length_out)
+{
+	Assert(node->type == JSON_STRING);
+	if (length_out)
+		*length_out = node->v.string.length;
+	return node->v.string.str;
+}
+
+void
+json_set_string(JSON * node, const char *str, size_t length)
+{
+	Assert(node->type == JSON_STRING);
+	if (node->v.string.str)
+		pfree(node->v.string.str);
+	if (str)
+	{
+		node->v.string.str = pnstrdup(str, length);
+		node->v.string.length = length;
+	}
+	else
+	{
+		node->v.string.str = NULL;
+		node->v.string.length = 0;
+	}
+	json_touch_value(node);
+}
+
+const char *
+json_get_number(JSON * node)
+{
+	Assert(node->type == JSON_NUMBER);
+	return node->v.number;
+}
+
+void
+json_set_number(JSON * node, const char *number, size_t length)
+{
+	Assert(node->type == JSON_NUMBER);
+	if (node->v.number)
+		pfree(node->v.number);
+	if (number)
+		node->v.number = pnstrdup(number, length);
+	else
+		node->v.number = NULL;
+	json_touch_value(node);
+}
+
+/* Non-recursively free a node */
+static void
+free_node(JSON * node)
+{
+	if (node->type == JSON_STRING)
+	{
+		if (node->v.string.str)
+			pfree(node->v.string.str);
+	}
+	else if (node->type == JSON_NUMBER)
+	{
+		if (node->v.number)
+			pfree(node->v.number);
+	}
+
+	if (node->key)
+		pfree(node->key);
+
+	pfree(node);
+}
+
+/*
+ * Free a JSON node and all its descendants.
+ *
+ * Do not use this function if you have performed json_replace_value on
+ * a descendant, as this function relies on each node's ->parent field
+ * being trustworthy.
+ */
+static void
+json_delete(JSON * node)
+{
+	JSON	   *parent,
+			   *next;
+
+	if (node == NULL)
+		return;
+
+	/* Remove node from parent (if it has one). */
+	json_remove(node);
+
+descend:
+	while (is_internal(node) && node->v.children.head)
+		node = node->v.children.head;
+
+advance:
+	parent = node->parent;
+	next = node->next;
+	free_node(node);
+	node = next;
+
+	if (node != NULL)
+	{
+		goto descend;
+	}
+	else
+	{
+		node = parent;
+		if (node != NULL)
+			goto advance;
+		else
+			return;
+	}
+}
+
+
+/*********************** Parsing and validation **********************/
+
+static JSON *decode_leaf(const char **sp);
+static JSON *decode_number(const char **sp);
+
+/*
+ * json_decode_string has a different signature than its friends
+ * because it's also used to parse object member keys.
+ * It's also useful outside of json.c, such as in jsonpath.c .
+ */
+char	   *json_decode_string(const char **sp, size_t *length, bool strict);
+
+/*
+ * json_validate
+ *	  Make sure the given UTF-8 string is valid JSON.
+ *
+ * TODO: Consider making a dedicated function for this so we don't have to
+ *		 convert to UTF-8, build a JSON node, then free both
+ *		 whenever we need to validate (such as in json_in and json_recv).
+ */
+bool
+json_validate(const char *str)
+{
+	JSON	   *node = json_decode(str);
+
+	if (node == NULL)
+		return false;
+	json_delete(node);
+	return true;
+}
+
+/*
+ * json_validate_server_encoded
+ *	  Variant of json_validate that takes a server-encoded string
+ *	  rather than a UTF-8 string.
+ *
+ *	  Note that a dedicated json_validate (described in the TODO above)
+ *	  would be able to handle both encodings natively, since both are
+ *	  ASCII-compatible.
+ */
+bool
+json_validate_server_encoded(const char *str)
+{
+	char	   *str_utf8 = server_to_utf8(str, strlen(str));
+	bool		result = json_validate(str_utf8);
+
+	if (str_utf8 != str)
+		pfree(str_utf8);
+
+	return result;
+}
+
+/*
+ * json_decode
+ *	  Convert a JSON-encoded string to a JSON node.
+ *	  @str must be valid UTF-8.
+ */
+JSON *
+json_decode(const char *str)
+{
+	JSON	   *root = NULL,
+			   *parent = NULL,
+			   *node = NULL;
+	const char *s = str;
+	char	   *key;
+	size_t		key_length;
+	struct json_orig orig;
+	bool		expect_endp;
+
+	if (str == NULL)
+		return NULL;
+
+	Assert(utf8_validate(str, strlen(str)));
+
+	expect_endp = false;
+	goto item;
+
+item:							/* Expect a value (set expect_endp before goto
+								 * item; ) */
+	key = NULL;
+	key_length = 0;
+	memset(&orig, 0, sizeof(orig));
+
+	orig.key_left_space.start = s;
+	orig.left_space.start = s;
+
+	skip_whitespace(&s);
+
+	if (expect_endp)
+	{
+		if (*s == ']' || *s == '}')
+			goto endp;
+	}
+
+	if (parent != NULL && parent->type == JSON_OBJECT)
+	{
+		/* Parse member key string. */
+		orig.key_left_space.end = s;
+		orig.key.start = s;
+
+		key = json_decode_string(&s, &key_length, true);
+		if (key == NULL)
+			goto failed;
+
+		orig.key.end = s;
+		orig.key_right_space.start = s;
+
+		/* Eat the " : " */
+		skip_whitespace(&s);
+		if (*s != ':')
+			goto failed;
+
+		orig.key_right_space.end = s;
+		s++;
+		orig.left_space.start = s;
+
+		skip_whitespace(&s);
+	}
+
+	/*
+	 * The way orig.value and company are initialized is a bit funky. If this
+	 * node has children, we have to finish parsing the node's children before
+	 * we know where it ends.  Hence, initialization of orig.value_end and
+	 * after will be deferred if this node has children.
+	 */
+
+	orig.left_space.end = s;
+	orig.value.start = s;
+
+	node = decode_leaf(&s);
+	if (node == NULL)
+	{
+		if (*s == '[')
+			node = json_mknode(JSON_ARRAY);
+		else if (*s == '{')
+			node = json_mknode(JSON_OBJECT);
+		else
+			goto failed;
+		s++;
+
+		/*
+		 * orig.value.end and later are dangling (actually NULL) for now, but
+		 * will be initialized when we get to state 'endp' .
+		 */
+	}
+	else
+	{
+		orig.value.end = s;
+		orig.right_space.start = s;
+
+		skip_whitespace(&s);
+
+		orig.right_space.end = s;
+	}
+
+	node->key = key;
+	node->key_length = key_length;
+
+	/*
+	 * The key now belongs to the node.  This prevents a double free on
+	 * failure (see the failed: label).
+	 */
+	key = NULL;
+
+	node->orig = orig;
+
+	if (parent != NULL)
+		json_append_notouch(parent, node);
+	else
+		root = node;
+
+	if (is_internal(node))
+	{
+		/*
+		 * "push" node onto the "stack".  Nodes point up to their parents,
+		 * which is why this function doesn't need a "stack" per se.
+		 */
+		parent = node;
+
+		expect_endp = true;
+		goto item;
+	}
+
+	if (parent != NULL)
+		goto comma_endp;
+	else
+		goto end;
+
+comma_endp:						/* Expect a comma or end bracket/brace */
+	if (*s == ',')
+	{
+		s++;
+
+		expect_endp = false;
+		goto item;
+	}
+	if (*s == ']' || *s == '}')
+		goto endp;
+
+	goto failed;
+
+endp:							/* Handle an end bracket/brace */
+	if (*s != end_parenthesis(parent))
+		goto failed;
+	s++;
+
+	/* "pop" a node from the "stack" */
+	node = parent;
+	parent = parent->parent;
+
+	/*
+	 * The other pointers were set when we started parsing this node in the
+	 * 'item' state.
+	 */
+	node->orig.value.end = s;
+	node->orig.right_space.start = s;
+
+	skip_whitespace(&s);
+
+	node->orig.right_space.end = s;
+
+	if (parent != NULL)
+		goto comma_endp;
+	else
+		goto end;
+
+end:							/* Expect end of text */
+	if (*s != '\0')
+		goto failed;
+	return node;
+
+failed:					/* Handle failure */
+	if (key != NULL)
+		pfree(key);
+	json_delete(root);
+	return NULL;
+}
+
+/*
+ * Decode and skip a node that does not have children.
+ * Whitespace is not skipped first (it is done in the primary decode loop).
+ *
+ * Returns NULL if next character is '[', '{', or invalid.
+ */
+static JSON *
+decode_leaf(const char **sp)
+{
+	char		c = **sp;
+
+	if (c == '"')
+	{
+		size_t		length;
+		char	   *str = json_decode_string(sp, &length, true);
+
+		if (str != NULL)
+		{
+			JSON	   *node = json_mknode(JSON_STRING);
+
+			node->v.string.str = str;
+			node->v.string.length = length;
+			return node;
+		}
+
+		return NULL;
+	}
+	if ((c >= '0' && c <= '9') || c == '-')
+		return decode_number(sp);
+	if (strncmp(*sp, "true", 4) == 0)
+	{
+		(*sp) += 4;
+		return json_mkbool(true);
+	}
+	if (strncmp(*sp, "false", 5) == 0)
+	{
+		(*sp) += 5;
+		return json_mkbool(false);
+	}
+	if (strncmp(*sp, "null", 4) == 0)
+	{
+		(*sp) += 4;
+		return json_mknode(JSON_NULL);
+	}
+
+	return NULL;
+}
+
+/*
+ * The JSON spec says that a number shall follow this precise pattern
+ * (spaces and quotes added for readability):
+ *	 '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)?
+ *
+ * However, some JSON parsers are more liberal.  For instance, PHP accepts
+ * '.5' and '1.'.  JSON.parse accepts '+3'.
+ *
+ * This function takes the strict approach.
+ */
+static bool
+validate_number(const char **sp)
+{
+	const char *s = *sp;
+
+	/* '-'? */
+	if (*s == '-')
+		s++;
+
+	/* (0 | [1-9][0-9]*) */
+	if (*s == '0')
+	{
+		s++;
+	}
+	else
+	{
+		if (!isdigit(*s))
+			return false;
+		do
+			s++;
+		while (isdigit(*s));
+	}
+
+	/* ('.' [0-9]+)? */
+	if (*s == '.')
+	{
+		s++;
+		if (!isdigit(*s))
+			return false;
+		do
+			s++;
+		while (isdigit(*s));
+	}
+
+	/* ([Ee] [+-]? [0-9]+)? */
+	if (*s == 'E' || *s == 'e')
+	{
+		s++;
+		if (*s == '+' || *s == '-')
+			s++;
+		if (!isdigit(*s))
+			return false;
+		do
+			s++;
+		while (isdigit(*s));
+	}
+
+	*sp = s;
+	return true;
+}
+
+static JSON *
+decode_number(const char **sp)
+{
+	const char *start,
+			   *end;
+
+	start = *sp;
+	if (!validate_number(sp))
+		return NULL;
+	end = *sp;
+
+	return json_mknumber(start, end - start);
+}
+
+/*
+ * json_decode_string
+ *	  If you're interested in the decoding JSON in general, see json_decode.
+ *
+ *	  Decodes a JSON string literal (e.g. "\"hello\"").
+ *
+ *	  If strict is true, string must be double-quoted,
+ *	  as is required by the JSON RFC.
+ *	  Otherwise (e.g. if parsing something JSON-like, such as JSONPath),
+ *	  the string may be single- or double-quoted.
+ *
+ *	  Also, no whitespace skipping is done, so the caller should only
+ *	  call this function when it expects **sp to be either " or '
+ *
+ *	  On success, returns the decoded string, passes the decoded string's
+ *	  length through *length (which must not be NULL), and advances *sp to point
+ *	  to the end of string literal (after the closing quote character).
+ *
+ *	  On failure (parse error), returns NULL and
+ *	  leaves *length and *sp untouched.
+ */
+char *
+json_decode_string(const char **sp, size_t *length, bool strict)
+{
+	const char *s = *sp;
+	StringInfoData ret;
+	char		buf[4];
+	int			len;
+	char		quote;
+
+	Assert(length != NULL);
+
+	initStringInfo(&ret);
+
+	quote = *s++;
+	if (strict)
+	{
+		if (quote != '"')
+			return NULL;
+	}
+	else
+	{
+		if (quote != '"' && quote != '\'')
+			return NULL;
+	}
+
+	while (*s != '\0' && *s != quote)
+	{
+		unsigned char c = *s++;
+		unsigned int uc;
+		unsigned int lc;
+
+		if (c == '\\')
+		{
+			c = *s++;
+			switch (c)
+			{
+				case '\\':
+				case '/':
+					break;
+				case 'b':
+					c = '\b';
+					break;
+				case 'f':
+					c = '\f';
+					break;
+				case 'n':
+					c = '\n';
+					break;
+				case 'r':
+					c = '\r';
+					break;
+				case 't':
+					c = '\t';
+					break;
+				case 'u':
+					if (!read_hex16(s, &uc))
+						goto failed;
+					s += 4;
+
+					if (uc >= 0xD800 && uc <= 0xDFFF)
+					{
+						/* Handle UTF-16 surrogate pair. */
+
+						if (uc >= 0xDC00)
+							goto failed;		/* Second surrogate not
+												 * preceded by first
+												 * surrogate. */
+
+						if (s[0] != '\\' || s[1] != 'u'
+							|| !read_hex16(s + 2, &lc)
+							|| !(lc >= 0xDC00 && lc <= 0xDFFF))
+							goto failed;		/* First surrogate not
+												 * followed by second
+												 * surrogate. */
+
+						s += 6;
+
+						uc = 0x10000 | ((uc & 0x3FF) << 10) | (lc & 0x3FF);
+					}
+
+					unicode_to_utf8(uc, (unsigned char *) buf);
+					len = pg_utf_mblen((unsigned char *) buf);
+
+					Assert(len > 0);
+
+					appendBinaryStringInfo(&ret, buf, len);
+
+					continue;	/* Continue the enclosing while loop to skip
+								 * the str_append below. */
+				default:		/* Invalid escape */
+					if (c == quote)
+						break;
+					if (!strict && (c == '"' || c == '\''))
+						break;
+					goto failed;	/* Invalid escape */
+			}
+		}
+		else if (c <= 0x1F)
+		{
+			/* Control characters not allowed in string literals. */
+			goto failed;
+		}
+		appendStringInfoChar(&ret, c);
+	}
+
+	if (*s++ != quote)
+		goto failed;
+
+	*length = ret.len;
+	*sp = s;
+	return ret.data;
+
+failed:
+	pfree(ret.data);
+	return NULL;
+}
+
+/*
+ * json_text_type
+ *	  Determines the type of a JSON string without fully decoding it.
+ *	  Expects the given string to be valid JSON.
+ *	  Might return JSON_INVALID if something is wrong with the input.
+ */
+json_type
+json_text_type(const char *str, size_t nbytes)
+{
+	const char *s = str;
+	const char *e = str + nbytes;
+	char		c;
+
+	/* Skip whitespace characters. */
+	while (s < e && is_whitespace(*s))
+		s++;
+
+	/* Get first non-white character, making sure it's in bounds. */
+	if (s >= e)
+		return JSON_INVALID;
+	c = *s;
+
+	switch (c)
+	{
+		case 'n':
+			return JSON_NULL;
+		case '"':
+			return JSON_STRING;
+		case 't':
+		case 'f':
+			return JSON_BOOL;
+		case '{':
+			return JSON_OBJECT;
+		case '[':
+			return JSON_ARRAY;
+		default:
+			if (c == '-' || (c >= '0' && c <= '9'))
+				return JSON_NUMBER;
+			return JSON_INVALID;
+	}
+}
+
+
+/****************************** Encoding *****************************/
+
+/*
+ * encode_string
+ *	  Variant of json_encode_string that writes its output to a StringInfo.
+ */
+static void
+encode_string(StringInfo out, const char *string, size_t length, char quote,
+			  bool escape_unicode)
+{
+	const char *s = string;
+	const char *e = s + length;
+
+	Assert(quote != '\\');
+	if (escape_unicode)
+		Assert(utf8_validate(string, length));
+
+	appendStringInfoChar(out, quote);
+
+	while (s < e)
+	{
+		unsigned char c = *s++;
+		unsigned char endchar;
+
+		switch (c)
+		{
+			case '\\':
+				endchar = '\\';
+				break;
+			case '\b':
+				endchar = 'b';
+				break;
+			case '\f':
+				endchar = 'f';
+				break;
+			case '\n':
+				endchar = 'n';
+				break;
+			case '\r':
+				endchar = 'r';
+				break;
+			case '\t':
+				endchar = 't';
+				break;
+			default:
+				{
+					if (c == quote)
+					{
+						endchar = quote;
+						break;
+					}
+					if (c < 0x1F || (c >= 0x80 && escape_unicode))
+					{
+						/* Encode using \u.... */
+						pg_wchar	uc;
+						unsigned int lc;
+						char		txt[13];
+
+						s--;
+						uc = utf8_decode_char(&s);
+
+						txt[0] = '\\';
+						txt[1] = 'u';
+						txt[6] = '\\';
+						txt[7] = 'u';
+						if (uc <= 0xFFFF)
+						{
+							write_hex16(txt + 2, uc);
+							txt[6] = '\0';
+						}
+						else
+						{
+							uc -= 0x10000;
+							lc = uc & 0x3FF;
+							uc = uc >> 10;
+							uc |= 0xD800;
+							lc |= 0xDC00;
+							write_hex16(txt + 2, uc);
+							write_hex16(txt + 8, lc);
+							txt[12] = '\0';
+						}
+
+						appendStringInfoString(out, txt);
+						continue;		/* Skip backslash-encoding code below. */
+					}
+					endchar = '\0';
+				}
+		}
+
+		appendStringInfoChar(out, endchar ? '\\' : c);
+		if (endchar != '\0')
+			appendStringInfoChar(out, endchar);
+	}
+
+	appendStringInfoChar(out, quote);
+}
+
+static bool
+encode_number(StringInfo out, const char *string)
+{
+	const char *s = string;
+	const char *start,
+			   *end;
+
+	if (string == NULL)
+		return false;
+
+	/* Validate number, trimming whitespace. */
+	skip_whitespace(&s);
+	start = s;
+	if (!validate_number(&s))
+		return false;
+	end = s;
+	skip_whitespace(&s);
+	if (*s != '\0')
+		return false;
+
+	/* Append number to out */
+	appendBinaryStringInfo(out, start, end - start);
+
+	return true;
+}
+
+typedef struct
+{
+	StringInfoData str;
+	bool		use_orig;
+	bool		escape_unicode;
+	bool		trim;
+}	json_encode_ctx;
+
+static bool json_encode_recurse(JSON * node, json_encode_ctx * ctx);
+
+/*
+ * json_encode
+ *	  Encode a JSON node.
+ *
+ *	  The JSONOPT_ESCAPE_UNICODE option may only be used
+ *	  if the strings in the JSON tree are UTF-8-encoded.
+ */
+char *
+json_encode(JSON * node, int options)
+{
+	json_encode_ctx ctx;
+
+	initStringInfo(&ctx.str);
+	ctx.use_orig = !!(options & JSONOPT_USE_ORIG);
+	ctx.escape_unicode = !!(options & JSONOPT_ESCAPE_UNICODE);
+	ctx.trim = !(options & JSONOPT_NO_TRIM);
+
+	if (!json_encode_recurse(node, &ctx))
+	{
+		pfree(ctx.str.data);
+		return NULL;
+	}
+
+	return ctx.str.data;
+}
+
+static bool
+json_encode_recurse(JSON * node, json_encode_ctx * ctx)
+{
+#define has_orig(field) \
+		(use_orig && node->orig.field.start)
+#define push_orig(field) \
+		appendBinaryStringInfo(&ctx->str, node->orig.field.start, \
+							node->orig.field.end - node->orig.field.start)
+
+	bool		use_orig = ctx->use_orig;
+	bool		trim = ctx->trim;
+
+	ctx->trim = false;			/* Don't trim internal nodes, just the root
+								 * node. */
+
+	if (!trim && has_orig(left_space))
+		push_orig(left_space);
+
+	if (has_orig(value))
+	{
+		push_orig(value);
+	}
+	else
+	{
+		const char *txt = NULL;
+		JSON	   *child;
+
+		switch (node->type)
+		{
+			case JSON_NULL:
+				txt = "null";
+				break;
+			case JSON_BOOL:
+				if (node->v.v_bool)
+					txt = "true";
+				else
+					txt = "false";
+				break;
+			case JSON_STRING:
+				encode_string(&ctx->str,
+							  node->v.string.str, node->v.string.length,
+							  '"', ctx->escape_unicode);
+				break;
+			case JSON_NUMBER:
+				if (!encode_number(&ctx->str, node->v.number))
+					return false;
+				break;
+			case JSON_ARRAY:
+				appendStringInfoChar(&ctx->str, '[');
+
+				json_foreach(child, node)
+				{
+					json_encode_recurse(child, ctx);
+					if (child->next != NULL)
+						appendStringInfoChar(&ctx->str, ',');
+				}
+
+				appendStringInfoChar(&ctx->str, ']');
+				break;
+			case JSON_OBJECT:
+				appendStringInfoChar(&ctx->str, '{');
+
+				json_foreach(child, node)
+				{
+					/*
+					 * Shadows the parent node (assigned to the variable
+					 * @node) so we can use our macros on the child node
+					 * instead. Hurray for lexical scoping!
+					 */
+					JSON	   *node = child;
+
+					if (has_orig(key_left_space))
+						push_orig(key_left_space);
+
+					if (has_orig(key))
+						push_orig(key);
+					else
+						encode_string(&ctx->str, node->key, node->key_length,
+									  '"', ctx->escape_unicode);
+
+					if (has_orig(key_right_space))
+						push_orig(key_right_space);
+
+					appendStringInfoChar(&ctx->str, ':');
+
+					json_encode_recurse(node, ctx);
+
+					if (node->next != NULL)
+						appendStringInfoChar(&ctx->str, ',');
+				}
+
+				appendStringInfoChar(&ctx->str, '}');
+				break;
+			default:
+				return false;
+		}
+
+		if (txt != NULL)
+			appendStringInfoString(&ctx->str, txt);
+	}
+
+	if (!trim && has_orig(right_space))
+		push_orig(right_space);
+
+	return true;
+
+#undef has_orig
+#undef push_orig
+}
+
+/*
+ * json_encode_string
+ *	  If you're interested in encoding JSON in general, see json_encode .
+ *
+ *	  Encodes a string literal JSON-style using the given quote character.
+ *	  Note that using anything but '"' as the quote character will result
+ *	  in invalid JSON.
+ *
+ *	  If escape_unicode is true, str must be valid UTF-8.
+ *	  In any case, str may contain null characters (hence the length argument).
+ *
+ *	  quote must not be a backslash.
+ */
+char *
+json_encode_string(const char *str, size_t length, char quote,
+				   bool escape_unicode)
+{
+	StringInfoData ret;
+
+	initStringInfo(&ret);
+	encode_string(&ret, str, length, quote, escape_unicode);
+
+	return ret.data;
+}
diff --git a/contrib/json/json.h b/contrib/json/json.h
new file mode 100644
index 0000000..16654ed
--- /dev/null
+++ b/contrib/json/json.h
@@ -0,0 +1,220 @@
+/*-------------------------------------------------------------------------
+ *
+ * json.h
+ *	  Core JSON manipulation routines used by JSON data type support,
+ *	  along with miscellaneous declarations and includes.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef JSON_H
+#define JSON_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "libpq/pqformat.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+typedef struct varlena jsontype;
+
+#define DatumGetJSONP(X)	((jsontype *) PG_DETOAST_DATUM(X))
+#define JSONPGetDatum(X)	PointerGetDatum(X)
+
+#define PG_GETARG_JSON_P(n) DatumGetJSONP(PG_GETARG_DATUM(n))
+#define PG_RETURN_JSON_P(x) PG_RETURN_POINTER(x)
+
+void		report_corrupt_json(void);
+
+/* Keep the order of these enum entries in sync with
+ * enum_type_names[] in json_ops.c . */
+typedef enum
+{
+	JSON_NULL,
+	JSON_STRING,
+	JSON_NUMBER,
+	JSON_BOOL,
+	JSON_OBJECT,
+	JSON_ARRAY,
+	JSON_TYPE_COUNT = JSON_ARRAY + 1,
+
+	JSON_INVALID
+}	json_type;
+
+#define json_type_is_valid(type) ((type) >= 0 && (type) < JSON_TYPE_COUNT)
+
+typedef struct JSON JSON;
+
+/*
+ * All strings in JSON nodes are UTF-8-encoded, not server encoded.
+ * The reason for this is because we need to encode/decode individual
+ * Unicode codepoints when dealing with escape characters, but there
+ * are no functions for efficiently converting between Unicode code points
+ * and any server encoding.
+ *
+ * As an exception to the rule, if you only use node factory functions and
+ * json_encode without the JSONOPT_ESCAPE_UNICODE option, you may operate
+ * in the server encoding instead.
+ */
+struct JSON
+{
+	json_type	type;
+
+	union
+	{
+		/* JSON_BOOL */
+		bool		v_bool;
+
+		/* JSON_STRING */
+		struct
+		{
+			char	   *str;
+			size_t		length;
+		}			string;
+
+		/*
+		 * JSON_NUMBER
+		 *
+		 * Numbers are encoded as strings to avoid unnecessary precision loss.
+		 */
+		char	   *number;
+
+		/* JSON_ARRAY or JSON_OBJECT (children) */
+		struct
+		{
+			JSON	   *head;
+			JSON	   *tail;
+			size_t		count;
+		}			children;
+	}			v;
+
+	JSON	   *parent;
+	JSON	   *prev;
+	JSON	   *next;
+
+	/*
+	 * If node is a member of an object, key will be set. Otherwise, key will
+	 * be null.
+	 */
+	char	   *key;
+	size_t		key_length;
+
+	struct json_orig
+	{
+		/* These only apply if this node is a member of an object. */
+		struct
+		{
+			const char *start;
+			const char *end;
+		}			key_left_space, key, key_right_space;
+
+		struct
+		{
+			const char *start;
+			const char *end;
+		}			left_space, value, right_space;
+	}			orig;
+
+	/* Used by jp_set to indicate we should not visit this node again. */
+	bool		jp_changed;
+};
+
+
+/*** Encoding / decoding / validation ***/
+
+bool		json_validate(const char *str);
+bool		json_validate_server_encoded(const char *str);
+JSON	   *json_decode(const char *str);
+
+#define JSONOPT_USE_ORIG		1
+#define JSONOPT_ESCAPE_UNICODE	2
+#define JSONOPT_NO_TRIM			4
+char	   *json_encode(JSON * node, int options);
+
+json_type	json_text_type(const char *str, size_t nbytes);
+
+
+/*** Lookup / traversal ***/
+
+#define json_foreach(child, parent) \
+	for ((child) = json_head(parent); (child) != NULL; (child) = (child)->next)
+
+static inline JSON *
+json_head(JSON * parent)
+{
+	switch (parent->type)
+	{
+		case JSON_ARRAY:
+		case JSON_OBJECT:
+			return parent->v.children.head;
+		default:
+			return NULL;
+	}
+}
+
+
+/*** Parent/child manipulation ***/
+
+void		json_append(JSON * parent, JSON * child);
+void		json_remove(JSON * node);
+
+
+/*** Node factory functions ***/
+
+JSON	   *json_mknode(json_type type);
+JSON	   *json_mkbool(bool v_bool);
+JSON	   *json_mkstring(const char *str, size_t length);
+JSON	   *json_mknumber(const char *number, size_t length);
+static inline JSON *
+json_mkarray(void)
+{
+	return json_mknode(JSON_ARRAY);
+}
+static inline JSON *
+json_mkobject(void)
+{
+	return json_mknode(JSON_OBJECT);
+}
+
+
+/*** Value get/set functions ***/
+
+void		json_touch_value(JSON * node);
+
+static inline bool
+json_get_bool(JSON * node)
+{
+	Assert(node->type == JSON_BOOL);
+	return node->v.v_bool;
+}
+static inline void
+json_set_bool(JSON * node, bool v_bool)
+{
+	Assert(node->type == JSON_BOOL);
+	node->v.v_bool = v_bool;
+	json_touch_value(node);
+}
+const char *json_get_string(JSON * node, size_t *length_out);
+void		json_set_string(JSON * node, const char *str, size_t length);
+const char *json_get_number(JSON * node);
+void		json_set_number(JSON * node, const char *number, size_t length);
+
+void		json_replace_value(JSON * node, JSON * replacement);
+
+
+/*** Miscellaneous utility functions ***/
+
+char	   *json_decode_string(const char **sp, size_t *length, bool strict);
+char	   *json_encode_string(const char *str, size_t length, char quote, bool escape_unicode);
+
+#endif
diff --git a/contrib/json/json.sql.in b/contrib/json/json.sql.in
new file mode 100644
index 0000000..7176e4b
--- /dev/null
+++ b/contrib/json/json.sql.in
@@ -0,0 +1,85 @@
+-- Adjust this setting to control where the objects get created.
+SET search_path = public;
+
+CREATE TYPE json;
+
+CREATE OR REPLACE FUNCTION json_in(cstring)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_out(json)
+RETURNS cstring
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_recv(internal)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_send(json)
+RETURNS bytea
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE TYPE json (
+	INPUT = json_in,
+	OUTPUT = json_out,
+	RECEIVE = json_recv,
+	SEND = json_send,
+	INTERNALLENGTH = VARIABLE,
+	STORAGE = extended,
+	
+	-- make it a non-preferred member of string type category, as citext does
+	CATEGORY       = 'S',
+	PREFERRED      = false
+);
+
+-- Keep the labels of this enum in sync with enum_type_names[] in json_ops.c .
+CREATE TYPE json_type_t AS ENUM ('null', 'string', 'number', 'bool', 'object', 'array');
+
+CREATE OR REPLACE FUNCTION json_type(json)
+RETURNS json_type_t
+AS 'MODULE_PATHNAME','json_get_type'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_validate(text)
+RETURNS boolean
+AS 'MODULE_PATHNAME','json_validate_f'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION from_json(json)
+RETURNS text
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION to_json(anyelement)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE; -- not STRICT; allows to_json(NULL) to return 'null'::json.
+
+CREATE OR REPLACE FUNCTION json_get(json, json_path text)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_set(json, json_path text, json)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_path(json, json_path text)
+RETURNS SETOF json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION json_condense(json)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION parse_json_path(text)
+RETURNS text
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
diff --git a/contrib/json/json_io.c b/contrib/json/json_io.c
new file mode 100644
index 0000000..2510d49
--- /dev/null
+++ b/contrib/json/json_io.c
@@ -0,0 +1,465 @@
+/*-------------------------------------------------------------------------
+ *
+ * json_io.c
+ *	  Primary input/output and conversion procedures
+ *	  for JSON data type.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "json.h"
+#include "util.h"
+
+#include "utils/array.h"
+#include "mb/pg_wchar.h"
+
+PG_MODULE_MAGIC;
+
+static json_type decide_json_type(Oid type, char category);
+static const char *datum_to_json(Datum datum, TypeInfo *typeInfo,
+			  json_type target_type);
+static const char *array_to_json(Datum datum);
+static const char *build_array_string(const char **values,
+				   const int *dim, int ndim);
+static void build_array_string_recurse(StringInfo string, const char ***values,
+						   const int *dim, int ndim);
+
+void
+report_corrupt_json(void)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_DATA_CORRUPTED),
+			 errmsg("corrupt JSON content")));
+}
+
+PG_FUNCTION_INFO_V1(json_in);
+Datum		json_in(PG_FUNCTION_ARGS);
+Datum
+json_in(PG_FUNCTION_ARGS)
+{
+	char	   *string = PG_GETARG_CSTRING(0);
+	jsontype   *vardata;
+
+	if (!json_validate_server_encoded(string))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("could not parse JSON value")));
+
+	vardata = cstring_to_text(string);
+	PG_RETURN_JSON_P(vardata);
+}
+
+PG_FUNCTION_INFO_V1(json_out);
+Datum		json_out(PG_FUNCTION_ARGS);
+Datum
+json_out(PG_FUNCTION_ARGS)
+{
+	jsontype   *vardata = PG_GETARG_JSON_P(0);
+	char	   *string = text_to_cstring((text *) vardata);
+
+	Assert(json_validate_server_encoded(string));
+
+	PG_RETURN_CSTRING(string);
+}
+
+PG_FUNCTION_INFO_V1(json_recv);
+Datum		json_recv(PG_FUNCTION_ARGS);
+Datum
+json_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	char	   *str;
+	int			nbytes;
+	jsontype   *result;
+
+	str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
+
+	if (!json_validate_server_encoded(str))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+				 errmsg("could not parse JSON value")));
+
+	result = cstring_to_text_with_len(str, nbytes);
+	pfree(str);
+
+	PG_RETURN_JSON_P(result);
+}
+
+PG_FUNCTION_INFO_V1(json_send);
+Datum		json_send(PG_FUNCTION_ARGS);
+Datum
+json_send(PG_FUNCTION_ARGS)
+{
+	jsontype   *t = PG_GETARG_JSON_P(0);
+	StringInfoData buf;
+
+#ifdef USE_ASSERT_CHECKING
+	{
+		char	   *string = text_to_cstring(t);
+
+		Assert(json_validate_server_encoded(string));
+
+		pfree(string);
+	}
+#endif
+
+	pq_begintypsend(&buf);
+	pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+/*
+ * Performs JSON value extraction in UTF-8 C-String land.
+ *
+ * If the input was JSON-encoded NULL, this function returns NULL
+ * (indicating that we want from_json to yield actual SQL NULL).
+ */
+static const char *
+from_json_cstring(const char *input)
+{
+	size_t		len;
+	JSON	   *json = json_decode(input);
+	const char *cstring_out = NULL;
+
+	if (json == NULL)
+		report_corrupt_json();
+
+	switch (json->type)
+	{
+		case JSON_NULL:
+			cstring_out = NULL;
+			break;
+		case JSON_STRING:
+			cstring_out = json_get_string(json, &len);
+			if (strlen(cstring_out) != len)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
+						 errmsg("could not convert JSON-encoded string to TEXT: contains \\u0000"),
+						 errdetail("the string was: %s", utf8_to_server(input, strlen(input)))));
+
+			/*
+			 * todo: suggest using from_json_as_bytea instead when that is
+			 * available.
+			 */
+			break;
+		case JSON_NUMBER:
+			cstring_out = json_get_number(json);
+			break;
+		case JSON_BOOL:
+			cstring_out = json_get_bool(json) ? "true" : "false";
+			break;
+		case JSON_OBJECT:
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("from_json cannot be applied to JSON objects")));
+			break;
+		case JSON_ARRAY:
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("from_json cannot be applied to JSON arrays")));
+			break;
+		default:
+			Assert(false);
+	}
+
+	return cstring_out;
+}
+
+PG_FUNCTION_INFO_V1(from_json);
+Datum		from_json(PG_FUNCTION_ARGS);
+Datum
+from_json(PG_FUNCTION_ARGS)
+{
+	char	   *cstring_in = text_to_utf8_cstring(PG_GETARG_JSON_P(0));
+	const char *cstring_out = from_json_cstring(cstring_in);
+	text	   *vardata_out;
+
+	pfree(cstring_in);
+
+	if (cstring_out != NULL)
+	{
+		vardata_out = utf8_cstring_to_text(cstring_out);
+		PG_RETURN_TEXT_P(vardata_out);
+	}
+	else
+	{
+		PG_RETURN_NULL();
+	}
+}
+
+PG_FUNCTION_INFO_V1(to_json);
+Datum		to_json(PG_FUNCTION_ARGS);
+Datum
+to_json(PG_FUNCTION_ARGS)
+{
+	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+	TypeInfo   *typeInfo;
+	json_type	target_type;
+
+	typeInfo = FN_EXTRA();
+	if (typeInfo == NULL)
+	{
+		typeInfo = FN_EXTRA_ALLOC(sizeof(TypeInfo));
+		getTypeInfo(typeInfo, argtype, IOFunc_output, FN_MCXT());
+	}
+	else if (typeInfo->type != argtype)
+	{
+		getTypeInfo(typeInfo, argtype, IOFunc_output, FN_MCXT());
+	}
+
+	target_type = decide_json_type(argtype, typeInfo->typcategory);
+
+	/*
+	 * If NULL argument is given, return the string "null", not actual NULL.
+	 * The null check is done after the type check so users can identify type
+	 * bugs earlier.
+	 */
+	if (PG_ARGISNULL(0))
+		PG_RETURN_JSON_P(cstring_to_text("null"));
+
+	PG_RETURN_JSON_P(utf8_cstring_to_text(
+				  datum_to_json(PG_GETARG_DATUM(0), typeInfo, target_type)));
+}
+
+/*
+ * decide_json_type
+ *
+ * Given a type and its corresponding typcategory, determine what
+ * JSON type to encode it to.  If it's not possible to encode the type
+ * (or if support for the type isn't implemented yet), report an error.
+ */
+static json_type
+decide_json_type(Oid type, char category)
+{
+	switch (category)
+	{
+		case 'S':
+			return JSON_STRING;
+		case 'P':
+			if (type == CSTRINGOID)
+				return JSON_STRING;
+			goto invalid;
+		case 'N':
+			if (type == CASHOID)
+				goto invalid;
+			return JSON_NUMBER;
+		case 'B':
+			if (type == BOOLOID)
+				return JSON_BOOL;
+			goto invalid;
+		case 'A':
+			return JSON_ARRAY;
+		default:
+			goto invalid;
+	}
+
+invalid:
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 errmsg("to_json cannot convert %s to JSON", format_type_be(type))));
+	return JSON_INVALID;
+}
+
+/*
+ * datum_to_json
+ *	  Converts a datum to a UTF-8-encoded JSON string.
+ *
+ *	  typeInfo comes from the getTypeInfo function, and
+ *	  target_type comes from decide_json_type .
+ *
+ *	  See to_json and array_to_json for examples of
+ *	  how to invoke this function.
+ */
+static const char *
+datum_to_json(Datum datum, TypeInfo *typeInfo, json_type target_type)
+{
+	char	   *cstring;
+	char	   *cstring_utf8;
+	JSON	   *node;
+	char	   *encoded_utf8;
+
+	switch (target_type)
+	{
+		case JSON_STRING:
+		case JSON_NUMBER:
+			cstring = OutputFunctionCall(&typeInfo->proc, datum);
+			cstring_utf8 = server_to_utf8(cstring, strlen(cstring));
+
+			if (cstring != cstring_utf8)
+				pfree(cstring);
+
+			if (target_type == JSON_STRING)
+				node = json_mkstring(cstring_utf8, strlen(cstring_utf8));
+			else
+				node = json_mknumber(cstring_utf8, strlen(cstring_utf8));
+
+			encoded_utf8 = json_encode(node, 0);
+			if (encoded_utf8 == NULL)
+			{
+				/*
+				 * Currently, we assume that output conversion for all types
+				 * in category 'N' (excluding CASH) will, by coincidence,
+				 * always give a valid JSON-encoded number.
+				 *
+				 * If this error message appears, it means the assumption is
+				 * incorrect, and we need to either blacklist the type in
+				 * decide_json_type (like we did with CASH), or add code that
+				 * can parse the number type.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_INTERNAL_ERROR),
+						 errmsg("to_json failed to convert %s \"%s\" to a valid JSON %s.\n",
+								format_type_be(typeInfo->type),
+								cstring,
+						 target_type == JSON_STRING ? "string" : "number")));
+			}
+
+			return encoded_utf8;
+
+		case JSON_BOOL:
+			return DatumGetBool(datum) ? "true" : "false";
+
+		case JSON_ARRAY:
+			return array_to_json(datum);
+
+		default:
+			Assert(false);
+			return NULL;
+	}
+}
+
+/*
+ * array_to_json
+ *	  Converts a PostgreSQL array datum to a UTF-8-encoded JSON string.
+ *
+ *	  Note: We assume that any type with typcategory = 'A'
+ *			is compatible with array_out.
+ */
+static const char *
+array_to_json(Datum datum)
+{
+	ArrayType  *v = DatumGetArrayTypeP(datum);
+	Oid			element_type = ARR_ELEMTYPE(v);
+	TypeInfo	element_typeinfo;
+	json_type	target_type;
+	int		   *dim;
+	int			ndim;
+	int			nitems;
+	int			i;
+	char	   *s;
+	bits8	   *bitmap;
+	int			bitmask;
+	Datum		elt;
+	const char **values;
+	const char *ret;
+
+	/*
+	 * todo: Consider caching the TypeInfo of array items (which we're
+	 * computing now) in the fcinfo->flinfo->fn_mcxt of to_json like we do
+	 * with the TypeInfo of the array itself.
+	 */
+	getTypeInfo(&element_typeinfo, element_type, IOFunc_output, CurrentMemoryContext);
+
+	target_type = decide_json_type(element_type, element_typeinfo.typcategory);
+
+	ndim = ARR_NDIM(v);
+	dim = ARR_DIMS(v);
+	nitems = ArrayGetNItems(ndim, dim);
+
+	/*
+	 * Unfortunately, we can't make SELECT
+	 * to_json(ARRAY[[],[],[]]::integer[][]); yield '[[],[],[]]' because ndim
+	 * is set to 0 when the array has no items.
+	 */
+	if (nitems <= 0)
+		return "[]";
+
+	values = palloc(nitems * sizeof(const char *));
+
+	s = ARR_DATA_PTR(v);
+	bitmap = ARR_NULLBITMAP(v);
+	bitmask = 1;
+
+	for (i = 0; i < nitems; i++)
+	{
+		if (bitmap != NULL && (*bitmap & bitmask) == 0)
+		{
+			values[i] = NULL;
+		}
+		else
+		{
+			elt = fetch_att(s, element_typeinfo.typbyval, element_typeinfo.typlen);
+			s = att_addlength_datum(s, element_typeinfo.typlen, elt);
+			s = (char *) att_align_nominal(s, element_typeinfo.typalign);
+
+			values[i] = datum_to_json(elt, &element_typeinfo, target_type);
+		}
+
+		if (bitmap != NULL)
+		{
+			bitmask <<= 1;
+			if (bitmask == 0x100)
+			{
+				bitmap++;
+				bitmask = 1;
+			}
+		}
+	}
+
+	ret = build_array_string(values, dim, ndim);
+
+	pfree(values);
+
+	return ret;
+}
+
+/*
+ * build_array_string
+ *	  Takes a multidimensional array of (JSON-formatted) strings and
+ *	  converts it to JSON by wrapping the strings in array brackets and commas.
+ */
+static const char *
+build_array_string(const char **values, const int *dim, int ndim)
+{
+	StringInfoData string[1];
+
+	initStringInfo(string);
+	build_array_string_recurse(string, &values, dim, ndim);
+
+	return string->data;
+}
+
+static void
+build_array_string_recurse(StringInfo string, const char ***values,
+						   const int *dim, int ndim)
+{
+	int			i,
+				count = dim[0];
+
+	appendStringInfoChar(string, '[');
+
+	for (i = 0; i < count; i++)
+	{
+		if (ndim > 1)
+		{
+			build_array_string_recurse(string, values, dim + 1, ndim - 1);
+		}
+		else
+		{
+			const char *value = *(*values)++;
+
+			if (value != NULL)
+				appendStringInfoString(string, value);
+			else
+				appendStringInfoString(string, "null");
+		}
+
+		if (i + 1 < count)
+			appendStringInfoChar(string, ',');
+	}
+
+	appendStringInfoChar(string, ']');
+}
diff --git a/contrib/json/json_op.c b/contrib/json/json_op.c
new file mode 100644
index 0000000..f0a3ed2
--- /dev/null
+++ b/contrib/json/json_op.c
@@ -0,0 +1,253 @@
+/*-------------------------------------------------------------------------
+ *
+ * json_op.c
+ *	  Manipulation procedures for JSON data type.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "json.h"
+#include "jsonpath.h"
+#include "util.h"
+
+static EnumLabel enum_labels[JSON_TYPE_COUNT] =
+{
+	{JSON_NULL, "null"},
+	{JSON_STRING, "string"},
+	{JSON_NUMBER, "number"},
+	{JSON_BOOL, "bool"},
+	{JSON_OBJECT, "object"},
+	{JSON_ARRAY, "array"}
+};
+
+/* json_validate(text).  Renamed to avoid clashing
+ * with the C function json_validate. */
+PG_FUNCTION_INFO_V1(json_validate_f);
+Datum		json_validate_f(PG_FUNCTION_ARGS);
+Datum
+json_validate_f(PG_FUNCTION_ARGS)
+{
+	char	   *string;
+	bool		ret;
+
+	string = text_to_utf8_cstring(PG_GETARG_JSON_P(0));
+	ret = json_validate(string);
+	pfree(string);
+
+	PG_RETURN_BOOL(ret);
+}
+
+/* json_type(json).  Renamed to avoid clashing
+ * with the typedef enum of the same name. */
+PG_FUNCTION_INFO_V1(json_get_type);
+Datum		json_get_type(PG_FUNCTION_ARGS);
+Datum
+json_get_type(PG_FUNCTION_ARGS)
+{
+	jsontype   *t = PG_GETARG_JSON_P(0);
+	json_type	type;
+	Oid		   *label_oids;
+
+	/*
+	 * No need to convert to UTF-8 before calling json_text_type, as it looks
+	 * solely at ASCII characters.
+	 */
+	type = json_text_type(VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
+
+	if (!json_type_is_valid(type))
+		report_corrupt_json();
+
+	label_oids = FN_EXTRA();
+	if (label_oids == NULL)
+	{
+		label_oids = FN_EXTRA_ALLOC(JSON_TYPE_COUNT * sizeof(Oid));
+		getEnumLabelOids("json_type_t", enum_labels, label_oids, JSON_TYPE_COUNT);
+	}
+
+	PG_RETURN_OID(label_oids[type]);
+}
+
+PG_FUNCTION_INFO_V1(json_condense);
+Datum		json_condense(PG_FUNCTION_ARGS);
+Datum
+json_condense(PG_FUNCTION_ARGS)
+{
+	char	   *string;
+	JSON	   *json;
+	char	   *condensed;
+
+	string = text_to_utf8_cstring(PG_GETARG_JSON_P(0));
+	json = json_decode(string);
+	condensed = json_encode(json, 0);
+
+	if (condensed == NULL)
+		report_corrupt_json();
+
+	PG_RETURN_JSON_P(utf8_cstring_to_text(condensed));
+}
+
+/*
+ * json_path_base
+ *	  Given the function call info of a PostgreSQL function
+ *	  with arguments (JSON, jsonpath TEXT [, ...]),
+ *	  match the JSONPath string given in the second argument
+ *	  against the JSON tree given in the first argument.
+ *
+ *	  This returns a List of JPRef* items, just like jp_match.
+ *	  jpref_encode() is used to convert them to JSON-formatted strings.
+ */
+static List *
+json_path_base(FunctionCallInfo fcinfo)
+{
+	char	   *json_string = text_to_utf8_cstring(PG_GETARG_JSON_P(0));
+	char	   *path_string = text_to_utf8_cstring(PG_GETARG_TEXT_PP(1));
+	JSONPath   *jpath = jp_parse(path_string);
+	JSON	   *json = json_decode(json_string);
+	List	   *result_list;
+
+	if (jpath == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid JSONPath expression")));
+	if (json == NULL)
+		report_corrupt_json();
+
+	result_list = jp_match(jpath, json);
+
+	return result_list;
+}
+
+PG_FUNCTION_INFO_V1(json_get);
+Datum		json_get(PG_FUNCTION_ARGS);
+Datum
+json_get(PG_FUNCTION_ARGS)
+{
+	List	   *result_list = json_path_base(fcinfo);
+	ListCell   *result;
+	char	   *rs_json;
+	jsontype   *result_vardata;
+	int			length = list_length(result_list);
+
+	if (length == 0)
+	{
+		PG_RETURN_NULL();
+	}
+	else if (length == 1)
+	{
+		result = list_head(result_list);
+		rs_json = jpref_encode(lfirst(result));
+
+		Assert(rs_json != NULL);
+		Assert(json_validate(rs_json) == true);
+
+		result_vardata = (jsontype *) utf8_cstring_to_text(rs_json);
+
+		pfree(rs_json);
+
+		PG_RETURN_JSON_P(result_vardata);
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("JSONPath expression matched multiple results")));
+		PG_RETURN_NULL();
+	}
+}
+
+PG_FUNCTION_INFO_V1(json_set);
+Datum		json_set(PG_FUNCTION_ARGS);
+Datum
+json_set(PG_FUNCTION_ARGS)
+{
+	char	   *json_string = text_to_utf8_cstring(PG_GETARG_JSON_P(0));
+	char	   *path_string = text_to_utf8_cstring(PG_GETARG_TEXT_PP(1));
+	char	   *rvalue_string = text_to_utf8_cstring(PG_GETARG_JSON_P(2));
+	JSONPath   *jpath = jp_parse(path_string);
+	JSON	   *json = json_decode(json_string);
+	JSON	   *rvalue = json_decode(rvalue_string);
+	char	   *result;
+	jsontype   *result_text;
+
+	if (jpath == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid JSONPath expression")));
+
+	if (json == NULL || rvalue == NULL)
+		report_corrupt_json();
+
+	jp_set(jpath, json, rvalue);
+
+	result = json_encode(json, JSONOPT_USE_ORIG | JSONOPT_NO_TRIM);
+	result_text = utf8_cstring_to_text(result);
+
+	pfree(result);
+
+	PG_RETURN_JSON_P(result_text);
+}
+
+PG_FUNCTION_INFO_V1(json_path);
+Datum		json_path(PG_FUNCTION_ARGS);
+Datum
+json_path(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+	ListCell   *result;
+
+	/* TODO: consider returning the entire result set in one call. */
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		MemoryContext oldcontext;
+		List	   *results;
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		results = json_path_base(fcinfo);
+		funcctx->user_fctx = list_head(results);
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+	result = funcctx->user_fctx;
+
+	if (result != NULL)
+	{
+		char	   *json_string;
+		jsontype   *json_text;
+
+		json_string = jpref_encode(lfirst(result));
+		Assert(json_string != NULL);
+		Assert(json_validate(json_string) == true);
+
+		json_text = utf8_cstring_to_text(json_string);
+
+		funcctx->user_fctx = lnext(result);
+
+		SRF_RETURN_NEXT(funcctx, JSONPGetDatum(json_text));
+	}
+
+	SRF_RETURN_DONE(funcctx);
+}
+
+PG_FUNCTION_INFO_V1(parse_json_path);
+Datum		parse_json_path(PG_FUNCTION_ARGS);
+Datum
+parse_json_path(PG_FUNCTION_ARGS)
+{
+	char	   *string = text_to_utf8_cstring(PG_GETARG_TEXT_PP(0));
+	JSONPath   *jpath = jp_parse(string);
+	char	   *normalized;
+
+	if (jpath == NULL)
+		PG_RETURN_NULL();
+
+	normalized = jp_show(jpath);
+	PG_RETURN_TEXT_P(utf8_cstring_to_text(normalized));
+}
diff --git a/contrib/json/jsonpath.c b/contrib/json/jsonpath.c
new file mode 100644
index 0000000..edfa6e8
--- /dev/null
+++ b/contrib/json/jsonpath.c
@@ -0,0 +1,596 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.c
+ *	  JSONPath implementation routines for JSON data type support.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "jsonpath.h"
+#include "util.h"
+
+#include <ctype.h>
+
+/* NB: These macros evaluate their argument multiple times. */
+
+#define isletter(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
+ /* isalpha() is locale-specific.  This simply matches [A-Za-z] . */
+#define isextended(c) ((unsigned char)(c) > 127)
+
+/* Note that Unicode characters are allowed in identifiers. */
+#define identifier_start(c) (isletter(c) || (c) == '_' || (c) == '$' || isextended(c))
+#define identifier_char(c) (identifier_start(c) || isdigit(c))
+
+#define integer_start(c) (isdigit(c) || (c) == '+' || (c) == '-')
+
+/*
+ * In a valid JSONPath list, the first element is always of type JP_ROOT.
+ * This element is used so an otherwise empty JSONPath list won't be NULL.
+ * This allows us to use NULL to indicate an invalid JSONPath.
+ *
+ * This function returns the first cell,
+ * making sure it is of type JP_ROOT.
+ */
+static ListCell *
+jp_root(JSONPath * jp)
+{
+	ListCell   *cell;
+	jp_element *elem;
+
+	Assert(jp != NULL);
+
+	cell = list_head(jp);
+	elem = lfirst(cell);
+	Assert(elem->type == JP_ROOT);
+
+	return cell;
+}
+
+/*
+ * This function returns the second cell of a JSONPath list
+ * (the first cell after the JP_ROOT).
+ */
+static ListCell *
+jp_head(JSONPath * jp)
+{
+	return lnext(jp_root(jp));
+}
+
+/*
+ * Note that skip_spaces differs from skip_whitespace in json.c
+ * in that this function treats '\f' and '\v' as whitespace.
+ * This is because JSON does not accept these characters as
+ * whitespace, but since this is JSONPath,
+ * we can do whatever we want here :-)
+ */
+static void
+skip_spaces(const char **sp)
+{
+	const char *s = *sp;
+
+	while (isspace(*s))
+		s++;
+	*sp = s;
+}
+
+static jp_element *
+mkElement(jp_element_type type, bool rd)
+{
+	jp_element *elem = palloc0(sizeof(*elem));
+
+	elem->type = type;
+	elem->recursive_descent = rd;
+	return elem;
+}
+
+static jp_element *
+mkRoot(void)
+{
+	jp_element *elem = mkElement(JP_ROOT, false);
+
+	return elem;
+}
+
+static jp_element *
+mkWildcard(bool rd)
+{
+	jp_element *elem = mkElement(JP_WILDCARD, rd);
+
+	return elem;
+}
+
+static jp_element *
+mkIndexSubscript(long index, bool rd)
+{
+	jp_element *elem = mkElement(JP_INDEX_SUBSCRIPT, rd);
+
+	elem->data.index = index;
+	return elem;
+}
+
+static jp_element *
+mkKeySubscript(char *key, size_t length, bool rd)
+{
+	jp_element *elem = mkElement(JP_KEY_SUBSCRIPT, rd);
+
+	elem->data.key.ptr = key;
+	elem->data.key.length = length;
+	return elem;
+}
+
+static jp_element *
+mkCallChar(long index, bool rd)
+{
+	jp_element *elem = mkElement(JP_CALL_CHAR, rd);
+
+	elem->data.index = index;
+	return elem;
+}
+
+static JPRef *
+mkRef(JPRefType type)
+{
+	JPRef	   *ref = palloc0(sizeof(*ref));
+
+	ref->type = type;
+	return ref;
+}
+
+static JPRef *
+mkRefNode(JSON * node)
+{
+	JPRef	   *ref = mkRef(JP_REF_NODE);
+
+	ref->u.node = node;
+	return ref;
+}
+
+static JPRef *
+mkRefChar(const char *bytes, size_t length)
+{
+	JPRef	   *ref = mkRef(JP_REF_CHAR);
+	ref->u.chr. bytes = bytes;
+	ref->u.chr. length = length;
+
+	return ref;
+}
+
+/*
+ * jp_show
+ *	  Unparse a JSONPath expression.  This is used by parse_json_path
+ *	  to stringify successfully parsed JSONPaths.
+ */
+char *
+jp_show(JSONPath * jp)
+{
+	StringInfoData string[1];
+	ListCell   *cell;
+	jp_element *elem;
+	bool		rd;
+	char	   *tmp;
+
+	initStringInfo(string);
+
+	foreach(cell, jp)
+	{
+		elem = lfirst(cell);
+		rd = elem->recursive_descent;
+
+		switch (elem->type)
+		{
+			case JP_ROOT:
+				appendStringInfoChar(string, '$');
+				break;
+			case JP_WILDCARD:
+				appendStringInfoString(string, rd ? "..[*]" : "[*]");
+				break;
+			case JP_INDEX_SUBSCRIPT:
+				appendStringInfo(string, "%s[%ld]", rd ? ".." : "", elem->data.index);
+				break;
+			case JP_KEY_SUBSCRIPT:
+				tmp = json_encode_string(elem->data.key.ptr, elem->data.key.length, '"', false);
+				appendStringInfo(string, "%s[%s]", rd ? ".." : "", tmp);
+				pfree(tmp);
+				break;
+			case JP_CALL_CHAR:
+				appendStringInfo(string, "%s(%ld)", rd ? "..char" : ".char", elem->data.index);
+				break;
+			default:
+				Assert(false);
+		}
+	}
+
+	return string->data;
+}
+
+/*
+ * Parse a long starting at *s .
+ *
+ * On success, return true and update *s to point to the end of the number.
+ * On failure, return false and leave *s untouched.
+ */
+static bool
+parse_long(const char **s, long *out)
+{
+	const char *p = *s;
+
+	errno = 0;
+	*out = strtol(*s, (char **) &p, 10);
+	if (p <= *s || errno != 0)
+		return false;
+
+	*s = p;
+	return true;
+}
+
+/*
+ * jp_parse
+ *	  Parse a JSONPath expression (into a List of jp_element items).
+ *
+ *	  TODO: Get rid of all those gotos.  The parser uses constant space,
+ *			so there's no chance of a stack overflow anyway.
+ */
+JSONPath *
+jp_parse(const char *pattern)
+{
+	JSONPath   *jp = NIL;
+	const char *s = pattern;
+	const char *start;
+	const char *end;
+	bool		recursive_descent = false;
+	bool		bracket = false;
+	const char *err_msg = NULL;
+	long		index;
+	char	   *key;
+	size_t		key_length;
+
+	skip_spaces(&s);
+
+	/* pattern may not be empty */
+	if (*s == '\0')
+		return NULL;
+
+	jp = lappend(jp, mkRoot());
+
+	if (*s == '$')
+	{
+		s++;
+		goto begin_element;
+	}
+	else if (*s != '.')
+	{
+		goto dot_subscript;
+		/* implicit '.' at beginning */
+	}
+
+begin_element:
+	skip_spaces(&s);
+begin_element_noskip:
+
+	recursive_descent = false;
+	bracket = false;
+
+	if (*s == '\0')
+		goto end;
+	if (s[0] == '.' && s[1] == '.')
+	{
+		recursive_descent = true;
+		s += 2;
+		goto dot_subscript;
+	}
+	if (s[0] == '.')
+	{
+		s++;
+		goto dot_subscript;
+	}
+	if (s[0] == '[')
+	{
+		s++;
+		goto bracket_subscript;
+	}
+
+	goto failed;
+
+next_element:
+	if (bracket)
+	{
+		skip_spaces(&s);
+		if (*s != ']')
+			goto failed;
+		s++;
+	}
+	goto begin_element;
+
+dot_subscript:
+	skip_spaces(&s);
+
+	if (*s == '*')
+		goto wildcard;
+	if (integer_start(*s))
+		goto integer;
+	if (identifier_start(*s))
+		goto identifier;
+	if (*s == '"' || *s == '\'')
+		goto string;
+	if (*s == '[')
+	{
+		s++;
+		goto bracket_subscript;
+	}
+
+	goto failed;
+
+bracket_subscript:
+	skip_spaces(&s);
+
+	bracket = true;
+
+	if (*s == '*')
+		goto wildcard;
+	if (integer_start(*s))
+		goto integer;
+	if (identifier_start(*s))
+	{
+		err_msg = "Identifiers may not be bracketed.  This syntax is reserved for future use.";
+		goto failed;
+	}
+	if (*s == '"' || *s == '\'')
+		goto string;
+
+	goto failed;
+
+wildcard:
+	s++;
+	jp = lappend(jp, mkWildcard(recursive_descent));
+	goto next_element;
+
+integer:
+	if (!parse_long(&s, &index))
+		goto failed;
+
+	jp = lappend(jp, mkIndexSubscript(index, recursive_descent));
+	goto next_element;
+
+identifier:
+	start = s;
+	while (identifier_char(*s))
+		s++;
+	end = s;
+
+	skip_spaces(&s);
+
+	if (*s == '(')
+	{
+		if (end - start == 4 && memcmp(start, "char", 4) == 0)
+		{
+			s++;
+			skip_spaces(&s);
+			goto call_char;
+		}
+
+		goto failed;
+	}
+
+	key = pnstrdup(start, end - start);
+	key_length = end - start;
+
+	jp = lappend(jp, mkKeySubscript(key, key_length, recursive_descent));
+	goto begin_element_noskip;
+
+string:
+	key = json_decode_string(&s, &key_length, false);
+	if (key == NULL)
+		goto failed;
+
+	jp = lappend(jp, mkKeySubscript(key, key_length, recursive_descent));
+	goto next_element;
+
+call_char:
+	if (!parse_long(&s, &index))
+		goto failed;
+
+	skip_spaces(&s);
+
+	if (*s++ != ')')
+		goto failed;
+
+	jp = lappend(jp, mkCallChar(index, recursive_descent));
+	goto begin_element;
+
+end:
+	return jp;
+
+failed:
+	return NULL;
+}
+
+/*
+ * Currently, a lot of JPRef nodes are allocated just to pass JSON pointers
+ * to match_recurse.  If this becomes a memory/performance issue in the future,
+ * JPRef could merged with JSON by adding JPRef's specialty types to the
+ * json_type enum and JSON union.	JPRef is currently not merged with
+ * JSON in an attempt to keep the codebase tidy and easier to extend.
+ */
+static void match_recurse(void on_match(void *ctx, JPRef * ref), void *ctx,
+									  ListCell *path, JPRef * ref)
+{
+	jp_element *elem;
+	JSON	   *json,
+			   *child;
+
+	if (path == NULL)
+	{
+		/* The end of the JSONPath list is the "accept" state. */
+		on_match(ctx, ref);
+		return;
+	}
+
+	elem = lfirst(path);
+
+	if (ref->type == JP_REF_NODE)
+		json = ref->u.node;
+	else
+		json = NULL;
+
+	switch (elem->type)
+	{
+		case JP_WILDCARD:
+			if (json != NULL)
+			{
+				json_foreach(child, json)
+					match_recurse(on_match, ctx, lnext(path), mkRefNode(child));
+			}
+			break;
+
+		case JP_INDEX_SUBSCRIPT:
+			if (json != NULL && json->type == JSON_ARRAY &&
+				elem->data.index >= 0 &&
+				(size_t) elem->data.index < json->v.children.count)
+			{
+				size_t		i;
+
+				for (child = json->v.children.head, i = 0;
+					 child != NULL && i < (size_t) elem->data.index;
+					 child = child->next, i++)
+				{
+				}
+
+				/*
+				 * If this fails, it means json->v.children.count was greater
+				 * than the actual number of children.
+				 */
+				Assert(i == elem->data.index && child != NULL);
+
+				match_recurse(on_match, ctx, lnext(path), mkRefNode(child));
+			}
+			break;
+
+		case JP_KEY_SUBSCRIPT:
+			if (json != NULL && json->type == JSON_OBJECT)
+			{
+				json_foreach(child, json)
+				{
+					if (child->key != NULL &&
+						child->key_length == elem->data.key.length &&
+						memcmp(child->key, elem->data.key.ptr, child->key_length) == 0)
+					{
+						match_recurse(on_match, ctx, lnext(path), mkRefNode(child));
+					}
+				}
+			}
+			break;
+
+		case JP_CALL_CHAR:
+			if (json != NULL && json->type == JSON_STRING &&
+				elem->data.index >= 0)
+			{
+				const char *sub_start;
+				size_t		sub_bytes;
+				size_t		sub_length;
+
+				sub_length = utf8_substring(
+								   json->v.string.str, json->v.string.length,
+											elem->data.index, 1,
+											&sub_start, &sub_bytes);
+
+				if (sub_length == 1)
+					match_recurse(on_match, ctx, lnext(path), mkRefChar(sub_start, sub_bytes));
+			}
+			else if (ref->type == JP_REF_CHAR && elem->data.index == 0)
+			{
+				/* char(0) on a character yields itself. */
+				match_recurse(on_match, ctx, lnext(path), ref);
+			}
+			break;
+
+		default:;
+	}
+
+	if (elem->recursive_descent && json != NULL)
+	{
+		json_foreach(child, json)
+		{
+			if (!child->jp_changed)
+				match_recurse(on_match, ctx, path, mkRefNode(child));
+		}
+	}
+}
+
+static void
+jp_match_callback(List **results, JPRef * ref)
+{
+	*results = lappend(*results, ref);
+}
+
+/*
+ * jp_match
+ *	  Match a parsed JSONPath expression against a JSON tree,
+ *	  yielding a List of JPRef* items.
+ *
+ *	  To convert the JPRef* items to JSON-formatted strings, use jpref_encode.
+ */
+List *
+jp_match(JSONPath * jp, JSON * json)
+{
+	ListCell   *lc = jp_head(jp);
+	List	   *results = NIL;
+
+	match_recurse((void *) jp_match_callback, &results, lc, mkRefNode(json));
+
+	return results;
+}
+
+static void
+jp_set_callback(JSON * value, JPRef * ref)
+{
+	switch (ref->type)
+	{
+		case JP_REF_NODE:
+			json_replace_value(ref->u.node, value);
+			ref->u.node->jp_changed = true;
+			break;
+
+		default:;				/* Do nothing if ref is immutable. */
+	}
+}
+
+/*
+ * jp_set
+ *	  Set all elements that match a parsed JSONPath expression
+ *	  in a JSON tree to a new value.
+ *
+ *	  Note that jp_set uses json_replace_value so it doesn't have to deep-copy
+ *	  on every assignment if @value is a tree.	This means that parent pointers
+ *	  of the resulting tree will not be trustworthy.
+ */
+void
+jp_set(JSONPath * jp, JSON * json, JSON * value)
+{
+	ListCell   *lc = jp_head(jp);
+
+	match_recurse((void *) jp_set_callback, value, lc, mkRefNode(json));
+}
+
+/*
+ * jpref_encode
+ *	  Convert a JPRef to a JSON-formatted string.
+ */
+char *
+jpref_encode(JPRef * ref)
+{
+	switch (ref->type)
+	{
+		case JP_REF_NODE:
+			return json_encode(ref->u.node, JSONOPT_USE_ORIG);
+
+		case JP_REF_CHAR:
+			return json_encode_string(ref->u.chr.bytes, ref->u.chr.length, '"', false);
+
+		default:
+			Assert(false);
+			return NULL;
+	}
+}
diff --git a/contrib/json/jsonpath.h b/contrib/json/jsonpath.h
new file mode 100644
index 0000000..4be498d
--- /dev/null
+++ b/contrib/json/jsonpath.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.h
+ *	  JSONPath implementation routines for JSON data type support.
+ *
+ *	  The "JSONPath" implemented here is similar to, but not exactly the same as,
+ *	  the JSONPath described at http://goessner.net/articles/JsonPath/ .
+ *	  The main differences are stronger subscripting rules, special methods
+ *	  via function-call notation (currently, the only one provided is .char()),
+ *	  and that the '$' at the beginning of a JSONPath expression is optional.
+ *	  Also, array indices as a set (e.g. [0,1]) and filters/scripts are currently
+ *	  not implemented.	Array indices are a planned feature.  True filters
+ *	  would require an honest-to-goodness JavaScript engine, so perhaps
+ *	  someone will write a module for that in the future.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef JSONPATH_H
+#define JSONPATH_H
+
+#include "json.h"
+
+#include "nodes/pg_list.h"
+
+typedef enum
+{
+	JP_ROOT,
+	JP_WILDCARD,
+	JP_INDEX_SUBSCRIPT,
+	JP_KEY_SUBSCRIPT,
+	JP_CALL_CHAR
+}	jp_element_type;
+
+/*
+ * A jp_element is a single piece of a JSONPath expression
+ * (e.g. [3], .foo, .char(3), ..*).  It represents subscripting
+ * down one level in a JSON tree, or invoking a special method
+ * (like .char() ).  However, if recursive_descent is set to true,
+ * the subscript applies to a value and all of its descendants.
+ */
+typedef struct
+{
+	jp_element_type type;
+
+	union
+	{
+		long		index;
+		struct
+		{
+			char	   *ptr;
+			size_t		length;
+		}			key;
+	}			data;
+
+	/* If element was preceded by ".." in pattern */
+	bool		recursive_descent;
+}	jp_element;
+
+typedef enum
+{
+	JP_REF_NODE,
+	JP_REF_CHAR
+}	JPRefType;
+
+/*
+ * A JPRef is a reference to some part of a JSON tree,
+ * typically a value (but not always).
+ *
+ * JPRef* is really a "subclass" of JSON* .  In the future, this structure
+ * will likely be merged into the JSON structure to improve performance.
+ */
+typedef struct
+{
+	JPRefType	type;
+
+	union
+	{
+		JSON	   *node;
+
+		struct
+		{
+			const char *bytes;
+			size_t		length;
+		} chr;
+	}			u;
+}	JPRef;
+
+typedef List /* jp_element* */ JSONPath;
+
+JSONPath   *jp_parse(const char *pattern);
+char	   *jp_show(JSONPath * jp);
+
+List /* JPRef* */ *jp_match(JSONPath * jp, JSON * json);
+void		jp_set(JSONPath * jp, JSON * json, JSON * value);
+
+/* Returns the JSON encoding of the given reference. */
+char	   *jpref_encode(JPRef * ref);
+
+#endif
diff --git a/contrib/json/sql/array_to_json.sql b/contrib/json/sql/array_to_json.sql
new file mode 100644
index 0000000..ba07ea4
--- /dev/null
+++ b/contrib/json/sql/array_to_json.sql
@@ -0,0 +1,45 @@
+SELECT to_json(ARRAY[1,2,3]);
+SELECT to_json(ARRAY[[1],[2],[3]]);
+SELECT to_json(ARRAY[[1,2],[2],[3]]);
+SELECT to_json(ARRAY[[1,2],[3,4],[5,6]]);
+SELECT to_json(ARRAY[[],[],[]]);
+SELECT to_json(ARRAY[[],[],[]]::integer[][]);
+SELECT to_json(ARRAY['hello']);
+SELECT to_json(ARRAY['hello', 'world']);
+SELECT to_json(ARRAY['hello', 'world', 5]);
+SELECT to_json(ARRAY['hello', 'world', 5]::text[]);
+SELECT to_json(ARRAY[ARRAY['hello', 'world', 5]]::text[][]);
+SELECT to_json(ARRAY[]);
+SELECT to_json(ARRAY[]::text[]);
+SELECT to_json(ARRAY['hello', 'world', null]);
+SELECT to_json(ARRAY['hello', 'world', null, 5]);
+SELECT to_json(ARRAY['hello', 'world', null, '5']);
+SELECT to_json(ARRAY[null]);
+SELECT to_json(ARRAY[null, null]);
+SELECT to_json(ARRAY[[null],[null],[null]]);
+SELECT to_json(ARRAY[[null,null],[null,null],[null,null]]);
+SELECT to_json(ARRAY[[[null,null]],[[null,null]],[[null,null]]]);
+SELECT to_json(ARRAY[[[null,null],[0,1]],[[null,null],[2,3]],[[null,null],[4,5]]]);
+SELECT to_json(ARRAY[[[null,null],[0,1]],[[null,null],[2,3]],[[null,null],[4,5]]]::int[][][]);
+SELECT to_json(array[1,2,3,4,5]::int2vector);
+SELECT to_json(array[1,2,3,4,5]::oidvector);
+SELECT to_json(array[[1,2],[3,4],5]::oidvector);
+SELECT to_json(array[[1,2],[3,4],[5]]::oidvector);
+SELECT to_json(array[[1,2],[3,4],[5,5]]::oidvector);
+SELECT to_json(array[[1,2],[3,4],[5,6]]::oidvector);
+SELECT to_json(array[[1,2],[3,4],[5,6]]::int2vector);
+SELECT to_json(array[[1,2],[3,4],[5,6000]]::int2vector);
+SELECT to_json(array[[1,2],[3,4],[5,60000]]::int2vector);
+SELECT to_json(array[[1,2],[3,4],[5,null]]::int2vector);
+SELECT to_json(array[[1,2],[null,4],[5,null]]::int2vector);
+SELECT to_json(array[[1,2],[null,4],[5,null]]::oidvector);
+SELECT to_json(array[[true,false],[false,true]]::bool[][]);
+SELECT to_json(array[[true,false],[false,true],[null,null]]::bool[][]);
+SELECT to_json('{1,2,3}');
+SELECT to_json('{1,2,3}'::text); -- should form "{1,2,3}", not build an array
+SELECT to_json('{1,2,3}'::int[]);
+SELECT to_json('{one,two,three}'::text[]);
+SELECT to_json('{one,two,three,null}'::text[]);
+SELECT to_json('{one,two,three,null,five}'::text[]);
+SELECT to_json('{null}'::text[]);
+SELECT to_json('{{null}}'::text[][]);
diff --git a/contrib/json/sql/condense.sql b/contrib/json/sql/condense.sql
new file mode 100644
index 0000000..7ad563b
--- /dev/null
+++ b/contrib/json/sql/condense.sql
@@ -0,0 +1,14 @@
+SELECT json_condense('"hello"');
+SELECT json_condense($$"hello\u266Bworld"$$);
+SELECT json_condense($$"hello\u266bworld"$$);
+SELECT json_condense($$"hello♫world"$$);
+SELECT json_condense($$      "hello world"    $$);
+SELECT json_condense($$    {  "hello" : "world"}    $$);
+SELECT json_condense($$    {  "hello" : "world", "bye": 0.0001  }    $$);
+SELECT json_condense($$    {  "hello" : "world",
+								"bye": 0.0000001
+}    $$);
+SELECT json_condense($$    {  "hello" : "world"
+,
+"bye"
+: [-0.1234e1, 12345e0]		}    $$);
diff --git a/contrib/json/sql/init.sql b/contrib/json/sql/init.sql
new file mode 100644
index 0000000..9ce0d6b
--- /dev/null
+++ b/contrib/json/sql/init.sql
@@ -0,0 +1,10 @@
+SET client_min_messages = warning;
+\set ECHO none
+\i json.sql
+
+\i sql/test_strings.sql
+CREATE TABLE valid_test_strings AS
+	SELECT string FROM test_strings WHERE json_validate(string);
+
+\set ECHO all
+RESET client_min_messages;
diff --git a/contrib/json/sql/json.sql b/contrib/json/sql/json.sql
new file mode 100644
index 0000000..1e98d6c
--- /dev/null
+++ b/contrib/json/sql/json.sql
@@ -0,0 +1,159 @@
+SELECT '[]'::JSON;
+SELECT '['::JSON;
+SELECT '[1,2,3]'::JSON;
+SELECT '[1,2,3]'::JSON::TEXT;
+SELECT '[1,2,3  ]'::JSON;
+SELECT '[1,2,3  ,4]'::JSON;
+SELECT '[1,2,3  ,4.0]'::JSON;
+SELECT '[1,2,3  ,4]'::JSON;
+SELECT 'true'::JSON;
+SELECT 'true'::TEXT::JSON;
+SELECT 'false'::JSON;
+SELECT 'null'::JSON;
+SELECT '1.1'::JSON;
+SELECT '"string"'::JSON;
+SELECT '{"key1":"value1", "key2":"value2"}'::JSON;
+SELECT '{"key1":"value1", "key2":"value2"}'::JSON;
+SELECT 15::JSON;
+
+SELECT json_type('[]');
+SELECT json_type('{}');
+SELECT json_type('true');
+SELECT json_type('false');
+SELECT json_type('null');
+
+CREATE TABLE testjson (j JSON);
+INSERT INTO testjson VALUES ('[1,2,3,4]');
+INSERT INTO testjson VALUES ('{"key":"value"}');
+INSERT INTO testjson VALUES ('{"key":"value"');
+INSERT INTO testjson VALUES ('');
+INSERT INTO testjson VALUES ('""');
+INSERT INTO testjson VALUES ('true');
+INSERT INTO testjson VALUES ('false');
+INSERT INTO testjson VALUES ('null');
+INSERT INTO testjson VALUES ('[]');
+INSERT INTO testjson VALUES ('{}');
+
+SELECT * FROM testjson;
+
+SELECT json_type(j) FROM testjson;
+
+
+-- to_json: null
+SELECT to_json(NULL);
+SELECT to_json(NULL::INT);
+SELECT to_json(NULL::INT[]);
+SELECT to_json(NULL::VOID);
+SELECT to_json(NULL::MONEY);
+SELECT to_json('null'::text); -- should yield '"null"', not 'null'
+SELECT json_type(to_json(NULL));
+SELECT json_type(to_json(NULL::INT));
+SELECT json_type(to_json(NULL::VOID));
+SELECT json_type(to_json(NULL::MONEY));
+
+-- to_json: string
+SELECT to_json('');
+SELECT json_type(to_json(''));
+SELECT to_json('string');
+SELECT json_type(to_json('string'));
+SELECT to_json('string'::VARCHAR);
+SELECT json_type(to_json('string'::VARCHAR));
+SELECT to_json('string'::VARCHAR(3));
+SELECT json_type(to_json('string'::VARCHAR(3)));
+SELECT to_json('{1,2,3}'::TEXT);
+SELECT json_type(to_json('{1,2,3}'::TEXT));
+SELECT to_json('"doubly-encoded"'::JSON);
+SELECT json_type(to_json('"doubly-encoded"'::JSON));
+SELECT to_json('"nested quotes"'::TEXT);
+SELECT json_type(to_json('"nested quotes"'::TEXT));
+SELECT to_json('"nested quotes"'::TEXT)::TEXT::JSON;
+SELECT json_type(to_json('"nested quotes"'::TEXT)::TEXT::JSON);
+SELECT to_json('h'::CHAR);
+SELECT json_type(to_json('h'::CHAR));
+SELECT to_json('hello world'::CHAR);
+SELECT json_type(to_json('hello world'::CHAR));
+SELECT to_json('hello world!'::CHAR(11));
+SELECT json_type(to_json('hello world!'::CHAR(11)));
+
+-- to_json: number
+SELECT to_json(12345);
+SELECT to_json(12345.678);
+SELECT json_type(to_json(12345));
+SELECT json_type(to_json(12345.678));
+SELECT to_json(+1.23e100::FLOAT);
+SELECT to_json('+1.23e100'::FLOAT);
+SELECT to_json(123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678901234567890);
+SELECT json_type(to_json(123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678901234567890));
+SELECT to_json('100'::MONEY);
+
+-- to_json: bool
+SELECT to_json(TRUE);
+SELECT to_json(FALSE);
+SELECT to_json(1=1);
+SELECT to_json(1=2);
+SELECT json_type(to_json(TRUE));
+SELECT json_type(to_json(FALSE));
+SELECT json_type(to_json(1=1));
+SELECT json_type(to_json(1=2));
+SELECT to_json(TRUE::TEXT)::TEXT = '"' || TRUE || '"';
+SELECT to_json(FALSE::TEXT)::TEXT = '"' || FALSE || '"';
+
+-- to_json: array
+SELECT to_json(ARRAY[1,2,3]);
+-- more tests are in array_to_json.sql
+
+-- to_json: invalid types
+SELECT to_json(row(1,2));
+SELECT to_json('127.0.0.1'::INET);
+
+
+-- from_json: null
+SELECT from_json(null);
+SELECT from_json(NULL::TEXT);
+SELECT from_json(NULL::JSON);
+
+-- from_json: string
+SELECT from_json('"valid string"');
+SELECT from_json($$ "hello\nworld" $$);
+SELECT from_json($$ "hello\u0000world" $$);
+
+-- from_json: number
+SELECT from_json('123');
+SELECT from_json('123')::INT;
+SELECT from_json('123.456')::INT;
+SELECT from_json('123.456')::FLOAT;
+SELECT from_json('123e-38');
+SELECT from_json('123e-38')::FLOAT;
+SELECT from_json('1.23e-38')::FLOAT;
+SELECT from_json('1.23e-38');
+SELECT from_json('1.23e-38')::NUMERIC;
+
+-- from_json: bool
+SELECT from_json('true')::JSON;
+SELECT from_json('true');
+SELECT from_json('true')::BOOLEAN;
+SELECT from_json('true')::BOOLEAN::JSON;
+SELECT from_json('true')::BOOLEAN::TEXT::JSON;
+SELECT from_json('false')::BOOLEAN::TEXT::JSON;
+SELECT from_json('false');
+SELECT from_json('f');
+SELECT from_json('t');
+SELECT from_json('f'::BOOLEAN::TEXT);
+SELECT from_json('f'::BOOLEAN::TEXT::JSON);
+SELECT from_json('t'::BOOLEAN::TEXT::JSON);
+
+-- from_json: object
+SELECT from_json('{"key": "value"}');
+
+-- from_json: array
+SELECT from_json('[1,2,3]');
+
+-- from_json: invalid
+SELECT from_json('invalid');
+
+CREATE TABLE sample_query_text (json JSON);
+INSERT INTO sample_query_text VALUES ($$
+"SELECT pg_catalog.quote_ident(c.relname) FROM pg_catalog.pg_class c WHERE c.relkind IN ('i') AND substring(pg_catalog.quote_ident(c.relname),1,0)='' AND pg_catalog.pg_table_is_visible(c.oid) AND c.relnamespace <> (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')\nUNION\nSELECT pg_catalog.quote_ident(n.nspname) || '.' FROM pg_catalog.pg_namespace n WHERE substring(pg_catalog.quote_ident(n.nspname) || '.',1,0)='' AND (SELECT pg_catalog.count(*) FROM pg_catalog.pg_namespace WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,0) = substring('',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) > 1\nUNION\nSELECT pg_catalog.quote_ident(n.nspname) || '.' || pg_catalog.quote_ident(c.relname) FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE c.relnamespace = n.oid AND c.relkind IN ('i') AND substring(pg_catalog.quote_ident(n.nspname) || '.' || pg_catalog.quote_ident(c.relname),1,0)='' AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,0) = substring('',1,pg_catalog.length(pg_catalog.quote_ident(n.nspname))+1) AND (SELECT pg_catalog.count(*) FROM pg_catalog.pg_namespace WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,0) = substring('',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n UNION SELECT 'ON' UNION SELECT 'CONCURRENTLY'\nLIMIT 1000"
+$$);
+
+SELECT md5(from_json(json)) FROM sample_query_text;
diff --git a/contrib/json/sql/json_get.sql b/contrib/json/sql/json_get.sql
new file mode 100644
index 0000000..1427d47
--- /dev/null
+++ b/contrib/json/sql/json_get.sql
@@ -0,0 +1,18 @@
+SELECT json_get('{"key": "value", "key2": "value2"}', 'key');
+SELECT json_get('{"key": "value", "key": "value2"}', 'key');
+
+/* These each return no result because 0 is a number
+   (indicating a numeric subscript),
+   not a string (indicating an object subscript). */
+SELECT json_get('{"0": "value", "key": "value"}', '0');
+SELECT json_get('{"0": "value", "0": "value"}', '[0]');
+SELECT json_get('{"0": "value", "0": "value"}', '.0');
+
+SELECT json_get('{"0": "value", "1": "value"}', '["0"]');
+SELECT json_get('{"0": "value", "0": "value"}', '["0"]');
+
+SELECT json_get('[0,1,2,3]', '0');
+SELECT json_get('[0,1,2,3]', '"0"');
+SELECT json_get('[0,1,2,3]', '*');
+SELECT json_get('[0]', '*');
+SELECT json_get('[[0]]', '..*');
diff --git a/contrib/json/sql/json_path.sql b/contrib/json/sql/json_path.sql
new file mode 100644
index 0000000..01d972b
--- /dev/null
+++ b/contrib/json/sql/json_path.sql
@@ -0,0 +1,173 @@
+SELECT parse_json_path('..');
+SELECT parse_json_path('..*');
+SELECT parse_json_path('.*');
+SELECT parse_json_path('');
+SELECT parse_json_path('$.*');
+SELECT parse_json_path('$');
+SELECT parse_json_path('$*');
+SELECT parse_json_path('*');
+SELECT parse_json_path($$ .. [ 025  ] $$);
+SELECT parse_json_path($$ $ . 0 . 3 $$);
+SELECT parse_json_path($$ $ . 0_3 $$);
+SELECT parse_json_path($$ $ . 0.3 $$);
+SELECT parse_json_path($$ .. [ 031  ] $$);
+SELECT parse_json_path($$ $ . 0 . 3 . hello $$);
+SELECT parse_json_path($$ $ . 0 . 3 . hello . "back\\slash" $$);
+SELECT parse_json_path($$ $ . 0 . 3 . hello . ["back\\slash"] $$);
+SELECT parse_json_path($$ $ . 0 . 3 . hello . back\slash $$);
+SELECT parse_json_path($$ $ . 0 . 3 . hello . backslash $$);
+SELECT parse_json_path($$ .. [ 0x31  ] $$);
+SELECT parse_json_path($$ [ 0x31  ] $$);
+SELECT parse_json_path($$ _3 $$);
+SELECT parse_json_path($$ _3_ $$);
+SELECT parse_json_path($$ [ _3 ] $$);
+SELECT parse_json_path($$ $ _3 $$);
+SELECT parse_json_path($$ $ . _3 $$);
+SELECT parse_json_path('..["5"]');
+SELECT parse_json_path('..[5]');
+SELECT parse_json_path('..5');
+SELECT parse_json_path($$ .. [ -5  ] $$);
+SELECT parse_json_path($$ .. [ "5"  ] $$);
+SELECT parse_json_path($$ .. [ +5  ] $$);
+SELECT parse_json_path($$ .. [ 5  ] $$);
+SELECT parse_json_path($$ .. ["5"] $$);
+SELECT parse_json_path($$ ..["5"] $$);
+SELECT parse_json_path($$ [ "5" ] $$);
+SELECT parse_json_path($$ [ 5 ] $$);
+SELECT parse_json_path('."hello"');
+SELECT parse_json_path('.hello');
+SELECT parse_json_path('...["hello world"]');
+SELECT parse_json_path('..["hello world"]');
+SELECT parse_json_path('."hello world"');
+SELECT parse_json_path('.["hello world"]');
+SELECT parse_json_path('.hello world');
+SELECT parse_json_path('..["hello world"]["5"]');
+SELECT parse_json_path('..["hello world"][5]');
+SELECT parse_json_path('..["hello world"][5]..[3]..*.[*]');
+SELECT parse_json_path('..["hello world"][5]..[3]..*');
+SELECT parse_json_path('..["hello world"][5]..[3]');
+SELECT parse_json_path('..["hello world"][5]..[3]*');
+SELECT parse_json_path($$
+	.. [ '"hello"\\"world"' ]
+	[ 5 ] .. [3] . * [ * ]
+$$);
+
+select parse_json_path('$char');
+select parse_json_path('$.char');
+select parse_json_path('$.char()');
+select parse_json_path('$.char(5)');
+select parse_json_path('$.char(  -1  )');
+select parse_json_path('$.char(  -1  )  . char(0)');
+select parse_json_path('$.char(  -1  )  . char  ( 0 )  ');
+select parse_json_path('$.char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('$ . char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('$ .. char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('$ .. char(  +1  )  .. char  ( 0 )  ');
+select parse_json_path('$ . char(  +1  )  .. char  ( 0 )  ');
+select parse_json_path('$ . char(  +1  )  char  ( 0 )  ');
+select parse_json_path('$ char(  +1  )  char  ( 0 )  ');
+select parse_json_path('char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('.char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('..char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('...char(  +1  )  . char  ( 0 )  ');
+select parse_json_path('[char(5)]');
+
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(0)');
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(1)');
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(2)');
+SELECT json_path($$"⁰¹\u0000³"$$, 'char(3)');
+SELECT json_path('"0123"', 'char(-1)');
+SELECT json_path('"0123"', 'char(0)');
+SELECT json_path('"0123"', 'char(1)');
+SELECT json_path('"0123"', 'char(2)');
+SELECT json_path('"0123"', 'char(3)');
+SELECT json_path('"0123"', 'char(4)');
+SELECT json_path('"⁰¹²³"', 'char(0)');
+SELECT json_path('"⁰¹²³"', 'char(1)');
+SELECT json_path('"⁰¹²³"', 'char(2)');
+SELECT json_path('"⁰¹²³"', 'char(3)');
+SELECT json_path('[1,2,3]', '$');
+SELECT json_path('[1,2,3]', '$.+1');
+SELECT json_path('[1,2,3]', '$.-1');
+SELECT json_path('[1,2,3]', '$.0');
+SELECT json_path('[1,2,3]', '$.1');
+SELECT json_path('[1,2,3]', '$.2');
+SELECT json_path('[1,2,3]', '$.3');
+SELECT json_path('[1,2,3]', '*');
+SELECT json_path('[1,2,3]', '[0]');
+SELECT json_path('[1,2,3]', '[1]');
+SELECT json_path('[1,2,3]', '[2]');
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', $$['2']$$);
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '["0"]');
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '["1"]');
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '["2"]');
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '[0]');
+SELECT json_path('{"0": "zero", "1": "one", "2": "two"}', '[1]');
+
+-- Multiple JSONPath results are listed in BFS order
+-- (just because it's easier to implement efficiently than DFS)
+SELECT json_path('[0,1,[2, [3,4 , 5],6,[7,8],9],10]', '$..*');
+
+
+CREATE TABLE sample (json JSON);
+INSERT INTO sample VALUES ($$
+{
+	"store": {
+		"books": [
+			{
+				"title": "book 0",
+				"author": "author 0",
+				"prices": [1,2,3]
+			}, {
+				"title": "book 1",
+				"author": "author 1",
+				"prices": [4,5,6]
+			}, {
+				"title": "book 2",
+				"author": "author 2",
+				"prices": [7,8,9]
+			}
+		],
+		"toys": [
+			"Yo-yo",
+			"Boomerang",
+			"Basketball"
+		]
+	}
+}
+$$);
+
+SELECT json_path(json, 'store.books[*]') FROM sample;
+SELECT json_path(json, 'store.books[*].title') FROM sample;
+SELECT json_path(json, 'store.books[*].author') FROM sample;
+SELECT json_path(json, 'store.books[*].prices') FROM sample;
+SELECT json_path(json, 'store.books[*].prices[*]') FROM sample;
+SELECT json_path(json, 'store.toys[*]') FROM sample;
+SELECT json_path(json, 'store.toys[*][0]') FROM sample;
+SELECT json_path(json, 'store.toys[*][1]') FROM sample;
+SELECT json_path(json, 'store.toys[*][0][0]') FROM sample;
+SELECT json_path(json, 'store.toys[*][0][1]') FROM sample;
+
+SELECT json_path(json, '..books') FROM sample;
+SELECT json_path(json, '..books[*]') FROM sample;
+SELECT json_path(json, '..title') FROM sample;
+SELECT json_path(json, '..author') FROM sample;
+SELECT json_path(json, '..prices[*]') FROM sample;
+SELECT json_path(json, '..toys[*]') FROM sample;
+SELECT json_path(json, '..toys..[*]') FROM sample;
+
+SELECT json_path(json, '..[-1]') FROM sample;
+SELECT json_path(json, '..[0]') FROM sample;
+SELECT json_path(json, '..[1]') FROM sample;
+SELECT json_path(json, '..[2]') FROM sample;
+SELECT json_path(json, '..[3]') FROM sample;
+
+SELECT json_path(json, '$') FROM sample;
+SELECT json_path(json, '..*') FROM sample;
+
+SELECT json_path(json, '..char(-1)') FROM sample;
+SELECT json_path(json, '..char(0)') FROM sample;
+SELECT json_path(json, '..char(1)') FROM sample;
+SELECT json_path(json, '..char(2)') FROM sample;
+SELECT json_path(json, '..char(3)') FROM sample;
+SELECT json_path(json, '..char(100)') FROM sample;
diff --git a/contrib/json/sql/json_set.sql b/contrib/json/sql/json_set.sql
new file mode 100644
index 0000000..71c7a4c
--- /dev/null
+++ b/contrib/json/sql/json_set.sql
@@ -0,0 +1,31 @@
+SELECT json_set('[1,2,3]', '[1]', 5::json);
+SELECT json_set('[1,2,3]', '[3]', 5::json);
+SELECT json_set('[1,2,3]', '[2]', 5::json);
+SELECT json_set('[1,2,3]', '[2]', '[0,1]'::json);
+SELECT json_set('  [ 1 ,   2,3]', '1', '5');
+SELECT json_set('  [ 1 ,   [2,3],3]', '1', '5');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[1][0]', '5');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[5][0]', '5');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[4][0]', '5');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[3][0]', '5');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[2][0]', '5');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ],3]', '[1][0]', '   5  ');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1][0]', '   5  ');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', '   5  ');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', '     5  ');
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', $$     "hello\tworld"  $$);
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][0]', $$     "hello\u0009world"  $$);
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '[1 ][*]', $$     "hello\u0009world"  $$);
+SELECT json_set('  [ 1 ,   [  2 ,   3     ]  , 3  ] ', '$', $$     "hello\u0009world"  $$);
+SELECT json_set('       [ 1 ,   [  2 ,   3     ]  , 3  ] ', '$', $$     "hello\u0009world"  $$);
+
+-- Since JavaScript doesn't do anything when you assign to a character subscript,
+-- neither will json_set.
+SELECT json_set('"hello"', '[0]', '"H"');
+SELECT json_set('"hello"', '[0][0]', '"H"');
+SELECT json_set('["hello"]', '[0][0]', '"H"');
+SELECT json_set('["hello"]', '[0][0][0]', '"H"');
+
+SELECT json_set('[0,1,2,[3,4,5],4]', '$[*]', '["set"]');
+SELECT json_set('[0,1,2,[3,4,5],4]', '$[*][*]', '["set"]');
+SELECT json_set('[0,1,2,[3,4,5],4]', '$..[*]', '["set"]');
diff --git a/contrib/json/sql/orig.sql b/contrib/json/sql/orig.sql
new file mode 100644
index 0000000..7dad00e
--- /dev/null
+++ b/contrib/json/sql/orig.sql
@@ -0,0 +1,10 @@
+
+/* Make sure creating an array literal and subscripting
+ * preserves the original string exactly
+ * (except for trimming surrounding whitespace). */
+SELECT bool_and(btrim(before, E'\t\n\r ') = after) FROM (
+	SELECT
+		string AS before,
+		json_path(('  [ ' || string || '  ] ')::json, '$.[0]')::text AS after
+	FROM valid_test_strings
+) AS subquery;
diff --git a/contrib/json/sql/test_strings.sql b/contrib/json/sql/test_strings.sql
new file mode 100644
index 0000000..074080b
--- /dev/null
+++ b/contrib/json/sql/test_strings.sql
@@ -0,0 +1,216 @@
+CREATE TABLE test_strings (string TEXT);
+INSERT INTO test_strings VALUES ($$$$);
+INSERT INTO test_strings VALUES ($$  $$);
+INSERT INTO test_strings VALUES ($$"$$);
+INSERT INTO test_strings VALUES ($$[,]$$);
+INSERT INTO test_strings VALUES ($$[)$$);
+INSERT INTO test_strings VALUES ($$[]]$$);
+INSERT INTO test_strings VALUES ($$[}$$);
+INSERT INTO test_strings VALUES ($${,}$$);
+INSERT INTO test_strings VALUES ($${]$$);
+INSERT INTO test_strings VALUES ($$["1":2]$$);
+INSERT INTO test_strings VALUES ($$[1,2,]$$);
+INSERT INTO test_strings VALUES ($$[1:2}$$);
+INSERT INTO test_strings VALUES ($${"1":2,}$$);
+INSERT INTO test_strings VALUES ($${1:2}$$);
+INSERT INTO test_strings VALUES ($${"1":2, "2.5" : [3, 4, {}, {"5": ["6"], [7 ]}]}$$);
+INSERT INTO test_strings VALUES ($${"1":2, "2.5" : [3, 4, {}, {"5": ["6"], [7]}]}$$);
+INSERT INTO test_strings VALUES ($${"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]$$);
+INSERT INTO test_strings VALUES ($${"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]]$$);
+INSERT INTO test_strings VALUES ($${"1":2, "3":4$$);
+INSERT INTO test_strings VALUES ($$"1\u2"$$);
+INSERT INTO test_strings VALUES ($$[,2]$$);
+INSERT INTO test_strings VALUES ($$"3$$);
+INSERT INTO test_strings VALUES ($$"3" "4"$$);
+INSERT INTO test_strings VALUES ($$[3[4]$$);
+INSERT INTO test_strings VALUES ($$[3[4]]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6] 7, 8 9]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6] 7, 8, 9]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6], 7, 8 9]$$);
+INSERT INTO test_strings VALUES ($${"hello":true, "bye":false, null}$$);
+INSERT INTO test_strings VALUES ($${"hello":true, "bye":false, null:null}$$);
+INSERT INTO test_strings VALUES ($$"hi$$);
+INSERT INTO test_strings VALUES ($$"hi"""$$);
+INSERT INTO test_strings VALUES ($${"hi": "bye"]$$);
+INSERT INTO test_strings VALUES ($$"\uD800\uD800"$$);
+INSERT INTO test_strings VALUES ($$"\uD800\uDBFF"$$);
+INSERT INTO test_strings VALUES ($$"\UD834\UDD1E"$$);
+INSERT INTO test_strings VALUES ($$"\uDB00"$$);
+INSERT INTO test_strings VALUES ($$"\uDB00\uDBFF"$$);
+INSERT INTO test_strings VALUES ($$"\uFFFE"$$);
+INSERT INTO test_strings VALUES ($$"\uFFFF"$$);
+INSERT INTO test_strings VALUES ($$.$$);
+INSERT INTO test_strings VALUES ($$""$$);
+INSERT INTO test_strings VALUES ($$[]$$);
+INSERT INTO test_strings VALUES ($${}$$);
+INSERT INTO test_strings VALUES ($$+.$$);
+INSERT INTO test_strings VALUES ($$0.5$$);
+INSERT INTO test_strings VALUES ($$0.e1$$);
+INSERT INTO test_strings VALUES ($${"1":{}}$$);
+INSERT INTO test_strings VALUES ($${"1":2}$$);
+INSERT INTO test_strings VALUES ($${"1":2, "2.5" : [3, 4, {}, {"5": ["6"]}]}$$);
+INSERT INTO test_strings VALUES ($${"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]}$$);
+INSERT INTO test_strings VALUES ($$1234$$);
+INSERT INTO test_strings VALUES ($$-1234$$);
+INSERT INTO test_strings VALUES ($${"1":2, "3":4}$$);
+INSERT INTO test_strings VALUES ($$+1234$$);
+INSERT INTO test_strings VALUES ($$++1234$$);
+INSERT INTO test_strings VALUES ($$123.456e14234$$);
+INSERT INTO test_strings VALUES ($$123.456e-14234$$);
+INSERT INTO test_strings VALUES ($$123.456e+14234$$);
+INSERT INTO test_strings VALUES ($$123.e-14234$$);
+INSERT INTO test_strings VALUES ($$"1\u2000"$$);
+INSERT INTO test_strings VALUES ($$"1\u20001"$$);
+INSERT INTO test_strings VALUES ($$2$$);
+INSERT INTO test_strings VALUES ($$.246e-14234$$);
+INSERT INTO test_strings VALUES ($$.2e-14234$$);
+INSERT INTO test_strings VALUES ($$3$$);
+INSERT INTO test_strings VALUES ($$.3$$);
+INSERT INTO test_strings VALUES ($$"3"$$);
+INSERT INTO test_strings VALUES ($$[3]$$);
+INSERT INTO test_strings VALUES ($$+3.$$);
+INSERT INTO test_strings VALUES ($$3.2e+1$$);
+INSERT INTO test_strings VALUES ($$[3, [4]]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5]]]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6]]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6], 7]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6], 7, 8]$$);
+INSERT INTO test_strings VALUES ($$[3, [4, [5], 6], 7, 8, 9]$$);
+INSERT INTO test_strings VALUES ($$+3.5$$);
+INSERT INTO test_strings VALUES ($$.3e$$);
+INSERT INTO test_strings VALUES ($$.3e1$$);
+INSERT INTO test_strings VALUES ($$.3e-1$$);
+INSERT INTO test_strings VALUES ($$.3e+1$$);
+INSERT INTO test_strings VALUES ($$3.e1$$);
+INSERT INTO test_strings VALUES ($$3.e+1$$);
+INSERT INTO test_strings VALUES ($$3e+1$$);
+INSERT INTO test_strings VALUES ($$.5$$);
+INSERT INTO test_strings VALUES ($$+.5$$);
+INSERT INTO test_strings VALUES ($$.5e+1$$);
+INSERT INTO test_strings VALUES ($$[ 7]$$);
+INSERT INTO test_strings VALUES ($$[7 ]$$);
+INSERT INTO test_strings VALUES ($$[7]$$);
+INSERT INTO test_strings VALUES ($$.e-14234$$);
+INSERT INTO test_strings VALUES ($$"hello"$$);
+INSERT INTO test_strings VALUES ($$["hello"]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye"]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n"]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n\r\t"]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n\r\t\b"]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n\r\t\b",true]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n\r\t\b",true , false]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n\r\t\b",true , false,    null]$$);
+INSERT INTO test_strings VALUES ($$["hello", "bye\n\r\t\v"]$$);
+INSERT INTO test_strings VALUES ($${"hello":true}$$);
+INSERT INTO test_strings VALUES ($${"hello":true, "bye":false}$$);
+INSERT INTO test_strings VALUES ($${"hello":true, "bye":false, "foo":["one","two","three"]}$$);
+INSERT INTO test_strings VALUES ($$"hi"$$);
+INSERT INTO test_strings VALUES ($$["hi"]$$);
+INSERT INTO test_strings VALUES ($$["hi", "bye"]$$);
+INSERT INTO test_strings VALUES ($${"hi": "bye"}$$);
+INSERT INTO test_strings VALUES ($$["hi", "bye", 3]$$);
+INSERT INTO test_strings VALUES ($$["hi", "bye[", 3]$$);
+INSERT INTO test_strings VALUES ($$"\u0007"$$);
+INSERT INTO test_strings VALUES ($$"\u0008"$$);
+INSERT INTO test_strings VALUES ($$"\u0009"$$);
+INSERT INTO test_strings VALUES ($$"\u0010"$$);
+INSERT INTO test_strings VALUES ($$"\u0020"$$);
+INSERT INTO test_strings VALUES ($$"\u10000"$$);
+INSERT INTO test_strings VALUES ($$"\u1234"$$);
+INSERT INTO test_strings VALUES ($$"\u99999"$$);
+INSERT INTO test_strings VALUES ($$"\ud800\udc00"$$);
+INSERT INTO test_strings VALUES ($$"\uD800\uDC00"$$);
+INSERT INTO test_strings VALUES ($$"\uD834\uDD1E"$$);
+INSERT INTO test_strings VALUES ($$"\uDBFF\uDFFF"$$);
+INSERT INTO test_strings VALUES ($$"\uFFFD"$$);
+INSERT INTO test_strings VALUES ($$"\uFFFF"$$);
+INSERT INTO test_strings VALUES ($$hello$$);
+INSERT INTO test_strings VALUES ($$[32, 1]$$);
+INSERT INTO test_strings VALUES ($$[32, $$);
+INSERT INTO test_strings VALUES ($$"\uD800\uDC00"$$);
+INSERT INTO test_strings VALUES ($$"\n"$$);
+INSERT INTO test_strings VALUES ($$"hello"$$);
+INSERT INTO test_strings VALUES ($$"hello\u0009world"$$);
+INSERT INTO test_strings VALUES ($$"hello"$$);
+INSERT INTO test_strings VALUES ($$"hello\n"$$);
+INSERT INTO test_strings VALUES ($$"hello"$$);
+INSERT INTO test_strings VALUES ($$3$$);
+INSERT INTO test_strings VALUES ($$3.$$);
+INSERT INTO test_strings VALUES ($$.3$$);
+INSERT INTO test_strings VALUES ($$0.3$$);
+INSERT INTO test_strings VALUES ($$0.3e$$);
+INSERT INTO test_strings VALUES ($$0.3e+$$);
+INSERT INTO test_strings VALUES ($$0.3e+5$$);
+INSERT INTO test_strings VALUES ($$0.3e-5$$);
+INSERT INTO test_strings VALUES ($$0.3e5$$);
+INSERT INTO test_strings VALUES ($$"hello"$$);
+INSERT INTO test_strings VALUES ($$+3$$);
+INSERT INTO test_strings VALUES ($$-3$$);
+INSERT INTO test_strings VALUES ($$-3.$$);
+INSERT INTO test_strings VALUES ($$-3.1$$);
+INSERT INTO test_strings VALUES ($$.5$$);
+INSERT INTO test_strings VALUES ($$5.$$);
+INSERT INTO test_strings VALUES ($$5.e1$$);
+INSERT INTO test_strings VALUES ($$0.5$$);
+INSERT INTO test_strings VALUES ($$.3e1$$);
+INSERT INTO test_strings VALUES ($$.3e+1$$);
+INSERT INTO test_strings VALUES ($$.3e-1$$);
+INSERT INTO test_strings VALUES ($$.3e-1 .5$$);
+INSERT INTO test_strings VALUES ($$.3e-1.5$$);
+INSERT INTO test_strings VALUES ($$.3e+1.5$$);
+INSERT INTO test_strings VALUES ($$.3e+.$$);
+INSERT INTO test_strings VALUES ($$.3e+.5$$);
+INSERT INTO test_strings VALUES ($$.3e+1.5$$);
+INSERT INTO test_strings VALUES ($$9.3e+1.5$$);
+INSERT INTO test_strings VALUES ($$9.e+1.5$$);
+INSERT INTO test_strings VALUES ($$9.e+$$);
+INSERT INTO test_strings VALUES ($$9.e+1$$);
+INSERT INTO test_strings VALUES ($$"\""$$);
+INSERT INTO test_strings VALUES ($$"\"3.5"$$);
+INSERT INTO test_strings VALUES ($$"\"."$$);
+INSERT INTO test_strings VALUES ($$"\".".$$);
+INSERT INTO test_strings VALUES ($$"\"....."$$);
+INSERT INTO test_strings VALUES ($$"\"\"\"\"""$$);
+INSERT INTO test_strings VALUES ($$["\"\"\"\"", .5]$$);
+INSERT INTO test_strings VALUES ($$[.5]$$);
+INSERT INTO test_strings VALUES ($$["\"\"\"\"", 0.5]$$);
+INSERT INTO test_strings VALUES ($$["\"\"\"\"", .5]$$);
+INSERT INTO test_strings VALUES ($$["\"\"\"\"",.5]$$);
+INSERT INTO test_strings VALUES ($$["\"",.5]$$);
+INSERT INTO test_strings VALUES ($$["\".5",.5]$$);
+INSERT INTO test_strings VALUES ($$["\".5",".5\"".5]$$);
+INSERT INTO test_strings VALUES ($$["\".5",".5\"", .5]$$);
+INSERT INTO test_strings VALUES ($$["\".5",".5\"",.5]$$);
+INSERT INTO test_strings VALUES ($$["\".5",".5\"",0.5]$$);
+INSERT INTO test_strings VALUES ($${"key":/*comment*/"value"}$$);
+INSERT INTO test_strings VALUES ($${"key":/*comment"value"}$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}/*$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}/**/$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}/***/$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}/**//$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}/**///$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}/**///----$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}#$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}#{$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}#{}$$);
+INSERT INTO test_strings VALUES ($${"key":"value"}#,$$);
+INSERT INTO test_strings VALUES ($${"key":"value"/**/, "k2":"v2"}$$);
+INSERT INTO test_strings VALUES ($$"\u0027"$$);
+INSERT INTO test_strings VALUES ($$"hello\'"$$);
+INSERT INTO test_strings VALUES ($$'hello\''$$);
+INSERT INTO test_strings VALUES ($$'hello'$$);
+INSERT INTO test_strings VALUES ($$'hell\'o'$$);
+INSERT INTO test_strings VALUES ($$'\'hello'$$);
+INSERT INTO test_strings VALUES ($$'\'hello\''$$);
+INSERT INTO test_strings VALUES ($$\'hello\'$$);
+INSERT INTO test_strings VALUES ($$'hello\'$$);
+INSERT INTO test_strings VALUES ($$['hello\']$$);
+INSERT INTO test_strings VALUES ($$['hello\'']$$);
+INSERT INTO test_strings VALUES ($$['hello"']$$);
+INSERT INTO test_strings VALUES ($$['hello\"']$$);
+INSERT INTO test_strings VALUES ($$['hello"o']$$);
+INSERT INTO test_strings VALUES ($$['"']$$);
+INSERT INTO test_strings VALUES ($$'"'$$);
+INSERT INTO test_strings VALUES ($$'"hello"'$$);
+INSERT INTO test_strings VALUES ($$'"hello'$$);
+INSERT INTO test_strings VALUES ($$'"hi"'$$);
diff --git a/contrib/json/sql/validate.sql b/contrib/json/sql/validate.sql
new file mode 100644
index 0000000..32da7f7
--- /dev/null
+++ b/contrib/json/sql/validate.sql
@@ -0,0 +1 @@
+SELECT json_validate(string), string FROM test_strings;
diff --git a/contrib/json/uninstall_json.sql b/contrib/json/uninstall_json.sql
new file mode 100644
index 0000000..52abb1b
--- /dev/null
+++ b/contrib/json/uninstall_json.sql
@@ -0,0 +1,8 @@
+-- Adjust this setting to control where the objects get dropped.
+SET search_path = public;
+
+DROP TYPE json CASCADE;
+DROP TYPE json_type_t;
+
+DROP FUNCTION json_validate(text);
+DROP FUNCTION parse_json_path(text);
diff --git a/contrib/json/util.c b/contrib/json/util.c
new file mode 100644
index 0000000..97c08d4
--- /dev/null
+++ b/contrib/json/util.c
@@ -0,0 +1,351 @@
+/*-------------------------------------------------------------------------
+ *
+ * util.c
+ *	  General purpose routines used by JSON data type support.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "util.h"
+
+#include "catalog/namespace.h"
+#include "catalog/pg_enum.h"
+#include "utils/syscache.h"
+
+/*
+ * getTypeInfo
+ *	  Retrieve information about a type, along with either its
+ *	  input, output, binary receive, or binary send procedure.
+ *
+ *	  which_func should be one of:
+ *		  IOFunc_input
+ *		  IOFunc_output
+ *		  IOFunc_receive
+ *		  IOFunc_send
+ *
+ *	  mcxt is the memory context the IO function selected will use to store
+ *	  subsidiary data.	The memory context should live at least as long as
+ *	  the TypeInfo structure you specify.
+ */
+void
+getTypeInfo(TypeInfo *d, Oid type, IOFuncSelector which_func, MemoryContext mcxt)
+{
+	d->type = type;
+	d->which_func = which_func;
+	d->mcxt = mcxt;
+
+	get_type_io_data(type, which_func,
+					 &d->typlen, &d->typbyval, &d->typalign,
+					 &d->typdelim, &d->typioparam, &d->typiofunc);
+	fmgr_info_cxt(d->typiofunc, &d->proc, d->mcxt);
+
+	get_type_category_preferred(type,
+								&d->typcategory, &d->typispreferred);
+}
+
+static int
+enum_label_cmp(const void *left, const void *right)
+{
+	const char *l = ((EnumLabel *) left)->label;
+	const char *r = ((EnumLabel *) right)->label;
+
+	return strcmp(l, r);
+}
+
+/*
+ * getEnumLabelOids
+ *	  Look up the OIDs of enum labels.	Enum label OIDs are needed to
+ *	  return values of a custom enum type from a C function.
+ *
+ *	  Callers should typically cache the OIDs produced by this function
+ *	  using FN_EXTRA, as retrieving enum label OIDs is somewhat expensive.
+ *
+ *	  Every labels[i].index must be between 0 and count, and oid_out
+ *	  must be allocated to hold count items.  Note that getEnumLabelOids
+ *	  sorts the labels[] array passed to it.
+ *
+ *	  Any labels not found in the enum will have their corresponding
+ *	  oid_out entries set to InvalidOid.
+ *
+ *	  Sample usage:
+ *
+ *	  -- SQL --
+ *	  CREATE TYPE colors AS ENUM ('red', 'green', 'blue');
+ *
+ *	  -- C --
+ *	  enum Colors {RED, GREEN, BLUE, COLOR_COUNT};
+ *
+ *	  static EnumLabel enum_labels[COLOR_COUNT] =
+ *	  {
+ *		  {RED,   "red"},
+ *		  {GREEN, "green"},
+ *		  {BLUE,  "blue"}
+ *	  };
+ *
+ *	  Oid *label_oids = palloc(COLOR_COUNT * sizeof(Oid));
+ *	  getEnumLabelOids("colors", enum_labels, label_oids, COLOR_COUNT);
+ *
+ *	  PG_RETURN_OID(label_oids[GREEN]);
+ */
+void
+getEnumLabelOids(const char *typname, EnumLabel labels[], Oid oid_out[], int count)
+{
+	CatCList   *list;
+	Oid			enumtypoid;
+	int			total;
+	int			i;
+	EnumLabel	key;
+	EnumLabel  *found;
+
+	enumtypoid = TypenameGetTypid(typname);
+	Assert(OidIsValid(enumtypoid));
+
+	qsort(labels, count, sizeof(EnumLabel), enum_label_cmp);
+
+	for (i = 0; i < count; i++)
+		oid_out[i] = InvalidOid;
+
+	list = SearchSysCacheList1(ENUMTYPOIDNAME,
+							   ObjectIdGetDatum(enumtypoid));
+	total = list->n_members;
+
+	for (i = 0; i < total; i++)
+	{
+		HeapTuple	tup = &list->members[i]->tuple;
+		Oid			oid = HeapTupleGetOid(tup);
+		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+
+		key.label = NameStr(en->enumlabel);
+		found = bsearch(&key, labels, count, sizeof(EnumLabel), enum_label_cmp);
+		if (found != NULL)
+		{
+			Assert(found->index >= 0 && found->index < count);
+			oid_out[found->index] = oid;
+		}
+	}
+
+	ReleaseCatCacheList(list);
+}
+
+/*
+ * utf8_substring
+ *	  Find substring bounds in a UTF-8-encoded string.
+ *
+ *	  @src and @srcbytes are the start and byte length of the input string.
+ *	  @start and @length are the start and number of characters requested.
+ *
+ *	  Writes the bounds of the substring to
+ *	  *out_start (start) and *out_bytes (byte length).
+ *	  Returns the number of characters (not bytes) in the string.
+ *
+ *	  Example:
+ *		 const char *out_start;
+ *		 int		 out_bytes;
+ *		 int		 out_chars;
+ *
+ *		 out_chars =
+ *			 unicode_substring("⁰¹²³", 9,
+ *							   1, 100,
+ *							   &out_start, &out_bytes);
+ *
+ *	  out_chars will be 3.
+ *	  out_start will point to the "¹".
+ *	  out_bytes will be 6.
+ */
+size_t
+utf8_substring(
+			   const char *src, size_t srcbytes,
+			   size_t start, size_t length,
+			   const char **out_start, size_t *out_bytes)
+{
+	const char *e = src + srcbytes;
+	const char *sub_start;
+	const char *sub_end;
+	size_t		sub_length;
+
+	sub_start = src;
+	while (start > 0 && sub_start < e)
+	{
+		sub_start += pg_utf_mblen((const unsigned char *) sub_start);
+		start--;
+	}
+
+	sub_end = sub_start;
+	sub_length = 0;
+	while (sub_length < length && sub_end < e)
+	{
+		sub_end += pg_utf_mblen((const unsigned char *) sub_end);
+		sub_length++;
+	}
+
+	/* Make sure the input didn't have a clipped UTF-8 character */
+	if (sub_start > e)
+	{
+		Assert(false);
+		sub_start = sub_end = e;
+	}
+	else if (sub_end > e)
+	{
+		Assert(false);
+		sub_end = e;
+	}
+
+	*out_start = sub_start;
+	*out_bytes = sub_end - sub_start;
+	return sub_length;
+}
+
+static const bool utf8_allow_surrogates = false;
+
+/*
+ * utf8_decode_char
+ *	  Decodes a UTF-8 character, advancing *sp to point to the end of it.
+ *	  Returns the Unicode code point of the character.
+ *
+ *	  This function will go away when a utf8_to_unicode
+ *	  function becomes available.
+ */
+pg_wchar
+utf8_decode_char(const char **sp)
+{
+	const unsigned char *s = (const unsigned char *) *sp;
+	unsigned char c = *s++;
+	unsigned int len;
+	unsigned char sf[4] = {0xFF, 0x1F, 0xF, 0x7};
+	pg_wchar	uc;
+
+	if (c < 0x80)
+		len = 0;
+	else if (c < 0xE0)
+		len = 1;
+	else if (c < 0xF0)
+		len = 2;
+	else
+		len = 3;
+
+	uc = c & sf[len];
+	while (len--)
+	{
+		uc <<= 6;
+		uc |= *s++ & 0x3F;
+	}
+
+	*sp = (const char *) s;
+	return uc;
+}
+
+/*
+ * utf8_validate
+ *	  Essentially a variant of pg_verify_mbstr(PG_UTF8, str, length, true)
+ *	  that allows '\0' characters.
+ */
+bool
+utf8_validate(const char *str, size_t length)
+{
+	const unsigned char *s = (const unsigned char *) str;
+	const unsigned char *e = s + length;
+	int			len;
+
+	while (s < e)
+	{
+		if (!IS_HIGHBIT_SET(*s))
+		{
+			s++;
+			continue;
+		}
+
+		len = pg_utf_mblen(s);
+		if (s + len > e)
+			return false;
+
+		if (!pg_utf8_islegal(s, len))
+			return false;
+
+		s += len;
+	}
+
+	return true;
+}
+
+char *
+server_to_utf8(const char *str, int len)
+{
+	return (char *) pg_do_encoding_conversion(
+				 (unsigned char *) str, len, GetDatabaseEncoding(), PG_UTF8);
+}
+
+char *
+utf8_to_server(const char *str, int len)
+{
+	return (char *) pg_do_encoding_conversion(
+				 (unsigned char *) str, len, PG_UTF8, GetDatabaseEncoding());
+}
+
+/*
+ * text_to_utf8_cstring
+ *	  Just like text_to_cstring, but yields a C string
+ *	  encoded in UTF-8 instead of the server encoding.
+ */
+char *
+text_to_utf8_cstring(const text *t)
+{
+	/* must cast away the const, just like in text_to_cstring */
+	text	   *tunpacked = pg_detoast_datum_packed((struct varlena *) t);
+	const char *data = VARDATA_ANY(tunpacked);
+	int			len = VARSIZE_ANY_EXHDR(tunpacked);
+	char	   *result;
+
+	result = server_to_utf8(data, len);
+	if (result == data)
+		result = pnstrdup(data, len);
+
+	if (tunpacked != t)
+		pfree(tunpacked);
+
+	return result;
+}
+
+/*
+ * text_to_utf8_cstring
+ *	  Just like cstring_to_text, but takes a C string
+ *	  encoded in UTF-8 instead of the server encoding.
+ */
+text *
+utf8_cstring_to_text(const char *s)
+{
+	return utf8_cstring_to_text_with_len(s, strlen(s));
+}
+
+/*
+ * utf8_cstring_to_text_with_len
+ *	  Just like cstring_to_text_with_len, but takes a C string
+ *	  encoded in UTF-8 instead of the server encoding.
+ *
+ *	  The input string should not contain null characters.
+ */
+text *
+utf8_cstring_to_text_with_len(const char *s, int len)
+{
+	char	   *cstring;
+	int			cstring_len;
+	text	   *result;
+
+	cstring = utf8_to_server(s, len);
+	if (cstring == s)
+		cstring_len = len;
+	else
+		cstring_len = strlen(cstring);
+
+	result = (text *) palloc(len + VARHDRSZ);
+
+	SET_VARSIZE(result, len + VARHDRSZ);
+	memcpy(VARDATA(result), cstring, cstring_len);
+
+	if (cstring != s)
+		pfree(cstring);
+
+	return result;
+}
diff --git a/contrib/json/util.h b/contrib/json/util.h
new file mode 100644
index 0000000..6877ab5
--- /dev/null
+++ b/contrib/json/util.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * util.h
+ *	  General purpose routines used by JSON data type support.
+ *
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * Written by Joey Adams <joeyadams3.14159@gmail.com>.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef JSON_UTIL_H
+#define JSON_UTIL_H
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "utils/lsyscache.h"
+
+typedef struct
+{
+	Oid			type;
+	IOFuncSelector which_func;
+	MemoryContext mcxt;
+
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char		typdelim;
+	Oid			typioparam;
+	Oid			typiofunc;
+	FmgrInfo	proc;
+
+	char		typcategory;
+	bool		typispreferred;
+} TypeInfo;
+
+typedef struct
+{
+	int			index;
+	const char *label;
+}	EnumLabel;
+
+/*
+ * FN_EXTRA, FN_EXTRA_ALLOC, FN_MCXT
+ *	   Macros for manipulating context preserved across function calls.
+ *
+ *	   FN_EXTRA is typically used for caching lookups and other nontrivial
+ *	   operations across multiple calls of a user-defined function.
+ *
+ *	   Do not use FN_EXTRA in a set-returning function.  Use user_fctx instead.
+ *
+ *	   Typical usage looks like:
+ *
+ *	   my_extra = FN_EXTRA();
+ *	   if (my_extra == NULL)
+ *	   {
+ *		   my_extra = FN_EXTRA_ALLOC(sizeof(MyExtra));
+ *		   my_extra->type_name = NULL;
+ *	   }
+ *
+ *	   if (my_extra->type_name == NULL ||
+ *		   strcmp(my_extra->type_name, type_name) != 0)
+ *	   {
+ *		   my_extra->type_name = MemoryContextStrdup(FN_MCXT(), type_name);
+ *		   my_extra->type_id   = TypenameGetTypid(my_extra->type_name);
+ *	   }
+ */
+#define FN_EXTRA() (fcinfo->flinfo->fn_extra)
+#define FN_EXTRA_ALLOC(size) \
+	(fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, size))
+
+/*
+ * Data allocated inside of FN_EXTRA() should be allocated into FN_MCXT()
+ * so it is preserved across calls
+ */
+#define FN_MCXT() (fcinfo->flinfo->fn_mcxt)
+
+void		getTypeInfo(TypeInfo *d, Oid type, IOFuncSelector which_func, MemoryContext mcxt);
+
+void		getEnumLabelOids(const char *typname, EnumLabel labels[], Oid oid_out[], int count);
+
+size_t utf8_substring(const char *src, size_t srcbytes,
+			   size_t start, size_t length,
+			   const char **out_start, size_t *out_bytes);
+pg_wchar	utf8_decode_char(const char **sp);
+bool		utf8_validate(const char *str, size_t length);
+
+/*
+ * Adaptations of pg_do_encoding_conversion for simplifying UTF-8 conversions.
+ *
+ * These are used frequently in the JSON code because JSON nodes are encoded
+ * in UTF-8.  The reason they are encoded in UTF-8 is because we need to
+ * be able to handle Unicode escapes, but there is
+ * no simple and efficient way to do that with the server encoding.
+ *
+ * Just like pg_do_encoding_conversion, if no conversion is done, the original
+ * pointer given is returned.
+ */
+char	   *server_to_utf8(const char *str, int len);
+char	   *utf8_to_server(const char *str, int len);
+
+/*
+ * Variants of text_to_cstring and cstring_to_text for simplifying UTF-8 conversions.
+ *
+ * Just like text_to_cstring, text_to_utf8_cstring will always return a palloc'd,
+ * null-terminated C-string.
+ */
+char	   *text_to_utf8_cstring(const text *t);
+text	   *utf8_cstring_to_text(const char *s);
+text	   *utf8_cstring_to_text_with_len(const char *s, int len);
+
+
+#endif
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index 3ea383c..f3bbc52 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -96,6 +96,7 @@ psql -d dbname -f <replaceable>SHAREDIR</>/contrib/<replaceable>module</>.sql
  &intagg;
  &intarray;
  &isn;
+ &json;
  &lo;
  &ltree;
  &oid2name;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 98f7470..7b44f15 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -108,6 +108,7 @@
 <!entity intagg          SYSTEM "intagg.sgml">
 <!entity intarray        SYSTEM "intarray.sgml">
 <!entity isn             SYSTEM "isn.sgml">
+<!entity json            SYSTEM "json.sgml">
 <!entity lo              SYSTEM "lo.sgml">
 <!entity ltree           SYSTEM "ltree.sgml">
 <!entity oid2name        SYSTEM "oid2name.sgml">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
new file mode 100644
index 0000000..ad48a2f
--- /dev/null
+++ b/doc/src/sgml/json.sgml
@@ -0,0 +1,177 @@
+<sect1 id="json">
+ <title>json</title>
+
+ <indexterm zone="json">
+  <primary>json</primary>
+ </indexterm>
+
+ <para>
+  This module implements the <type>json</> data type for storing <ulink url="http://www.json.org/">JSON</ulink> content in <productname>PostgreSQL</>.  The advantage of using the <type>json</> type over storing JSON content in a <type>text</> field is that it makes sure input values are valid JSON, and there are several type-safe functions for manipulating JSON content.
+ </para>
+
+ <para>
+  The <type>json</> type stores valid JSON <quote>values</quote> as defined by <ulink url="http://json.org/">json.org</ulink>.  That is, a <type>json</> field can hold a string, number, object, array, 'true', 'false', or 'null'.
+ </para>
+
+ <para>
+  The <type>json</> datatype should be thought of as a specialization of <type>text</type> rather than a wrapper around <type>text</>, <type>int</>, <type>float</>, etc.  For instance, <literal>' "string" '::json::text</literal> will simply yield <literal>' "string" '</literal>.  Also, bear in mind that JSON null (<literal>'null'::json</literal>) and SQL NULL (<literal>NULL::json</literal>) are two different things.
+ </para>
+
+ <para>
+  The json module is currently under development.
+ </para>
+
+ <sect2>
+  <title><type>json</> Functions</title>
+
+  <table id="json-func-table">
+   <title><type>json</type> Functions</title>
+
+   <tgroup cols="5">
+    <thead>
+     <row>
+      <entry>Function</entry>
+      <entry>Return Type</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry morerows="1"><function>to_json(anyelement)</function></entry>
+      <entry morerows="1"><type>json</type></entry>
+      <entry morerows="1">Encode a value as JSON.</entry>
+      <entry><literal>to_json('string'::TEXT)</literal></entry>
+      <entry><literal>'"string"'</literal></entry>
+     </row>
+     <row>
+      <entry><literal>to_json(array['one','two','three',null]::text[])</literal></entry>
+      <entry><literal>'["one","two","three",null]'</literal></entry>
+     </row>
+     <row>
+      <entry><function>from_json(json)</function></entry>
+      <entry><type>text</type></entry>
+      <entry>Decode a JSON-encoded value.</entry>
+      <entry><literal>from_json('"string"')</literal></entry>
+      <entry><literal>'string'</literal></entry>
+     </row>
+     <row>
+      <entry morerows="1"><function>json_validate(text)</function></entry>
+      <entry morerows="1"><type>boolean</type></entry>
+      <entry morerows="1">Determine if text is valid JSON.</entry>
+      <entry><literal>json_validate('{key: "value"}')</literal></entry>
+      <entry><literal>false</literal></entry>
+     </row>
+     <row>
+      <entry><literal>json_validate('{"key": "value"}')</literal></entry>
+      <entry><literal>true</literal></entry>
+     </row>
+     <row>
+      <entry morerows="2"><function>json_get(json, jsonpath text)</function></entry>
+      <entry morerows="2"><type>json</type></entry>
+      <entry morerows="2">Select a single value from a JSON tree using a JSONPath expression.</entry>
+      <entry><literal>json_get('[0,1,2]', '$[1]')</literal></entry>
+      <entry><literal>'1'</literal></entry>
+     </row>
+     <row>
+      <entry><literal>json_get('[0,1,2]', '$[100]')</literal></entry>
+      <entry><literal>NULL</literal></entry>
+     </row>
+     <row>
+      <entry><literal>json_get('[0,1,2]', '$[*]')</literal></entry>
+      <entry><literal>Error</literal></entry>
+     </row>
+     <row>
+      <entry morerows="2"><function>json_set(json, jsonpath text, json)</function></entry>
+      <entry morerows="2"><type>json</type></entry>
+      <entry morerows="2">Set items in a JSON tree that match a JSONPath expression.</entry>
+      <entry><literal>json_set('[0,1,2]', '$[1]', '"x"')</literal></entry>
+      <entry><literal>'[0,"x",2]'</literal></entry>
+     </row>
+     <row>
+      <entry><literal>json_set('[0,1,2]', '$[100]', '"x"')</literal></entry>
+      <entry><literal>'[0,1,2]'</literal></entry>
+     </row>
+     <row>
+      <entry><literal>json_set('[0,1,2]', '$[*]', '"x"')</literal></entry>
+      <entry><literal>'["x","x","x"]'</literal></entry>
+     </row>
+     <row>
+      <entry morerows="2"><function>json_path(json, jsonpath text)</function></entry>
+      <entry morerows="2"><type>setof json</type></entry>
+      <entry morerows="2">Select multiple values from a JSON tree using a JSONPath expression.</entry>
+      <entry><literal>json_path('[0,1,2]', '$[1]')</literal></entry>
+      <entry>
+<programlisting>
+ 1
+(1 row)
+</programlisting>
+      </entry>
+     </row>
+     <row>
+      <entry><literal>json_path('[0,1,2]', '$[100]')</literal></entry>
+      <entry>
+<programlisting>
+(0 rows)
+</programlisting>
+      </entry>
+     </row>
+     <row>
+      <entry><literal>json_path('[0,1,2]', '$[*]')</literal></entry>
+      <entry>
+<programlisting>
+ 0
+ 1
+ 2
+(3 rows)
+</programlisting>
+      </entry>
+     </row>
+     <row>
+      <entry morerows="1"><function>json_condense(json)</function></entry>
+      <entry morerows="1"><type>json</type></entry>
+      <entry morerows="1">Re-encodes JSON to form a string with minimal length (mainly removes whitespace).</entry>
+      <entry><literal>json_condense('    {  "key" : "value"}    ')</literal></entry>
+      <entry><literal>'{"key":"value"}'</literal></entry>
+     </row>
+     <row>
+      <entry><literal>json_condense($$  "\u266B"  $$)</literal></entry>
+      <entry><literal>'"&#9835;"' -- if encoding supports Unicode</literal></entry>
+     </row>
+     <row>
+      <entry><function>json_type(json)</function></entry>
+      <entry><type>json_type_t</type>&nbsp;-&nbsp;&nbsp;one&nbsp;of:
+<programlisting>
+'null'
+'string'
+'number'
+'bool'
+'object'
+'array'
+</programlisting>
+      </entry>
+      <entry>Get the type of a <type>json</type> value.</entry>
+      <entry><literal>json_type('{"pi": "3.14159", "e": "2.71828"}')</literal></entry>
+      <entry><literal>'object'</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect2>
+
+ <sect2>
+  <title>Author</title>
+
+  <para>
+   Joey Adams <email>joeyadams3.14159@gmail.com</email>
+  </para>
+  
+  <para>
+   Development of this module was sponsored by Google through its Google Summer of Code program (<ulink url="http://code.google.com/soc">code.google.com/soc</ulink>).
+  </para>
+ </sect2>
+
+</sect1>