JSON NULLs

Started by David E. Wheeleralmost 13 years ago8 messages
#1David E. Wheeler
david@justatheory.com

Hackers,

While playing with Andrew’s JSON enhancements, I noticed this:

david=# select * From json_each_as_text('{"baz": null}'::json);
key | value
-----+-------
baz | null

It is returning 'null'::text there, not NULL::text. I had expected the latter, because otherwise it's not possible to tell the difference between '{"foo": null}' and '{"foo": "null"}'.

But then I noticed that this seems to be true for JSON NULLs in general:

david=# select 'null'::json::text IS NULL;
?column?
----------
f

Again, I expected a NULL there. I recognize that JSON NULLs are not the same as SQL NULLs, but if there is no way to tell the difference, well, it’s annoying.

I see that '"null"'::json::text resolves to '"null"'::text, so that’s one way to deal with it. But since json_each_as_text returns values as text, not quoted JSON values, maybe *it* should return JSON NULLs as SQL NULLs?

Thanks,

David

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

#2Merlin Moncure
mmoncure@gmail.com
In reply to: David E. Wheeler (#1)
Re: JSON NULLs

On Wed, Feb 6, 2013 at 1:08 PM, David E. Wheeler <david@justatheory.com> wrote:

Hackers,

While playing with Andrew’s JSON enhancements, I noticed this:

david=# select * From json_each_as_text('{"baz": null}'::json);
key | value
-----+-------
baz | null

It is returning 'null'::text there, not NULL::text. I had expected the latter, because otherwise it's not possible to tell the difference between '{"foo": null}' and '{"foo": "null"}'.

IMO, this is bug in proposed implementation. json unquoted null
should not map to string 'null' but to SQL, casting behavior from
text as implemented looks correct. (only SQL null should produce json
null)

merlin

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

#3Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#2)
Re: JSON NULLs

On 02/06/2013 02:24 PM, Merlin Moncure wrote:

On Wed, Feb 6, 2013 at 1:08 PM, David E. Wheeler <david@justatheory.com> wrote:

Hackers,

While playing with Andrew’s JSON enhancements, I noticed this:

david=# select * From json_each_as_text('{"baz": null}'::json);
key | value
-----+-------
baz | null

It is returning 'null'::text there, not NULL::text. I had expected the latter, because otherwise it's not possible to tell the difference between '{"foo": null}' and '{"foo": "null"}'.

IMO, this is bug in proposed implementation. json unquoted null
should not map to string 'null' but to SQL, casting behavior from
text as implemented looks correct. (only SQL null should produce json
null)

Probably. I'm on it.

cheers

andrew

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

#4Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#3)
1 attachment(s)
Re: JSON NULLs

On 02/06/2013 02:36 PM, Andrew Dunstan wrote:

On 02/06/2013 02:24 PM, Merlin Moncure wrote:

On Wed, Feb 6, 2013 at 1:08 PM, David E. Wheeler
<david@justatheory.com> wrote:

Hackers,

While playing with Andrew’s JSON enhancements, I noticed this:

david=# select * From json_each_as_text('{"baz": null}'::json);
key | value
-----+-------
baz | null

It is returning 'null'::text there, not NULL::text. I had expected
the latter, because otherwise it's not possible to tell the
difference between '{"foo": null}' and '{"foo": "null"}'.

IMO, this is bug in proposed implementation. json unquoted null
should not map to string 'null' but to SQL, casting behavior from
text as implemented looks correct. (only SQL null should produce json
null)

Probably. I'm on it.

Revised patch attached. The problem also existed with the get*_as_text
functions (and their operators). Some additional regression tests are
added to test these cases.

cheers

andrew

Attachments:

jsonapi8.patchtext/x-patch; name=jsonapi8.patchDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 9632,9641 **** table2-mapping
  
    <table id="functions-json-table">
      <title>JSON Support Functions</title>
!     <tgroup cols="4">
       <thead>
        <row>
         <entry>Function</entry>
         <entry>Description</entry>
         <entry>Example</entry>
         <entry>Example Result</entry>
--- 9632,9642 ----
  
    <table id="functions-json-table">
      <title>JSON Support Functions</title>
!     <tgroup cols="5">
       <thead>
        <row>
         <entry>Function</entry>
+        <entry>Return Type</entry>
         <entry>Description</entry>
         <entry>Example</entry>
         <entry>Example Result</entry>
***************
*** 9649,9654 **** table2-mapping
--- 9650,9656 ----
           </indexterm>
           <literal>array_to_json(anyarray [, pretty_bool])</literal>
         </entry>
+        <entry>json</entry>
         <entry>
           Returns the array as JSON. A PostgreSQL multidimensional array
           becomes a JSON array of arrays. Line feeds will be added between
***************
*** 9664,9669 **** table2-mapping
--- 9666,9672 ----
           </indexterm>
           <literal>row_to_json(record [, pretty_bool])</literal>
         </entry>
+        <entry>json</entry>
         <entry>
           Returns the row as JSON. Line feeds will be added between level
           1 elements if <parameter>pretty_bool</parameter> is true.
***************
*** 9671,9680 **** table2-mapping
         <entry><literal>row_to_json(row(1,'foo'))</literal></entry>
         <entry><literal>{"f1":1,"f2":"foo"}</literal></entry>
        </row>
       </tbody>
      </tgroup>
     </table>
! 
   </sect1>
  
   <sect1 id="functions-sequence">
--- 9674,9963 ----
         <entry><literal>row_to_json(row(1,'foo'))</literal></entry>
         <entry><literal>{"f1":1,"f2":"foo"}</literal></entry>
        </row>
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_array_length</primary>
+          </indexterm>
+          <literal>json_array_length(json)</literal>
+        </entry>
+        <entry>int</entry>
+        <entry>
+          Returns the number of elements in the outermost json array. 
+        </entry>
+        <entry><literal>json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]')</literal></entry>
+        <entry><literal>5</literal></entry>
+       </row>
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_each</primary>
+          </indexterm>
+          <literal>json_each(json)</literal>
+        </entry>
+        <entry>SETOF key text, value json</entry>
+        <entry>
+          Expands the outermost json object into a set of key/value pairs.
+        </entry>
+        <entry><literal>select * from json_each_as_text('{"a":"foo", "b":"bar"}')</literal></entry>
+        <entry>
+ <programlisting>
+  key | value 
+ -----+-------
+  a   | "foo"
+  b   | "bar"
+  </programlisting>       
+        </entry>
+       </row>      
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_each_as_text</primary>
+          </indexterm>
+          <literal>json_each_as_text(from_json json)</literal>
+        </entry>
+        <entry>SETOF key text, value text</entry>
+        <entry>
+          Expands the outermost json object into a set of key/value pairs. The
+          returned value will be of type text.
+        </entry>
+        <entry><literal>select * from json_each_as_text('{"a":"foo", "b":"bar"}')</literal></entry>
+        <entry>
+ <programlisting>
+  key | value 
+ -----+-------
+  a   | foo
+  b   | bar 
+  </programlisting>
+        </entry>
+       </row>       
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get</primary>
+          </indexterm>
+          <literal>json_get(json, index int)</literal>
+        </entry>
+        <entry>json</entry>
+        <entry>
+          Returns nth json object from a json array.  Array indexing is zero based.   
+        </entry>
+        <entry><literal>json_get('[1,2,3]', 2)</literal></entry>
+        <entry><literal>3</literal></entry>
+       </row>           
+       <row>
+        <entry>
+          <literal>json_get(json, key text)</literal>
+        </entry>
+        <entry>json</entry>       
+        <entry>
+           Returns value of json object named by key
+        </entry>
+        <entry><literal>json_get('{"f1":"abc"}', 'f1')</literal></entry>
+        <entry><literal>"abc"</literal></entry>
+       </row>     
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get_as_text</primary>
+          </indexterm>
+          <literal>json_get_as_text(json, index int)</literal>
+        </entry>
+        <entry>text</entry>
+        <entry>
+          Returns nth json object from a json array as SQL scalar (that is, 
+          without any json quoting or escaping).  Array indexing is zero based.
+        </entry>
+        <entry><literal>json_get('{"f1":"abc"}', 'f1')</literal></entry>
+        <entry><literal>abc</literal></entry>
+       </row>   
+       <row>
+        <entry>
+          <literal>json_get(json, key text)</literal>
+        </entry>
+        <entry>text</entry>       
+        <entry>
+           Returns value of json object named by key as SQL scalar (that is, 
+           without any json quoting or escaping)
+        </entry>
+        <entry><literal>json_get('{"f1":[1,2,3],"f2":{"f3":1}}', 'f1')</literal></entry>
+        <entry><literal>[1,2,3]</literal></entry>
+       </row>        
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get_path</primary>
+          </indexterm>
+          <literal>json_get_path(from_json json, VARIADIC path_elems text[])</literal>
+        </entry>
+        <entry>json</entry>
+        <entry>
+          Returns json object pointed to by <parameter>path_elems</parameter>.
+        </entry>
+        <entry><literal>json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4')</literal></entry>
+        <entry><literal>{"f5":99,"f6":"foo"}</literal></entry>
+       </row>   
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get_path_as_text</primary>
+          </indexterm>
+          <literal>json_get_path_as_text(from_json json, VARIADIC path_elems text[])</literal>
+        </entry>
+        <entry>text</entry>
+        <entry>
+          Returns json object pointed to by <parameter>path_elems</parameter>.
+        </entry>
+        <entry><literal>json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6')</literal></entry>
+        <entry><literal>foo</literal></entry>
+       </row>   
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_object_keys</primary>
+          </indexterm>
+          <literal>json_object_keys(json)</literal>
+        </entry>
+        <entry>SETOF text</entry>
+        <entry>
+           Returns set of keys in the json object.  Only the "outer" object will be displayed.
+        </entry>
+        <entry><literal>json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}')</literal></entry>
+        <entry>
+ <programlisting>
+  json_object_keys 
+ ------------------
+  f1
+  f2
+ </programlisting>
+        </entry>
+       </row>         
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_populate_record</primary>
+          </indexterm>
+          <literal>json_populate_record(base anyelement, from_json json, [, use_json_as_text bool=false]</literal>
+        </entry>
+        <entry>anyelement</entry>
+        <entry>
+          Expands the object in from_json to a row whose columns match 
+          the record type defined by base. Conversion will be best 
+          effort; columns in base with no corresponding key in from_json 
+          will be left null.  A column may only be specified once.         
+        </entry>
+        <entry><literal>json_populate_record(null::x, '{"a":1,"b":2}')</literal></entry>
+        <entry>
+ <programlisting>       
+  a | b 
+ ---+---
+  1 | 2
+ </programlisting>
+        </entry>
+       </row>      
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_populate_recordset</primary>
+          </indexterm>
+          <literal>json_populate_recordset(base anyelement, from_json json, [, use_json_as_text bool=false]</literal>
+        </entry>
+        <entry>SETOF anyelement</entry>
+        <entry>
+          Expands the outermost set of objects in from_json to a set 
+          whose columns match the record type defined by base.
+          Conversion will be best effort; columns in base with no
+          corresponding key in from_json will be left null.  A column 
+          may only be specified once.
+        </entry>
+        <entry><literal>json_populate_recordset(null::x, '[{"a":1,"b":2},{"a":3,"b":4}]')</literal></entry>
+        <entry>
+ <programlisting>       
+  a | b 
+ ---+---
+  1 | 2
+  3 | 4
+  </programlisting>
+        </entry>
+       </row>   
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_unnest</primary>
+          </indexterm>
+          <literal>json_unnest(json)</literal>
+        </entry>
+        <entry>SETOF json</entry>
+        <entry>
+          Expands a json array to a set of json elements.  However, 
+          unlike standard unnest only the outermost array is 
+          expanded.
+        </entry>
+        <entry><literal>json_unnest('[1,true, [2,false]]')</literal></entry>
+        <entry>
+ <programlisting>
+    value   
+ -----------
+  1
+  true
+  [2,false]
+ </programlisting>
+        </entry>
+       </row>   
       </tbody>
      </tgroup>
     </table>
!    <table id="functions-json-op-table">
!      <title>JSON Operators</title>
!      <tgroup cols="4">
!       <thead>
!        <row>
!         <entry>Operator</entry>
!         <entry>Right Operand Type</entry>
!         <entry>Description</entry>
!         <entry>Example</entry>
!        </row>
!       </thead>
!       <tbody>
!        <row>
!         <entry><literal>-&gt;</literal></entry>
!         <entry>int</entry>
!         <entry>Get JSON array element</entry>
!         <entry><literal>'[1,2,3]'::json-&gt;2</literal></entry>
!        </row>
!        <row>
!         <entry><literal>-&gt;</literal></entry>
!         <entry>text</entry>
!         <entry>Get JSON object field</entry>
!         <entry><literal>'{"a":1,"b":2}'::json-&gt;'b'</literal></entry>
!        </row>
!         <row>
!         <entry><literal>-&gt;&gt;</literal></entry>
!         <entry>int</entry>
!         <entry>Get JSON array element as text</entry>
!         <entry><literal>'[1,2,3]'::json-&gt;&gt;2</literal></entry>
!        </row>
!        <row>
!         <entry><literal>-&gt;&gt;</literal></entry>
!         <entry>text</entry>
!         <entry>Get JSON object field as text</entry>
!         <entry><literal>'{"a":1,"b":2}'::json-&gt;&gt;'b'</literal></entry>
!        </row>
!        <row>
!         <entry><literal>-&gt;</literal></entry>
!         <entry>array of text</entry>
!         <entry>Get JSON object at specified path</entry>
!         <entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json-&gt;ARRAY['a','2']</literal></entry>
!        </row>
!        <row>
!         <entry><literal>-&gt;&gt;</literal></entry>
!         <entry>array of text</entry>
!         <entry>Get JSON object at specified path as text</entry>
!         <entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json-&gt;&gt;ARRAY['a','2']</literal></entry>
!        </row>
!       </tbody>
!      </tgroup>
!    </table>   
   </sect1>
  
   <sect1 id="functions-sequence">
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 773,775 **** COMMENT ON FUNCTION ts_debug(text) IS
--- 773,783 ----
  CREATE OR REPLACE FUNCTION
    pg_start_backup(label text, fast boolean DEFAULT false)
    RETURNS text STRICT VOLATILE LANGUAGE internal AS 'pg_start_backup';
+ 
+ CREATE OR REPLACE FUNCTION 
+   json_populate_record(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
+   RETURNS anyelement LANGUAGE internal STABLE AS 'json_populate_record';
+ 
+ CREATE OR REPLACE FUNCTION 
+   json_populate_recordset(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
+   RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100  AS 'json_populate_recordset';
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 19,26 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o like.o lockfuncs.o \
! 	misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
--- 19,26 ----
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
! 	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
***************
*** 24,92 ****
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/json.h"
  #include "utils/typcache.h"
  
! typedef enum					/* types of JSON values */
! {
! 	JSON_VALUE_INVALID,			/* non-value tokens are reported as this */
! 	JSON_VALUE_STRING,
! 	JSON_VALUE_NUMBER,
! 	JSON_VALUE_OBJECT,
! 	JSON_VALUE_ARRAY,
! 	JSON_VALUE_TRUE,
! 	JSON_VALUE_FALSE,
! 	JSON_VALUE_NULL
! } JsonValueType;
! 
! typedef struct					/* state of JSON lexer */
! {
! 	char	   *input;			/* whole string being parsed */
! 	char	   *token_start;	/* start of current token within input */
! 	char	   *token_terminator; /* end of previous or current token */
! 	JsonValueType token_type;	/* type of current token, once it's known */
! } JsonLexContext;
! 
! typedef enum					/* states of JSON parser */
  {
  	JSON_PARSE_VALUE,			/* expecting a value */
  	JSON_PARSE_ARRAY_START,		/* saw '[', expecting value or ']' */
  	JSON_PARSE_ARRAY_NEXT,		/* saw array element, expecting ',' or ']' */
  	JSON_PARSE_OBJECT_START,	/* saw '{', expecting label or '}' */
  	JSON_PARSE_OBJECT_LABEL,	/* saw object label, expecting ':' */
  	JSON_PARSE_OBJECT_NEXT,		/* saw object value, expecting ',' or '}' */
! 	JSON_PARSE_OBJECT_COMMA		/* saw object ',', expecting next label */
! } JsonParseState;
! 
! typedef struct JsonParseStack	/* the parser state has to be stackable */
! {
! 	JsonParseState state;
! 	/* currently only need the state enum, but maybe someday more stuff */
! } JsonParseStack;
! 
! typedef enum					/* required operations on state stack */
! {
! 	JSON_STACKOP_NONE,			/* no-op */
! 	JSON_STACKOP_PUSH,			/* push new JSON_PARSE_VALUE stack item */
! 	JSON_STACKOP_PUSH_WITH_PUSHBACK, /* push, then rescan current token */
! 	JSON_STACKOP_POP			/* pop, or expect end of input if no stack */
! } JsonStackOp;
! 
! static void json_validate_cstring(char *input);
! static void json_lex(JsonLexContext *lex);
! static void json_lex_string(JsonLexContext *lex);
! static void json_lex_number(JsonLexContext *lex, char *s);
! static void report_parse_error(JsonParseStack *stack, JsonLexContext *lex);
  static void report_invalid_token(JsonLexContext *lex);
! static int report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
  static void composite_to_json(Datum composite, StringInfo result,
! 							  bool use_line_feeds);
  static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
  				  TYPCATEGORY tcategory, Oid typoutputfunc,
  				  bool use_line_feeds);
  static void array_to_json_internal(Datum array, StringInfo result,
! 								   bool use_line_feeds);
  
  /* fake type category for JSON so we can distinguish it in datum_to_json */
  #define TYPCATEGORY_JSON 'j'
--- 24,141 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/json.h"
+ #include "utils/jsonapi.h"
  #include "utils/typcache.h"
  
! /*
!  * The context of the parser is maintained by the recursive descent
!  * mechanism, but is passed explicitly to the error reporting routine
!  * for better diagnostics.
!  */
! typedef enum					/* contexts of JSON parser */
  {
  	JSON_PARSE_VALUE,			/* expecting a value */
+ 	JSON_PARSE_STRING,			/* expecting a string (for a field name) */
  	JSON_PARSE_ARRAY_START,		/* saw '[', expecting value or ']' */
  	JSON_PARSE_ARRAY_NEXT,		/* saw array element, expecting ',' or ']' */
  	JSON_PARSE_OBJECT_START,	/* saw '{', expecting label or '}' */
  	JSON_PARSE_OBJECT_LABEL,	/* saw object label, expecting ':' */
  	JSON_PARSE_OBJECT_NEXT,		/* saw object value, expecting ',' or '}' */
! 	JSON_PARSE_OBJECT_COMMA,	/* saw object ',', expecting next label */
! 	JSON_PARSE_END				/* saw the end of a document, expect nothing */
! }	JsonParseContext;
! 
! static inline void json_lex(JsonLexContext *lex);
! static inline void json_lex_string(JsonLexContext *lex);
! static inline void json_lex_number(JsonLexContext *lex, char *s);
! static inline void parse_scalar(JsonLexContext *lex, JsonSemAction sem);
! static void parse_object_field(JsonLexContext *lex, JsonSemAction sem);
! static void parse_object(JsonLexContext *lex, JsonSemAction sem);
! static void parse_array_element(JsonLexContext *lex, JsonSemAction sem);
! static void parse_array(JsonLexContext *lex, JsonSemAction sem);
! static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex);
  static void report_invalid_token(JsonLexContext *lex);
! static int	report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
  static void composite_to_json(Datum composite, StringInfo result,
! 				  bool use_line_feeds);
  static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
  				  TYPCATEGORY tcategory, Oid typoutputfunc,
  				  bool use_line_feeds);
  static void array_to_json_internal(Datum array, StringInfo result,
! 					   bool use_line_feeds);
! 
! /* the null action object used for pure validation */
! static jsonSemAction nullSemAction =
! {
! 	NULL, NULL, NULL, NULL, NULL,
! 	NULL, NULL, NULL, NULL, NULL
! };
! static JsonSemAction NullSemAction = &nullSemAction;
! 
! /* Recursive Descent parser support routines */
! 
! /*
!  * lex_peek
!  *
!  * what is the current look_ahead token?
! */
! static inline JsonTokenType
! lex_peek(JsonLexContext *lex)
! {
! 	return lex->token_type;
! }
! 
! /*
!  * lex_accept
!  *
!  * accept the look_ahead token and move the lexer to the next token if the
!  * look_ahead token matches the token parameter. In that case, and if required,
!  * also hand back the de-escaped lexeme.
!  *
!  * returns true if the token matched, false otherwise.
!  */
! static inline bool
! lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme)
! {
! 	if (lex->token_type == token)
! 	{
! 		if (lexeme != NULL)
! 		{
! 			if (lex->token_type == JSON_TOKEN_STRING)
! 			{
! 				if (lex->strval != NULL)
! 					*lexeme = pstrdup(lex->strval->data);
! 			}
! 			else
! 			{
! 				int			len = (lex->token_terminator - lex->token_start);
! 				char	   *tokstr = palloc(len + 1);
! 
! 				memcpy(tokstr, lex->token_start, len);
! 				tokstr[len] = '\0';
! 				*lexeme = tokstr;
! 			}
! 		}
! 		json_lex(lex);
! 		return true;
! 	}
! 	return false;
! }
! 
! /*
!  * lex_accept
!  *
!  * move the lexer to the next token if the current look_ahead token matches
!  * the parameter token. Otherwise, report an error.
!  */
! static inline void
! lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
! {
! 	if (!lex_accept(lex, token, NULL))
! 		report_parse_error(ctx, lex);;
! }
  
  /* fake type category for JSON so we can distinguish it in datum_to_json */
  #define TYPCATEGORY_JSON 'j'
***************
*** 100,118 **** static void array_to_json_internal(Datum array, StringInfo result,
  	 (c) == '_' || \
  	 IS_HIGHBIT_SET(c))
  
- 
  /*
   * Input.
   */
  Datum
  json_in(PG_FUNCTION_ARGS)
  {
! 	char	   *text = PG_GETARG_CSTRING(0);
  
! 	json_validate_cstring(text);
  
  	/* Internal representation is the same as text, for now */
! 	PG_RETURN_TEXT_P(cstring_to_text(text));
  }
  
  /*
--- 149,170 ----
  	 (c) == '_' || \
  	 IS_HIGHBIT_SET(c))
  
  /*
   * Input.
   */
  Datum
  json_in(PG_FUNCTION_ARGS)
  {
! 	char	   *json = PG_GETARG_CSTRING(0);
! 	text	   *result = cstring_to_text(json);
! 	JsonLexContext *lex;
  
! 	/* validate it */
! 	lex = makeJsonLexContext(result, false);
! 	pg_parse_json(lex, NullSemAction);
  
  	/* Internal representation is the same as text, for now */
! 	PG_RETURN_TEXT_P(result);
  }
  
  /*
***************
*** 151,443 **** json_recv(PG_FUNCTION_ARGS)
  	text	   *result;
  	char	   *str;
  	int			nbytes;
  
  	str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
  
! 	/*
! 	 * We need a null-terminated string to pass to json_validate_cstring().
! 	 * Rather than make a separate copy, make the temporary result one byte
! 	 * bigger than it needs to be.
! 	 */
! 	result = palloc(nbytes + 1 + VARHDRSZ);
  	SET_VARSIZE(result, nbytes + VARHDRSZ);
  	memcpy(VARDATA(result), str, nbytes);
- 	str = VARDATA(result);
- 	str[nbytes] = '\0';
  
  	/* Validate it. */
! 	json_validate_cstring(str);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /*
!  * Check whether supplied input is valid JSON.
   */
  static void
! json_validate_cstring(char *input)
  {
! 	JsonLexContext lex;
! 	JsonParseStack *stack,
! 			   *stacktop;
! 	int			stacksize;
! 
! 	/* Set up lexing context. */
! 	lex.input = input;
! 	lex.token_terminator = lex.input;
! 
! 	/* Set up parse stack. */
! 	stacksize = 32;
! 	stacktop = (JsonParseStack *) palloc(sizeof(JsonParseStack) * stacksize);
! 	stack = stacktop;
! 	stack->state = JSON_PARSE_VALUE;
! 
! 	/* Main parsing loop. */
! 	for (;;)
  	{
! 		JsonStackOp op;
  
! 		/* Fetch next token. */
! 		json_lex(&lex);
  
! 		/* Check for unexpected end of input. */
! 		if (lex.token_start == NULL)
! 			report_parse_error(stack, &lex);
  
! redo:
! 		/* Figure out what to do with this token. */
! 		op = JSON_STACKOP_NONE;
! 		switch (stack->state)
! 		{
! 			case JSON_PARSE_VALUE:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == '[')
! 					stack->state = JSON_PARSE_ARRAY_START;
! 				else if (lex.token_start[0] == '{')
! 					stack->state = JSON_PARSE_OBJECT_START;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_ARRAY_START:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					stack->state = JSON_PARSE_ARRAY_NEXT;
! 				else if (lex.token_start[0] == ']')
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == '[' ||
! 						 lex.token_start[0] == '{')
! 				{
! 					stack->state = JSON_PARSE_ARRAY_NEXT;
! 					op = JSON_STACKOP_PUSH_WITH_PUSHBACK;
! 				}
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_ARRAY_NEXT:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					report_parse_error(stack, &lex);
! 				else if (lex.token_start[0] == ']')
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == ',')
! 					op = JSON_STACKOP_PUSH;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_START:
! 				if (lex.token_type == JSON_VALUE_STRING)
! 					stack->state = JSON_PARSE_OBJECT_LABEL;
! 				else if (lex.token_type == JSON_VALUE_INVALID &&
! 						 lex.token_start[0] == '}')
! 					op = JSON_STACKOP_POP;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_LABEL:
! 				if (lex.token_type == JSON_VALUE_INVALID &&
! 					lex.token_start[0] == ':')
! 				{
! 					stack->state = JSON_PARSE_OBJECT_NEXT;
! 					op = JSON_STACKOP_PUSH;
! 				}
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_NEXT:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					report_parse_error(stack, &lex);
! 				else if (lex.token_start[0] == '}')
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == ',')
! 					stack->state = JSON_PARSE_OBJECT_COMMA;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_COMMA:
! 				if (lex.token_type == JSON_VALUE_STRING)
! 					stack->state = JSON_PARSE_OBJECT_LABEL;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			default:
! 				elog(ERROR, "unexpected json parse state: %d",
! 					 (int) stack->state);
! 		}
  
! 		/* Push or pop the state stack, if needed. */
! 		switch (op)
! 		{
! 			case JSON_STACKOP_PUSH:
! 			case JSON_STACKOP_PUSH_WITH_PUSHBACK:
! 				stack++;
! 				if (stack >= &stacktop[stacksize])
! 				{
! 					/* Need to enlarge the stack. */
! 					int			stackoffset = stack - stacktop;
! 
! 					stacksize += 32;
! 					stacktop = (JsonParseStack *)
! 						repalloc(stacktop,
! 								 sizeof(JsonParseStack) * stacksize);
! 					stack = stacktop + stackoffset;
! 				}
! 				stack->state = JSON_PARSE_VALUE;
! 				if (op == JSON_STACKOP_PUSH_WITH_PUSHBACK)
! 					goto redo;
! 				break;
! 			case JSON_STACKOP_POP:
! 				if (stack == stacktop)
! 				{
! 					/* Expect end of input. */
! 					json_lex(&lex);
! 					if (lex.token_start != NULL)
! 						report_parse_error(NULL, &lex);
! 					return;
! 				}
! 				stack--;
! 				break;
! 			case JSON_STACKOP_NONE:
! 				/* nothing to do */
! 				break;
! 		}
  	}
  }
  
  /*
   * Lex one token from the input stream.
   */
! static void
  json_lex(JsonLexContext *lex)
  {
  	char	   *s;
  
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
! 	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
! 		s++;
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (strchr("{}[],:", s[0]) != NULL)
  	{
! 		/* strchr() is willing to match a zero byte, so test for that. */
! 		if (s[0] == '\0')
! 		{
! 			/* End of string. */
! 			lex->token_start = NULL;
! 			lex->token_terminator = s;
! 		}
! 		else
! 		{
! 			/* Single-character token, some kind of punctuation mark. */
! 			lex->token_terminator = s + 1;
! 		}
! 		lex->token_type = JSON_VALUE_INVALID;
! 	}
! 	else if (*s == '"')
! 	{
! 		/* String. */
! 		json_lex_string(lex);
! 		lex->token_type = JSON_VALUE_STRING;
! 	}
! 	else if (*s == '-')
! 	{
! 		/* Negative number. */
! 		json_lex_number(lex, s + 1);
! 		lex->token_type = JSON_VALUE_NUMBER;
! 	}
! 	else if (*s >= '0' && *s <= '9')
! 	{
! 		/* Positive number. */
! 		json_lex_number(lex, s);
! 		lex->token_type = JSON_VALUE_NUMBER;
  	}
  	else
! 	{
! 		char	   *p;
  
! 		/*
! 		 * We're not dealing with a string, number, legal punctuation mark, or
! 		 * end of string.  The only legal tokens we might find here are true,
! 		 * false, and null, but for error reporting purposes we scan until we
! 		 * see a non-alphanumeric character.  That way, we can report the
! 		 * whole word as an unexpected token, rather than just some
! 		 * unintuitive prefix thereof.
! 		 */
! 		for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
! 			/* skip */ ;
  
! 		if (p == s)
! 		{
! 			/*
! 			 * We got some sort of unexpected punctuation or an otherwise
! 			 * unexpected character, so just complain about that one
! 			 * character.  (It can't be multibyte because the above loop
! 			 * will advance over any multibyte characters.)
! 			 */
! 			lex->token_terminator = s + 1;
! 			report_invalid_token(lex);
! 		}
  
! 		/*
! 		 * We've got a real alphanumeric token here.  If it happens to be
! 		 * true, false, or null, all is well.  If not, error out.
! 		 */
! 		lex->token_terminator = p;
! 		if (p - s == 4)
! 		{
! 			if (memcmp(s, "true", 4) == 0)
! 				lex->token_type = JSON_VALUE_TRUE;
! 			else if (memcmp(s, "null", 4) == 0)
! 				lex->token_type = JSON_VALUE_NULL;
! 			else
! 				report_invalid_token(lex);
! 		}
! 		else if (p - s == 5 && memcmp(s, "false", 5) == 0)
! 			lex->token_type = JSON_VALUE_FALSE;
! 		else
! 			report_invalid_token(lex);
! 	}
  }
  
  /*
   * The next token in the input stream is known to be a string; lex it.
   */
! static void
  json_lex_string(JsonLexContext *lex)
  {
  	char	   *s;
  
! 	for (s = lex->token_start + 1; *s != '"'; s++)
  	{
! 		/* Per RFC4627, these characters MUST be escaped. */
! 		if ((unsigned char) *s < 32)
  		{
! 			/* A NUL byte marks the (premature) end of the string. */
! 			if (*s == '\0')
! 			{
! 				lex->token_terminator = s;
! 				report_invalid_token(lex);
! 			}
  			/* Since *s isn't printable, exclude it from the context string */
  			lex->token_terminator = s;
  			ereport(ERROR,
--- 203,660 ----
  	text	   *result;
  	char	   *str;
  	int			nbytes;
+ 	JsonLexContext *lex;
  
  	str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
  
! 	result = palloc(nbytes + VARHDRSZ);
  	SET_VARSIZE(result, nbytes + VARHDRSZ);
  	memcpy(VARDATA(result), str, nbytes);
  
  	/* Validate it. */
! 	lex = makeJsonLexContext(result, false);
! 	pg_parse_json(lex, NullSemAction);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /*
!  * makeJsonLexContext
!  *
!  * lex constructor, with or without StringInfo object
!  * for de-escaped lexemes.
!  *
!  * Without is better as it makes the processing faster, so only make one
!  * if really required.
!  */
! JsonLexContext *
! makeJsonLexContext(text *json, bool need_escapes)
! {
! 	JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
! 
! 	lex->input = lex->token_terminator = lex->line_start = VARDATA(json);
! 	lex->line_number = 1;
! 	lex->input_length = VARSIZE(json) - VARHDRSZ;
! 	if (need_escapes)
! 		lex->strval = makeStringInfo();
! 	return lex;
! }
! 
! /*
!  * pg_parse_json
!  *
!  * Publicly visible entry point for the JSON parser.
!  *
!  * lex is a lexing context, set up for the json to be processed by calling
!  * makeJsonLexContext(). sem is a strucure of function pointers to semantic
!  * action routines to be called at appropriate spots during parsing, and a
!  * pointer to a state object to be passed to those routines.
   */
+ void
+ pg_parse_json(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	JsonTokenType tok;
+ 
+ 	/* get the initial token */
+ 	json_lex(lex);
+ 
+ 	tok = lex_peek(lex);
+ 
+ 	/* parse by recursive descent */
+ 	switch (tok)
+ 	{
+ 		case JSON_TOKEN_OBJECT_START:
+ 			parse_object(lex, sem);
+ 			break;
+ 		case JSON_TOKEN_ARRAY_START:
+ 			parse_array(lex, sem);
+ 			break;
+ 		default:
+ 			parse_scalar(lex, sem);		/* json can be a bare scalar */
+ 	}
+ 
+ 	lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
+ 
+ }
+ 
+ /*
+  *	Recursive Descent parse routines. There is one for each structural
+  *	element in a json document:
+  *	  - scalar (string, number, true, false, null)
+  *	  - array  ( [ ] )
+  *	  - array element
+  *	  - object ( { } )
+  *	  - object field
+  */
+ static inline void
+ parse_scalar(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	char	   *val = NULL;
+ 	json_scalar_action sfunc = sem->scalar;
+ 	char	  **valaddr;
+ 	JsonTokenType tok = lex_peek(lex);
+ 
+ 	valaddr = sfunc == NULL ? NULL : &val;
+ 
+ 	/* a scalar must be a string, a number, true, false, or null */
+ 	switch (tok)
+ 	{
+ 		case JSON_TOKEN_TRUE:
+ 			lex_accept(lex, JSON_TOKEN_TRUE, valaddr);
+ 			break;
+ 		case JSON_TOKEN_FALSE:
+ 			lex_accept(lex, JSON_TOKEN_FALSE, valaddr);
+ 			break;
+ 		case JSON_TOKEN_NULL:
+ 			lex_accept(lex, JSON_TOKEN_NULL, valaddr);
+ 			break;
+ 		case JSON_TOKEN_NUMBER:
+ 			lex_accept(lex, JSON_TOKEN_NUMBER, valaddr);
+ 			break;
+ 		case JSON_TOKEN_STRING:
+ 			lex_accept(lex, JSON_TOKEN_STRING, valaddr);
+ 			break;
+ 		default:
+ 			report_parse_error(JSON_PARSE_VALUE, lex);
+ 	}
+ 
+ 	if (sfunc != NULL)
+ 		(*sfunc) (sem->semstate, val, tok);
+ }
+ 
  static void
! parse_object_field(JsonLexContext *lex, JsonSemAction sem)
  {
! 	/*
! 	 * an object field is "fieldname" : value where value can be a scalar,
! 	 * object or array
! 	 */
! 
! 	char	   *fname = NULL;	/* keep compiler quiet */
! 	json_ofield_action ostart = sem->object_field_start;
! 	json_ofield_action oend = sem->object_field_end;
! 	bool		isnull;
! 	char	  **fnameaddr = NULL;
! 	JsonTokenType tok;
! 
! 	if (ostart != NULL || oend != NULL)
! 		fnameaddr = &fname;
! 
! 	if (!lex_accept(lex, JSON_TOKEN_STRING, fnameaddr))
! 		report_parse_error(JSON_PARSE_STRING, lex);
! 
! 	lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
! 
! 	tok = lex_peek(lex);
! 	isnull = tok == JSON_TOKEN_NULL;
! 
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate, fname, isnull);
! 
! 	switch (tok)
  	{
! 		case JSON_TOKEN_OBJECT_START:
! 			parse_object(lex, sem);
! 			break;
! 		case JSON_TOKEN_ARRAY_START:
! 			parse_array(lex, sem);
! 			break;
! 		default:
! 			parse_scalar(lex, sem);
! 	}
  
! 	if (oend != NULL)
! 		(*oend) (sem->semstate, fname, isnull);
  
! 	if (fname != NULL)
! 		pfree(fname);
! }
  
! static void
! parse_object(JsonLexContext *lex, JsonSemAction sem)
! {
! 	/*
! 	 * an object is a possibly empty sequence of object fields, separated by
! 	 * commas and surrounde by curly braces.
! 	 */
! 	json_struct_action ostart = sem->object_start;
! 	json_struct_action oend = sem->object_end;
! 	JsonTokenType tok;
  
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate);
! 
! 	/*
! 	 * Data inside an object at at a higher nesting level than the object
! 	 * itself. Note that we increment this after we call the semantic routine
! 	 * for the object start and restore it before we call the routine for the
! 	 * object end.
! 	 */
! 	lex->lex_level++;
! 
! 	/* we know this will succeeed, just clearing the token */
! 	lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
! 
! 	tok = lex_peek(lex);
! 	switch (tok)
! 	{
! 		case JSON_TOKEN_STRING:
! 			parse_object_field(lex, sem);
! 			while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
! 				parse_object_field(lex, sem);
! 			break;
! 		case JSON_TOKEN_OBJECT_END:
! 			break;
! 		default:
! 			/* case of an invalid initial token inside the object */
! 			report_parse_error(JSON_PARSE_OBJECT_START, lex);
! 	}
! 
! 	lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END);
! 
! 	lex->lex_level--;
! 
! 	if (oend != NULL)
! 		(*oend) (sem->semstate);
! }
! 
! static void
! parse_array_element(JsonLexContext *lex, JsonSemAction sem)
! {
! 	json_aelem_action astart = sem->array_element_start;
! 	json_aelem_action aend = sem->array_element_end;
! 	JsonTokenType tok = lex_peek(lex);
! 
! 	bool		isnull;
! 
! 	isnull = tok == JSON_TOKEN_NULL;
! 
! 	if (astart != NULL)
! 		(*astart) (sem->semstate, isnull);
! 
! 	/* an array element is any object, array or scalar */
! 	switch (tok)
! 	{
! 		case JSON_TOKEN_OBJECT_START:
! 			parse_object(lex, sem);
! 			break;
! 		case JSON_TOKEN_ARRAY_START:
! 			parse_array(lex, sem);
! 			break;
! 		default:
! 			parse_scalar(lex, sem);
  	}
+ 
+ 	if (aend != NULL)
+ 		(*aend) (sem->semstate, isnull);
+ }
+ 
+ static void
+ parse_array(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	/*
+ 	 * an array is a possibly empty sequence of array elements, separated by
+ 	 * commas and surrounded by square brackets.
+ 	 */
+ 	json_struct_action astart = sem->array_start;
+ 	json_struct_action aend = sem->array_end;
+ 
+ 	if (astart != NULL)
+ 		(*astart) (sem->semstate);
+ 
+ 	/*
+ 	 * Data inside an array at at a higher nesting level than the array
+ 	 * itself. Note that we increment this after we call the semantic routine
+ 	 * for the array start and restore it before we call the routine for the
+ 	 * array end.
+ 	 */
+ 	lex->lex_level++;
+ 
+ 	lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START);
+ 	if (lex_peek(lex) != JSON_TOKEN_ARRAY_END)
+ 	{
+ 
+ 		parse_array_element(lex, sem);
+ 
+ 		while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
+ 			parse_array_element(lex, sem);
+ 	}
+ 
+ 	lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END);
+ 
+ 	lex->lex_level--;
+ 
+ 	if (aend != NULL)
+ 		(*aend) (sem->semstate);
  }
  
  /*
   * Lex one token from the input stream.
   */
! static inline void
  json_lex(JsonLexContext *lex)
  {
  	char	   *s;
+ 	int			len;
  
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
! 	len = s - lex->input;
! 	while (len < lex->input_length &&
! 		   (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
! 	{
! 		if (*s == '\n')
! 			++lex->line_number;
! 		++s;
! 		++len;
! 	}
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (len >= lex->input_length)
  	{
! 		lex->token_start = NULL;
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s;
! 		lex->token_type = JSON_TOKEN_END;
  	}
  	else
! 		switch (*s)
! 		{
! 				/* Single-character token, some kind of punctuation mark. */
! 			case '{':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_OBJECT_START;
! 				break;
! 			case '}':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_OBJECT_END;
! 				break;
! 			case '[':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_ARRAY_START;
! 				break;
! 			case ']':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_ARRAY_END;
! 				break;
! 			case ',':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_COMMA;
! 				break;
! 			case ':':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_COLON;
! 				break;
  
! 			case '"':
! 				/* string */
! 				json_lex_string(lex);
! 				lex->token_type = JSON_TOKEN_STRING;
! 				break;
! 			case '-':
! 				/* Negative number. */
! 				json_lex_number(lex, s + 1);
! 				lex->token_type = JSON_TOKEN_NUMBER;
! 				break;
! 			case '0':
! 			case '1':
! 			case '2':
! 			case '3':
! 			case '4':
! 			case '5':
! 			case '6':
! 			case '7':
! 			case '8':
! 			case '9':
! 				/* Positive number. */
! 				json_lex_number(lex, s);
! 				lex->token_type = JSON_TOKEN_NUMBER;
! 				break;
! 			default:
! 				{
! 					char	   *p;
! 
! 					/*
! 					 * We're not dealing with a string, number, legal
! 					 * punctuation mark, or end of string.	The only legal
! 					 * tokens we might find here are true, false, and null,
! 					 * but for error reporting purposes we scan until we see a
! 					 * non-alphanumeric character.	That way, we can report
! 					 * the whole word as an unexpected token, rather than just
! 					 * some unintuitive prefix thereof.
! 					 */
! 					for (p = s; JSON_ALPHANUMERIC_CHAR(*p) && p - s < lex->input_length - len; p++)
! 						 /* skip */ ;
! 
! 					/*
! 					 * We got some sort of unexpected punctuation or an
! 					 * otherwise unexpected character, so just complain about
! 					 * that one character.
! 					 */
! 					if (p == s)
! 					{
! 						lex->prev_token_terminator = lex->token_terminator;
! 						lex->token_terminator = s + 1;
! 						report_invalid_token(lex);
! 					}
  
! 					/*
! 					 * We've got a real alphanumeric token here.  If it
! 					 * happens to be true, false, or null, all is well.  If
! 					 * not, error out.
! 					 */
! 					lex->prev_token_terminator = lex->token_terminator;
! 					lex->token_terminator = p;
! 					if (p - s == 4)
! 					{
! 						if (memcmp(s, "true", 4) == 0)
! 							lex->token_type = JSON_TOKEN_TRUE;
! 						else if (memcmp(s, "null", 4) == 0)
! 							lex->token_type = JSON_TOKEN_NULL;
! 						else
! 							report_invalid_token(lex);
! 					}
! 					else if (p - s == 5 && memcmp(s, "false", 5) == 0)
! 						lex->token_type = JSON_TOKEN_FALSE;
! 					else
! 						report_invalid_token(lex);
  
! 				}
! 		}						/* end of switch */
  }
  
  /*
   * The next token in the input stream is known to be a string; lex it.
   */
! static inline void
  json_lex_string(JsonLexContext *lex)
  {
  	char	   *s;
+ 	int			len;
  
! 	if (lex->strval != NULL)
! 		resetStringInfo(lex->strval);
! 
! 	len = lex->token_start - lex->input;
! 	len++;
! 	for (s = lex->token_start + 1; *s != '"'; s++, len++)
  	{
! 		/* Premature end of the string. */
! 		if (len >= lex->input_length)
  		{
! 			lex->token_terminator = s;
! 			report_invalid_token(lex);
! 		}
! 		else if ((unsigned char) *s < 32)
! 		{
! 			/* Per RFC4627, these characters MUST be escaped. */
  			/* Since *s isn't printable, exclude it from the context string */
  			lex->token_terminator = s;
  			ereport(ERROR,
***************
*** 451,457 **** json_lex_string(JsonLexContext *lex)
  		{
  			/* OK, we have an escape character. */
  			s++;
! 			if (*s == '\0')
  			{
  				lex->token_terminator = s;
  				report_invalid_token(lex);
--- 668,675 ----
  		{
  			/* OK, we have an escape character. */
  			s++;
! 			len++;
! 			if (len >= lex->input_length)
  			{
  				lex->token_terminator = s;
  				report_invalid_token(lex);
***************
*** 464,470 **** json_lex_string(JsonLexContext *lex)
  				for (i = 1; i <= 4; i++)
  				{
  					s++;
! 					if (*s == '\0')
  					{
  						lex->token_terminator = s;
  						report_invalid_token(lex);
--- 682,689 ----
  				for (i = 1; i <= 4; i++)
  				{
  					s++;
! 					len++;
! 					if (len >= lex->input_length)
  					{
  						lex->token_terminator = s;
  						report_invalid_token(lex);
***************
*** 485,494 **** json_lex_string(JsonLexContext *lex)
  								 report_json_context(lex)));
  					}
  				}
  			}
  			else if (strchr("\"\\/bfnrt", *s) == NULL)
  			{
! 				/* Not a valid string escape, so error out. */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
--- 704,769 ----
  								 report_json_context(lex)));
  					}
  				}
+ 				if (lex->strval != NULL)
+ 				{
+ 					char		utf8str[5];
+ 					int			utf8len;
+ 					char	   *converted;
+ 
+ 					unicode_to_utf8(ch, (unsigned char *) utf8str);
+ 					utf8len = pg_utf_mblen((unsigned char *) utf8str);
+ 					utf8str[utf8len] = '\0';
+ 					converted = pg_any_to_server(utf8str, 1, PG_UTF8);
+ 					appendStringInfoString(lex->strval, converted);
+ 					if (converted != utf8str)
+ 						pfree(converted);
+ 
+ 				}
+ 			}
+ 			else if (lex->strval != NULL)
+ 			{
+ 				switch (*s)
+ 				{
+ 					case '"':
+ 					case '\\':
+ 					case '/':
+ 						appendStringInfoChar(lex->strval, *s);
+ 						break;
+ 					case 'b':
+ 						appendStringInfoChar(lex->strval, '\b');
+ 						break;
+ 					case 'f':
+ 						appendStringInfoChar(lex->strval, '\f');
+ 						break;
+ 					case 'n':
+ 						appendStringInfoChar(lex->strval, '\n');
+ 						break;
+ 					case 'r':
+ 						appendStringInfoChar(lex->strval, '\r');
+ 						break;
+ 					case 't':
+ 						appendStringInfoChar(lex->strval, '\t');
+ 						break;
+ 					default:
+ 						/* Not a valid string escape, so error out. */
+ 						lex->token_terminator = s + pg_mblen(s);
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 								 errmsg("invalid input syntax for type json"),
+ 							errdetail("Escape sequence \"\\%s\" is invalid.",
+ 									  extract_mb_char(s)),
+ 								 report_json_context(lex)));
+ 				}
  			}
  			else if (strchr("\"\\/bfnrt", *s) == NULL)
  			{
! 				/*
! 				 * Simpler processing if we're not bothered about de-escaping
! 				 *
! 				 * It's very tempting to remove the strchr() call here and
! 				 * replace it with a switch statement, but testing so far has
! 				 * shown it's not a performance win.
! 				 */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 497,506 **** json_lex_string(JsonLexContext *lex)
--- 772,788 ----
  								   extract_mb_char(s)),
  						 report_json_context(lex)));
  			}
+ 
+ 		}
+ 		else if (lex->strval != NULL)
+ 		{
+ 			appendStringInfoChar(lex->strval, *s);
  		}
+ 
  	}
  
  	/* Hooray, we found the end of the string! */
+ 	lex->prev_token_terminator = lex->token_terminator;
  	lex->token_terminator = s + 1;
  }
  
***************
*** 530,596 **** json_lex_string(JsonLexContext *lex)
   *
   *-------------------------------------------------------------------------
   */
! static void
  json_lex_number(JsonLexContext *lex, char *s)
  {
  	bool		error = false;
  	char	   *p;
  
  	/* Part (1): leading sign indicator. */
  	/* Caller already did this for us; so do nothing. */
  
  	/* Part (2): parse main digit string. */
  	if (*s == '0')
  		s++;
  	else if (*s >= '1' && *s <= '9')
  	{
  		do
  		{
  			s++;
! 		} while (*s >= '0' && *s <= '9');
  	}
  	else
  		error = true;
  
  	/* Part (3): parse optional decimal portion. */
! 	if (*s == '.')
  	{
  		s++;
! 		if (*s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 			} while (*s >= '0' && *s <= '9');
  		}
  	}
  
  	/* Part (4): parse optional exponent. */
! 	if (*s == 'e' || *s == 'E')
  	{
  		s++;
! 		if (*s == '+' || *s == '-')
  			s++;
! 		if (*s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 			} while (*s >= '0' && *s <= '9');
  		}
  	}
  
  	/*
! 	 * Check for trailing garbage.  As in json_lex(), any alphanumeric stuff
  	 * here should be considered part of the token for error-reporting
  	 * purposes.
  	 */
! 	for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
  		error = true;
  	lex->token_terminator = p;
  	if (error)
  		report_invalid_token(lex);
--- 812,892 ----
   *
   *-------------------------------------------------------------------------
   */
! static inline void
  json_lex_number(JsonLexContext *lex, char *s)
  {
  	bool		error = false;
  	char	   *p;
+ 	int			len;
  
+ 	len = s - lex->input;
  	/* Part (1): leading sign indicator. */
  	/* Caller already did this for us; so do nothing. */
  
  	/* Part (2): parse main digit string. */
  	if (*s == '0')
+ 	{
  		s++;
+ 		len++;
+ 	}
  	else if (*s >= '1' && *s <= '9')
  	{
  		do
  		{
  			s++;
! 			len++;
! 		} while (*s >= '0' && *s <= '9' && len < lex->input_length);
  	}
  	else
  		error = true;
  
  	/* Part (3): parse optional decimal portion. */
! 	if (len < lex->input_length && *s == '.')
  	{
  		s++;
! 		len++;
! 		if (len == lex->input_length || *s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 				len++;
! 			} while (*s >= '0' && *s <= '9' && len < lex->input_length);
  		}
  	}
  
  	/* Part (4): parse optional exponent. */
! 	if (len < lex->input_length && (*s == 'e' || *s == 'E'))
  	{
  		s++;
! 		len++;
! 		if (len < lex->input_length && (*s == '+' || *s == '-'))
! 		{
  			s++;
! 			len++;
! 		}
! 		if (len == lex->input_length || *s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 				len++;
! 			} while (len < lex->input_length && *s >= '0' && *s <= '9');
  		}
  	}
  
  	/*
! 	 * Check for trailing garbage.	As in json_lex(), any alphanumeric stuff
  	 * here should be considered part of the token for error-reporting
  	 * purposes.
  	 */
! 	for (p = s; JSON_ALPHANUMERIC_CHAR(*p) && len < lex->input_length; p++, len++)
  		error = true;
+ 	lex->prev_token_terminator = lex->token_terminator;
  	lex->token_terminator = p;
  	if (error)
  		report_invalid_token(lex);
***************
*** 602,614 **** json_lex_number(JsonLexContext *lex, char *s)
   * lex->token_start and lex->token_terminator must identify the current token.
   */
  static void
! report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  {
  	char	   *token;
  	int			toklen;
  
  	/* Handle case where the input ended prematurely. */
! 	if (lex->token_start == NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
--- 898,910 ----
   * lex->token_start and lex->token_terminator must identify the current token.
   */
  static void
! report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
  {
  	char	   *token;
  	int			toklen;
  
  	/* Handle case where the input ended prematurely. */
! 	if (lex->token_start == NULL || lex->token_type == JSON_TOKEN_END)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
***************
*** 622,628 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  	token[toklen] = '\0';
  
  	/* Complain, with the appropriate detail message. */
! 	if (stack == NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
--- 918,924 ----
  	token[toklen] = '\0';
  
  	/* Complain, with the appropriate detail message. */
! 	if (ctx == JSON_PARSE_END)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
***************
*** 631,637 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  				 report_json_context(lex)));
  	else
  	{
! 		switch (stack->state)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
--- 927,933 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 937,950 ----
  								   token),
  						 report_json_context(lex)));
  				break;
+ 			case JSON_PARSE_STRING:
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 						 errmsg("invalid input syntax for type json"),
+ 						 errdetail("Expected string, but found \"%s\".",
+ 								   token),
+ 						 report_json_context(lex)));
+ 				break;
  			case JSON_PARSE_ARRAY_START:
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 653,668 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Expected \",\" or \"]\", but found \"%s\".",
! 								   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_START:
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Expected string or \"}\", but found \"%s\".",
! 								   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_LABEL:
--- 957,972 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 					  errdetail("Expected \",\" or \"]\", but found \"%s\".",
! 								token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_START:
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 					 errdetail("Expected string or \"}\", but found \"%s\".",
! 							   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_LABEL:
***************
*** 677,684 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Expected \",\" or \"}\", but found \"%s\".",
! 								   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_COMMA:
--- 981,988 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 					  errdetail("Expected \",\" or \"}\", but found \"%s\".",
! 								token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_COMMA:
***************
*** 690,697 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  						 report_json_context(lex)));
  				break;
  			default:
! 				elog(ERROR, "unexpected json parse state: %d",
! 					 (int) stack->state);
  		}
  	}
  }
--- 994,1000 ----
  						 report_json_context(lex)));
  				break;
  			default:
! 				elog(ERROR, "unexpected json parse state: %d", ctx);
  		}
  	}
  }
***************
*** 786,792 **** report_json_context(JsonLexContext *lex)
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (*context_end != '\0' && *context_end != '\n' && *context_end != '\r') ? "..." : "";
  
  	return errcontext("JSON data, line %d: %s%s%s",
  					  line_number, prefix, ctxt, suffix);
--- 1089,1095 ----
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (lex->token_type != JSON_TOKEN_END && context_end - lex->input < lex->input_length && *context_end != '\n' && *context_end != '\r') ? "..." : "";
  
  	return errcontext("JSON data, line %d: %s%s%s",
  					  line_number, prefix, ctxt, suffix);
*** /dev/null
--- b/src/backend/utils/adt/jsonfuncs.c
***************
*** 0 ****
--- 1,2063 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/jsonfuncs.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include <limits.h>
+ 
+ #include "fmgr.h"
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "access/htup_details.h"
+ #include "catalog/pg_type.h"
+ #include "lib/stringinfo.h"
+ #include "mb/pg_wchar.h"
+ #include "utils/array.h"
+ #include "utils/builtins.h"
+ #include "utils/hsearch.h"
+ #include "utils/json.h"
+ #include "utils/jsonapi.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ #include "utils/typcache.h"
+ 
+ /* semantic action functions for json_object_keys */
+ static void okeys_object_field_start(void *state, char *fname, bool isnull);
+ static void okeys_array_start(void *state);
+ static void okeys_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* semantic action functions for json_get* functions */
+ static void get_object_start(void *state);
+ static void get_object_field_start(void *state, char *fname, bool isnull);
+ static void get_object_field_end(void *state, char *fname, bool isnull);
+ static void get_array_start(void *state);
+ static void get_array_element_start(void *state, bool isnull);
+ static void get_array_element_end(void *state, bool isnull);
+ static void get_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* common worker function for json_get* functions */
+ static text *get_worker(text *json, char *field, int elem_index, char **path,
+ 		   int npath, bool normalize_results);
+ 
+ /* semantic action functions for json_array_length */
+ static void alen_object_start(void *state);
+ static void alen_scalar(void *state, char *token, JsonTokenType tokentype);
+ static void alen_array_element_start(void *state, bool isnull);
+ 
+ /* semantic action functions for json_each */
+ static void each_object_field_start(void *state, char *fname, bool isnull);
+ static void each_object_field_end(void *state, char *fname, bool isnull);
+ static void each_array_start(void *state);
+ static void each_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* semantic action functions for json_unnest */
+ static void unnest_object_start(void *state);
+ static void unnest_array_element_start(void *state, bool isnull);
+ static void unnest_array_element_end(void *state, bool isnull);
+ static void unnest_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* turn a json object into a hash table */
+ static HTAB *get_json_object_as_hash(text *json, char *funcname, bool use_json_as_text);
+ 
+ /* semantic action functions for get_json_object_as_hash */
+ static void hash_object_field_start(void *state, char *fname, bool isnull);
+ static void hash_object_field_end(void *state, char *fname, bool isnull);
+ static void hash_array_start(void *state);
+ static void hash_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* semantic action functions for populate_recordset */
+ static void populate_recordset_object_field_start(void *state, char *fname, bool isnull);
+ static void populate_recordset_object_field_end(void *state, char *fname, bool isnull);
+ static void populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype);
+ static void populate_recordset_object_start(void *state);
+ static void populate_recordset_object_end(void *state);
+ static void populate_recordset_array_start(void *state);
+ static void populate_recordset_array_element_start(void *state, bool isnull);
+ 
+ /* search type classification for json_get* functions */
+ typedef enum
+ {
+ 	JSON_SEARCH_OBJECT = 1,
+ 	JSON_SEARCH_ARRAY,
+ 	JSON_SEARCH_PATH
+ }	JsonSearch;
+ 
+ /* state for json_object_keys */
+ typedef struct okeysState
+ {
+ 	JsonLexContext *lex;
+ 	char	  **result;
+ 	int			result_size;
+ 	int			result_count;
+ 	int			sent_count;
+ }	okeysState, *OkeysState;
+ 
+ /* state for json_get* functions */
+ typedef struct getState
+ {
+ 	JsonLexContext *lex;
+ 	JsonSearch	search_type;
+ 	int			search_index;
+ 	int			array_index;
+ 	char	   *search_term;
+ 	char	   *result_start;
+ 	text	   *tresult;
+ 	bool		result_is_null;
+ 	bool		normalize_results;
+ 	bool		next_scalar;
+ 	char	  **path;
+ 	int			npath;
+ 	char	  **current_path;
+ 	bool	   *pathok;
+ 	int		   *array_level_index;
+ 	int		   *path_level_index;
+ }	getState, *GetState;
+ 
+ /* state for json_array_length */
+ typedef struct alenState
+ {
+ 	JsonLexContext *lex;
+ 	int			count;
+ }	alenState, *AlenState;
+ 
+ /* state for json_each */
+ typedef struct eachState
+ {
+ 	JsonLexContext *lex;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	MemoryContext tmp_cxt;
+ 	char	   *result_start;
+ 	bool		normalize_results;
+ 	bool		next_scalar;
+ 	char	   *normalized_scalar;
+ }	eachState, *EachState;
+ 
+ /* state for json_unnest */
+ typedef struct unnestState
+ {
+ 	JsonLexContext *lex;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	MemoryContext tmp_cxt;
+ 	char	   *result_start;
+ }	unnestState, *UnnestState;
+ 
+ /* state for get_json_object_as_hash */
+ typedef struct jhashState
+ {
+ 	JsonLexContext *lex;
+ 	HTAB	   *hash;
+ 	char	   *saved_scalar;
+ 	char	   *save_json_start;
+ 	bool		use_json_as_text;
+ 	char	   *function_name;
+ }	jhashState, *JHashState;
+ 
+ /* used to build the hashtable */
+ typedef struct jsonHashEntry
+ {
+ 	char		fname[NAMEDATALEN];
+ 	char	   *val;
+ 	char	   *json;
+ 	bool		isnull;
+ }	jsonHashEntry, *JsonHashEntry;
+ 
+ /* these two are stolen from hstore / record_out, used in populate_record* */
+ typedef struct ColumnIOData
+ {
+ 	Oid			column_type;
+ 	Oid			typiofunc;
+ 	Oid			typioparam;
+ 	FmgrInfo	proc;
+ } ColumnIOData;
+ 
+ typedef struct RecordIOData
+ {
+ 	Oid			record_type;
+ 	int32		record_typmod;
+ 	int			ncolumns;
+ 	ColumnIOData columns[1];	/* VARIABLE LENGTH ARRAY */
+ } RecordIOData;
+ 
+ /* state for populate_recordset */
+ typedef struct populateRecordsetState
+ {
+ 	JsonLexContext *lex;
+ 	HTAB	   *json_hash;
+ 	char	   *saved_scalar;
+ 	char	   *save_json_start;
+ 	bool		use_json_as_text;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	HeapTupleHeader rec;
+ 	RecordIOData *my_extra;
+ 	MemoryContext fn_mcxt;		/* used to stash IO funcs */
+ }	populateRecordsetState, *PopulateRecordsetState;
+ 
+ /*
+  * SQL function json_object-keys
+  *
+  * Returns the set of keys for the object argument.
+  *
+  * This SRF operates in value-per-call mode. It processes the
+  * object during the first call, and the keys are simply stashed
+  * in an array, whise size is expanded as necessary. This is probably
+  * safe enough for a list of keys of a single object, since they are
+  * limited in size to NAMEDATALEN and the number of keys is unlikely to
+  * be so huge that it has major memory implications.
+  */
+ 
+ Datum
+ json_object_keys(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 	OkeysState	state;
+ 	int			i;
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		text	   *json = PG_GETARG_TEXT_P(0);
+ 		JsonLexContext *lex = makeJsonLexContext(json, true);
+ 		JsonSemAction sem;
+ 
+ 		MemoryContext oldcontext;
+ 
+ 		funcctx = SRF_FIRSTCALL_INIT();
+ 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ 
+ 		state = palloc(sizeof(okeysState));
+ 		sem = palloc0(sizeof(jsonSemAction));
+ 
+ 		state->lex = lex;
+ 		state->result_size = 256;
+ 		state->result_count = 0;
+ 		state->sent_count = 0;
+ 		state->result = palloc(256 * sizeof(char *));
+ 
+ 		sem->semstate = (void *) state;
+ 		sem->array_start = okeys_array_start;
+ 		sem->scalar = okeys_scalar;
+ 		sem->object_field_start = okeys_object_field_start;
+ 		/* remainder are all NULL, courtesy of palloc0 above */
+ 
+ 		pg_parse_json(lex, sem);
+ 		/* keys are now in state->result */
+ 
+ 		pfree(lex->strval->data);
+ 		pfree(lex->strval);
+ 		pfree(lex);
+ 		pfree(sem);
+ 
+ 		MemoryContextSwitchTo(oldcontext);
+ 		funcctx->user_fctx = (void *) state;
+ 
+ 	}
+ 
+ 	funcctx = SRF_PERCALL_SETUP();
+ 	state = (OkeysState) funcctx->user_fctx;
+ 
+ 	if (state->sent_count < state->result_count)
+ 	{
+ 		char	   *nxt = state->result[state->sent_count++];
+ 
+ 		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt));
+ 	}
+ 
+ 	/* cleanup to reduce or eliminate memory leaks */
+ 	for (i = 0; i < state->result_count; i++)
+ 		pfree(state->result[i]);
+ 	pfree(state->result);
+ 	pfree(state);
+ 
+ 	SRF_RETURN_DONE(funcctx);
+ }
+ 
+ static void
+ okeys_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	/* only collecting keys for the top level object */
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 
+ 	/* enlarge result array if necessary */
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 
+ 	/* save a copy of the field name */
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	/* top level must be a json object */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_object_keys on an array")));
+ }
+ 
+ static void
+ okeys_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	/* top level must be a json object */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_object_keys on a scalar")));
+ }
+ 
+ /*
+  * json_get* functions
+  * these all use a common worker, just with some slightly
+  * different setup options.
+  */
+ 
+ 
+ /*
+  * SQL function json_get(json text) -> json
+  *
+  * return json for named field
+  *
+  * also used for json -> text operator
+  */
+ Datum
+ json_get_ofield(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, fnamestr, -1, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_get_as_text(json text) -> text
+  *
+  * return text for named field. If the field is a
+  * string the de-escaped value of the string is delivered.
+  *
+  * also used for json ->> text operator
+  */
+ Datum
+ json_get_ofield_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, fnamestr, -1, NULL, -1, true);
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_get(json, int) -> json
+  *
+  * return json for numbered field
+  *
+  * also used for json -> int operator
+  */
+ Datum
+ json_get_aelem(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, NULL, element, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ /*
+  * SQL function json_get_as_text(json, int) -> text
+  *
+  * return text for numbered field . If the field is a
+  * string the de-escaped value of the string is delivered.
+  *
+  * also used for json ->> int operator
+  */
+ Datum
+ json_get_aelem_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, NULL, element, NULL, -1, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ /*
+  * SQL function json_get_path(json, variadic text[]) -> json
+  *
+  * return json for object pointed to by path contained in second
+  * parameter. If the json structure refered to by a path element is
+  * an array, the path element is treated as a (zero based) index. If
+  * it's an object it is treated as a field name. Since SQL arrays are
+  * homogeneous, integer arguments for array indexes must be passed as text.
+  *
+  * There is also a non-variadic function json_get_path_op
+  * that maps to this function and is used in the construction of the
+  * json -> text[] operator.
+  */
+ Datum
+ json_get_path(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	text	   *result;
+ 	Datum	   *pathtext;
+ 	bool	   *pathnulls;
+ 	int			npath;
+ 	char	  **pathstr;
+ 	int			i;
+ 
+ 	if (array_contains_nulls(path))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call %s with null path elements",
+ 						"json_get_path_as_text")));
+ 
+ 
+ 	deconstruct_array(path, TEXTOID, -1, false, 'i',
+ 					  &pathtext, &pathnulls, &npath);
+ 
+ 	pathstr = palloc(npath * sizeof(char *));
+ 
+ 	for (i = 0; i < npath; i++)
+ 	{
+ 		pathstr[i] = TextDatumGetCString(pathtext[i]);
+ 		if (*pathstr[i] == '\0')
+ 			ereport(
+ 					ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot call %s with empty path elements",
+ 							"json_get_path_as_text")));
+ 	}
+ 
+ 	result = get_worker(json, NULL, -1, pathstr, npath, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_get_path_as_text(json, variadic text[]) -> json
+  *
+  * return text for object pointed to by path contained in second
+  * parameter. If the json structure refered to by a path element is
+  * an array, the path element is treated as a (zero based) index. If
+  * it's an object it is treated as a field name. Since SQL arrays are
+  * homogeneous, integer arguments for array indexes must be passed as text.
+  *
+  * If the field is a string the de-escaped value of the string is delivered.
+  *
+  * There is also a non-variadic function json_get_path_as_text_op
+  * that maps to this function and is used in the construction of the
+  * json ->> text[] operator.
+  */
+ Datum
+ json_get_path_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	text	   *result;
+ 	Datum	   *pathtext;
+ 	bool	   *pathnulls;
+ 	int			npath;
+ 	char	  **pathstr;
+ 	int			i;
+ 
+ 	if (array_contains_nulls(path))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call %s with null path elements",
+ 						"json_get_path_as_text")));
+ 
+ 
+ 	deconstruct_array(path, TEXTOID, -1, false, 'i',
+ 					  &pathtext, &pathnulls, &npath);
+ 
+ 	pathstr = palloc(npath * sizeof(char *));
+ 
+ 	for (i = 0; i < npath; i++)
+ 	{
+ 		pathstr[i] = TextDatumGetCString(pathtext[i]);
+ 		if (*pathstr[i] == '\0')
+ 			ereport(
+ 					ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot call %s with empty path elements",
+ 							"json_get_path_as_text")));
+ 	}
+ 
+ 	result = get_worker(json, NULL, -1, pathstr, npath, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * get_worker
+  *
+  * common worker for json_get* functions
+  */
+ static text *
+ get_worker(text *json,
+ 		   char *field,
+ 		   int elem_index,
+ 		   char **path,
+ 		   int npath,
+ 		   bool normalize_results)
+ {
+ 	GetState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 
+ 	/* only allowed to use one of these */
+ 	Assert(elem_index < 0 || (path == NULL && field == NULL));
+ 	Assert(path == NULL || field == NULL);
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex = lex;
+ 	/* is is "_as_text" variant? */
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		/* single text argument */
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else if (path != NULL)
+ 	{
+ 		/* path array argument */
+ 		int			i;
+ 		long int	ind;
+ 		char	   *endptr;
+ 
+ 		state->search_type = JSON_SEARCH_PATH;
+ 		state->path = path;
+ 		state->npath = npath;
+ 		state->current_path = palloc(sizeof(char *) * npath);
+ 		state->pathok = palloc(sizeof(bool) * npath);
+ 		state->pathok[0] = true;
+ 		state->array_level_index = palloc(sizeof(int) * npath);
+ 		state->path_level_index = palloc(sizeof(int) * npath);
+ 
+ 		/*
+ 		 * we have no idea at this stage what structure the document is so
+ 		 * just convert anything in the path that we can to an integer and set
+ 		 * all the other integers to -1 which will never match.
+ 		 */
+ 		for (i = 0; i < npath; i++)
+ 		{
+ 			ind = strtol(path[i], &endptr, 10);
+ 			if (*endptr == '\0' && ind <= INT_MAX && ind >= 0)
+ 				state->path_level_index[i] = (int) ind;
+ 			else
+ 				state->path_level_index[i] = -1;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* single integer argument */
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 
+ 	/*
+ 	 * Not all	variants need all the semantic routines. only set the ones
+ 	 * that ar actually needed for maximum efficiency.
+ 	 */
+ 	sem->object_start = get_object_start;
+ 	sem->array_start = get_array_start;
+ 	sem->scalar = get_scalar;
+ 	if (field != NULL || path != NULL)
+ 	{
+ 		sem->object_field_start = get_object_field_start;
+ 		sem->object_field_end = get_object_field_end;
+ 	}
+ 	if (field == NULL)
+ 	{
+ 		sem->array_element_start = get_array_element_start;
+ 		sem->array_element_end = get_array_element_end;
+ 	}
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	return state->tresult;
+ }
+ 
+ static void
+ get_object_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0 && _state->search_type == JSON_SEARCH_ARRAY)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_get(int) on a non-array")));
+ }
+ 
+ static void
+ get_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_next = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 		strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		/* single field search and we have a match at the right nesting level */
+ 		get_next = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[_state->lex->lex_level - 1] &&
+ 			 strcmp(fname, _state->path[lex_level - 1]) == 0)
+ 	{
+ 		/* path search, path so far is ok,	and we have a match */
+ 
+ 		/* if not at end of path just mark path ok */
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = true;
+ 
+ 		/* end of path, so we want this value */
+ 		if (lex_level == _state->npath)
+ 			get_next = true;
+ 	}
+ 
+ 	if (get_next)
+ 	{
+ 		/*
+ 		 * If tresult is already set it means we've already made this match.
+ 		 * So complain about it.
+ 		 */
+ 		if (_state->tresult != NULL || _state->result_start != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("field name is not unique in json object")));
+ 
+ 		if (_state->normalize_results &&
+ 			_state->lex->token_type == JSON_TOKEN_STRING)
+ 		{
+ 			/* for as_text variants, tell get_scalar to set it for us */
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			/* for non-as_text variants, just note the json starting point */
+ 			_state->result_start = _state->lex->token_start;
+ 		}
+ 	}
+ }
+ 
+ static void
+ get_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_last = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 
+ 	/* same tests as in get_object_field_start, mutatis mutandis */
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 		strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		get_last = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[lex_level - 1] &&
+ 			 strcmp(fname, _state->path[lex_level - 1]) == 0)
+ 	{
+ 		/* done with this field so reset pathok */
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = false;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_last = true;
+ 	}
+ 
+ 	/* for as_test variants our work is already done */
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		/*
+ 		 * make a text object from the string from the prevously noted json
+ 		 * start up to the end of the previous token (the lexer is by now
+ 		 * ahead of us on whatevere came after what we're interested in).
+ 		 */
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		if (isnull  && _state->normalize_results)
+ 			_state->tresult = (text *) NULL;
+ 		else
+ 			_state->tresult = cstring_to_text_with_len(_state->result_start, len);
+ 	}
+ 
+ 	/*
+ 	 * don't need to reset _state->result_start b/c we're only returning one
+ 	 * datum, the conditions should not occur more than once, and this lets us
+ 	 * check cheaply that they don't (see object_field_start() )
+ 	 */
+ }
+ 
+ static void
+ get_array_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	/* json structure check */
+ 	if (lex_level == 0 && _state->search_type == JSON_SEARCH_OBJECT)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_get(fieldname) on a non-object")));
+ 	/* initialize array count for this nesting level */
+ 	if (_state->search_type == JSON_SEARCH_PATH &&
+ 		lex_level <= _state->npath)
+ 		_state->array_level_index[lex_level] = -1;
+ }
+ 
+ static void
+ get_array_element_start(void *state, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_next = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY)
+ 	{
+ 		/* single integer search */
+ 		_state->array_index++;
+ 		if (_state->array_index == _state->search_index)
+ 			get_next = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[lex_level - 1])
+ 	{
+ 		/*
+ 		 * path search, path so far is ok
+ 		 *
+ 		 * increment the array counter. no point doing this if we already know
+ 		 * the path is bad.
+ 		 *
+ 		 * then check if we have a match.
+ 		 */
+ 
+ 		if (++_state->array_level_index[lex_level - 1] ==
+ 			_state->path_level_index[lex_level - 1])
+ 		{
+ 			if (lex_level == _state->npath)
+ 			{
+ 				/* match and at end of path, so get value */
+ 				get_next = true;
+ 			}
+ 			else
+ 			{
+ 				/* not at end of path just mark path ok */
+ 				_state->pathok[lex_level] = true;
+ 			}
+ 		}
+ 
+ 	}
+ 
+ 	/* same logic as for objects */
+ 	if (get_next)
+ 	{
+ 		if (_state->normalize_results &&
+ 			_state->lex->token_type == JSON_TOKEN_STRING)
+ 		{
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			_state->result_start = _state->lex->token_start;
+ 		}
+ 	}
+ }
+ 
+ static void
+ get_array_element_end(void *state, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_last = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	/* same logic as in get_object_end, modified for arrays */
+ 
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY &&
+ 		_state->array_index == _state->search_index)
+ 	{
+ 		get_last = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[lex_level - 1] &&
+ 			 _state->array_level_index[lex_level - 1] ==
+ 			 _state->path_level_index[lex_level - 1])
+ 	{
+ 		/* done with this element so reset pathok */
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = false;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_last = true;
+ 	}
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int	 len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		if (isnull  && _state->normalize_results)
+ 			_state->tresult = (text *) NULL;
+ 		else
+ 			_state->tresult = cstring_to_text_with_len(_state->result_start, len);
+ 	}
+ }
+ 
+ static void
+ get_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	if (_state->lex->lex_level == 0 && _state->search_type != JSON_SEARCH_PATH)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_get on a scalar")));
+ 	if (_state->next_scalar)
+ 	{
+ 		/* a de-escaped text value is wanted, so supply it */
+ 		_state->tresult = cstring_to_text(token);
+ 		/* make sure the next call to get_scalar doesn't overwrite it */
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
+ 
+ /*
+  * SQL function json_array_length(json) -> int
+  */
+ Datum
+ json_array_length(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 
+ 	AlenState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, false);
+ 	JsonSemAction sem;
+ 
+ 	state = palloc0(sizeof(alenState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	/* palloc0 does this for us */
+ #if 0
+ 	state->count = 0;
+ #endif
+ 	state->lex = lex;
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = alen_object_start;
+ 	sem->scalar = alen_scalar;
+ 	sem->array_element_start = alen_array_element_start;
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	PG_RETURN_INT32(state->count);
+ }
+ 
+ /*
+  * These next two check ensure that the json is an array (since it can't be
+  * a scala or an object).
+  */
+ 
+ static void
+ alen_object_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_array_length on an object")));
+ }
+ 
+ static void
+ alen_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_array_length on a scalar")));
+ }
+ 
+ static void
+ alen_array_element_start(void *state, bool isnull)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	/* just count up all the level 1 elements */
+ 	if (_state->lex->lex_level == 1)
+ 		_state->count++;
+ }
+ 
+ /*
+  * SQL function json_each
+  *
+  * decompose a json object into key value pairs.
+  *
+  * Unlike json_object_keys() this SRF operates in materialize mode,
+  * stashing its results into a Tuplestore object as it goes.
+  * The constriction of tuples is done using a temporary memory context
+  * that is cleared out after each tuple is built.
+  */
+ Datum
+ json_each(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	TupleDesc	tupdesc;
+ 	EachState	state;
+ 
+ 	state = palloc0(sizeof(eachState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = each_array_start;
+ 	sem->scalar = each_scalar;
+ 	sem->object_field_start = each_object_field_start;
+ 	sem->object_field_end = each_object_field_end;
+ 
+ 	state->normalize_results = false;
+ 	state->next_scalar = false;
+ 
+ 	state->lex = lex;
+ 	state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 										   "json_each temporary cxt",
+ 										   ALLOCSET_DEFAULT_MINSIZE,
+ 										   ALLOCSET_DEFAULT_INITSIZE,
+ 										   ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_each_as_text
+  *
+  * decompose a json object into key value pairs with
+  * de-escaped scalar string values.
+  *
+  * See also comments for json_each
+  */
+ Datum
+ json_each_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	TupleDesc	tupdesc;
+ 	EachState	state;
+ 
+ 	state = palloc0(sizeof(eachState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = each_array_start;
+ 	sem->scalar = each_scalar;
+ 	sem->object_field_start = each_object_field_start;
+ 	sem->object_field_end = each_object_field_end;
+ 
+ 	/* next line is what's different from json_each */
+ 	state->normalize_results = true;
+ 	state->next_scalar = false;
+ 
+ 	state->lex = lex;
+ 	state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 										   "json_each temporary cxt",
+ 										   ALLOCSET_DEFAULT_MINSIZE,
+ 										   ALLOCSET_DEFAULT_INITSIZE,
+ 										   ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ static void
+ each_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	/* save a pointer to where the value starts */
+ 	if (_state->lex->lex_level == 1)
+ 	{
+ 		/*
+ 		 * next_scalar will be reset in the object_field_end handler, and
+ 		 * since we know the value is a scalar there is no danger of it being
+ 		 * on while recursing down the tree.
+ 		 */
+ 		if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING)
+ 			_state->next_scalar = true;
+ 		else
+ 			_state->result_start = _state->lex->token_start;
+ 	}
+ }
+ 
+ static void
+ each_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	EachState	_state = (EachState) state;
+ 	MemoryContext old_cxt;
+ 	int			len;
+ 	text	   *val;
+ 	HeapTuple	tuple;
+ 	Datum		values[2];
+ 	bool        nulls[2] = {false, false};
+ 
+ 	/* skip over nested objects */
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 
+ 	/* use the tmp context so we can clean up after each tuple is done */
+ 	old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
+ 
+ 	values[0] = CStringGetTextDatum(fname);
+ 
+ 	if (isnull && _state->normalize_results)
+ 	{
+ 		nulls[1] = true;
+ 		values[1] = NULL;
+ 	}
+ 	else if (_state->next_scalar)
+ 	{
+ 		values[1] = CStringGetTextDatum(_state->normalized_scalar);
+ 		_state->next_scalar = false;
+ 	}
+ 	else
+ 	{
+ 		len = _state->lex->prev_token_terminator - _state->result_start;
+ 		val = cstring_to_text_with_len(_state->result_start, len);
+ 		values[1] = PointerGetDatum(val);
+ 	}
+ 
+ 
+ 	tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
+ 
+ 	tuplestore_puttuple(_state->tuple_store, tuple);
+ 
+ 	/* clean up and switch back */
+ 	MemoryContextSwitchTo(old_cxt);
+ 	MemoryContextReset(_state->tmp_cxt);
+ }
+ 
+ static void
+ each_array_start(void *state)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on an array")));
+ }
+ 
+ static void
+ each_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on a scalar")));
+ 
+ 	/* supply de-escaped value if required */
+ 	if (_state->next_scalar)
+ 		_state->normalized_scalar = token;
+ }
+ 
+ /*
+  * SQL function json_unnest
+  *
+  * get the elements from a json array
+  *
+  * a lot of this processing is similar to the json_each* functions
+  */
+ Datum
+ json_unnest(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 
+ 	/* unnest doesn't need any escaped strings, so use false here */
+ 	JsonLexContext *lex = makeJsonLexContext(json, false);
+ 	JsonSemAction sem;
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	TupleDesc	tupdesc;
+ 	UnnestState state;
+ 
+ 	state = palloc0(sizeof(unnestState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/* it's a simple type, so don't use get_call_result_type() */
+ 	tupdesc = rsi->expectedDesc;
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = unnest_object_start;
+ 	sem->scalar = unnest_scalar;
+ 	sem->array_element_start = unnest_array_element_start;
+ 	sem->array_element_end = unnest_array_element_end;
+ 
+ 	state->lex = lex;
+ 	state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 										   "json_unnest temporary cxt",
+ 										   ALLOCSET_DEFAULT_MINSIZE,
+ 										   ALLOCSET_DEFAULT_INITSIZE,
+ 										   ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ static void
+ unnest_array_element_start(void *state, bool isnull)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	/* save a pointer to where the value starts */
+ 	if (_state->lex->lex_level == 1)
+ 		_state->result_start = _state->lex->token_start;
+ }
+ 
+ static void
+ unnest_array_element_end(void *state, bool isnull)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 	MemoryContext old_cxt;
+ 	int			len;
+ 	text	   *val;
+ 	HeapTuple	tuple;
+ 	Datum		values[1];
+ 	static bool nulls[1] = {false};
+ 
+ 	/* skip over nested objects */
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 
+ 	/* use the tmp context so we can clean up after each tuple is done */
+ 	old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
+ 
+ 	len = _state->lex->prev_token_terminator - _state->result_start;
+ 	val = cstring_to_text_with_len(_state->result_start, len);
+ 
+ 	values[0] = PointerGetDatum(val);
+ 
+ 	tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
+ 
+ 	tuplestore_puttuple(_state->tuple_store, tuple);
+ 
+ 	/* clean up and switch back */
+ 	MemoryContextSwitchTo(old_cxt);
+ 	MemoryContextReset(_state->tmp_cxt);
+ }
+ 
+ static void
+ unnest_object_start(void *state)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on an object")));
+ }
+ 
+ static void
+ unnest_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on a scalar")));
+ 
+ 	/*
+ 	 * json_unnest always returns json, so there's no need to think about
+ 	 * de-escaped values here.
+ 	 */
+ }
+ 
+ /*
+  * SQL function json_populate_record
+  *
+  * set fields in a record from the argument json
+  *
+  * Code adapted shamelessly from hstore's populate_record
+  * which is in turn partly adapted from record_out.
+  *
+  * The json is decomposed into a hash table, in which each
+  * field in the record is then looked up by name.
+  */
+ Datum
+ json_populate_record(PG_FUNCTION_ARGS)
+ {
+ 	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	text	   *json = PG_GETARG_TEXT_P(1);
+ 	bool		use_json_as_text = PG_GETARG_BOOL(2);
+ 	HTAB	   *json_hash;
+ 	HeapTupleHeader rec;
+ 	Oid			tupType;
+ 	int32		tupTypmod;
+ 	TupleDesc	tupdesc;
+ 	HeapTupleData tuple;
+ 	HeapTuple	rettuple;
+ 	RecordIOData *my_extra;
+ 	int			ncolumns;
+ 	int			i;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	char		fname[NAMEDATALEN];
+ 	JsonHashEntry hashentry;
+ 
+ 
+ 	if (!type_is_rowtype(argtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("first argument must be a rowtype")));
+ 
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		if (PG_ARGISNULL(1))
+ 			PG_RETURN_NULL();
+ 
+ 		rec = NULL;
+ 
+ 		/*
+ 		 * have no tuple to look at, so the only source of type info is the
+ 		 * argtype. The lookup_rowtype_tupdesc call below will error out if we
+ 		 * don't have a known composite type oid here.
+ 		 */
+ 		tupType = argtype;
+ 		tupTypmod = -1;
+ 	}
+ 	else
+ 	{
+ 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ 
+ 		if (PG_ARGISNULL(1))
+ 			PG_RETURN_POINTER(rec);
+ 
+ 		/* Extract type info from the tuple itself */
+ 		tupType = HeapTupleHeaderGetTypeId(rec);
+ 		tupTypmod = HeapTupleHeaderGetTypMod(rec);
+ 	}
+ 
+ 	json_hash = get_json_object_as_hash(json, "json_populate_record", use_json_as_text);
+ 
+ 	/*
+ 	 * if the input json is empty, we can only skip the rest if we were passed
+ 	 * in a non-null record, since otherwise there may be issues with domain
+ 	 * nulls.
+ 	 */
+ 	if (hash_get_num_entries(json_hash) == 0 && rec)
+ 		PG_RETURN_POINTER(rec);
+ 
+ 
+ 	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ 	ncolumns = tupdesc->natts;
+ 
+ 	if (rec)
+ 	{
+ 		/* Build a temporary HeapTuple control structure */
+ 		tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ 		ItemPointerSetInvalid(&(tuple.t_self));
+ 		tuple.t_tableOid = InvalidOid;
+ 		tuple.t_data = rec;
+ 	}
+ 
+ 	/*
+ 	 * We arrange to look up the needed I/O info just once per series of
+ 	 * calls, assuming the record type doesn't change underneath us.
+ 	 */
+ 	my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL ||
+ 		my_extra->ncolumns != ncolumns)
+ 	{
+ 		fcinfo->flinfo->fn_extra =
+ 			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 							   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 		my_extra->record_type = InvalidOid;
+ 		my_extra->record_typmod = 0;
+ 	}
+ 
+ 	if (my_extra->record_type != tupType ||
+ 		my_extra->record_typmod != tupTypmod)
+ 	{
+ 		MemSet(my_extra, 0,
+ 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 			   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra->record_type = tupType;
+ 		my_extra->record_typmod = tupTypmod;
+ 		my_extra->ncolumns = ncolumns;
+ 	}
+ 
+ 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
+ 
+ 	if (rec)
+ 	{
+ 		/* Break down the tuple into fields */
+ 		heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ 	}
+ 	else
+ 	{
+ 		for (i = 0; i < ncolumns; ++i)
+ 		{
+ 			values[i] = (Datum) 0;
+ 			nulls[i] = true;
+ 		}
+ 	}
+ 
+ 	for (i = 0; i < ncolumns; ++i)
+ 	{
+ 		ColumnIOData *column_info = &my_extra->columns[i];
+ 		Oid			column_type = tupdesc->attrs[i]->atttypid;
+ 		char	   *value;
+ 
+ 		/* Ignore dropped columns in datatype */
+ 		if (tupdesc->attrs[i]->attisdropped)
+ 		{
+ 			nulls[i] = true;
+ 			continue;
+ 		}
+ 
+ 		memset(fname, 0, NAMEDATALEN);
+ 		strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
+ 		hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+ 
+ 		/*
+ 		 * we can't just skip here if the key wasn't found since we might have
+ 		 * a domain to deal with. If we were passed in a non-null record
+ 		 * datum, we assume that the existing values are valid (if they're
+ 		 * not, then it's not our fault), but if we were passed in a null,
+ 		 * then every field which we don't populate needs to be run through
+ 		 * the input function just in case it's a domain type.
+ 		 */
+ 		if (hashentry == NULL && rec)
+ 			continue;
+ 
+ 		/*
+ 		 * Prepare to convert the column value from text
+ 		 */
+ 		if (column_info->column_type != column_type)
+ 		{
+ 			getTypeInputInfo(column_type,
+ 							 &column_info->typiofunc,
+ 							 &column_info->typioparam);
+ 			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+ 						  fcinfo->flinfo->fn_mcxt);
+ 			column_info->column_type = column_type;
+ 		}
+ 		if (hashentry == NULL || hashentry->isnull)
+ 		{
+ 			/*
+ 			 * need InputFunctionCall to happen even for nulls, so that domain
+ 			 * checks are done
+ 			 */
+ 			values[i] = InputFunctionCall(&column_info->proc, NULL,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = true;
+ 		}
+ 		else
+ 		{
+ 			value = hashentry->val;
+ 
+ 			values[i] = InputFunctionCall(&column_info->proc, value,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = false;
+ 		}
+ 	}
+ 
+ 	rettuple = heap_form_tuple(tupdesc, values, nulls);
+ 
+ 	ReleaseTupleDesc(tupdesc);
+ 
+ 	PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
+ }
+ 
+ /*
+  * get_json_object_as_hash
+  *
+  * decompose a json object into a hash table.
+  *
+  * Currently doesn't allow anything but a flat object. Should this
+  * change?
+  *
+  * funcname argument allows caller to pass in its name for use in
+  * error messages.
+  */
+ static HTAB *
+ get_json_object_as_hash(text *json, char *funcname, bool use_json_as_text)
+ {
+ 	HASHCTL		ctl;
+ 	HTAB	   *tab;
+ 	JHashState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 
+ 	memset(&ctl, 0, sizeof(ctl));
+ 	ctl.keysize = NAMEDATALEN;
+ 	ctl.entrysize = sizeof(jsonHashEntry);
+ 	ctl.hcxt = CurrentMemoryContext;
+ 	tab = hash_create("json object hashtable",
+ 					  100,
+ 					  &ctl,
+ 					  HASH_ELEM | HASH_CONTEXT);
+ 
+ 	state = palloc0(sizeof(jhashState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->function_name = funcname;
+ 	state->hash = tab;
+ 	state->lex = lex;
+ 	state->use_json_as_text = use_json_as_text;
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = hash_array_start;
+ 	sem->scalar = hash_scalar;
+ 	sem->object_field_start = hash_object_field_start;
+ 	sem->object_field_end = hash_object_field_end;
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	return tab;
+ }
+ 
+ static void
+ hash_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->lex_level > 1)
+ 		return;
+ 
+ 	if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
+ 		_state->lex->token_type == JSON_TOKEN_OBJECT_START)
+ 	{
+ 		if (!_state->use_json_as_text)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot call %s on a nested object",
+ 							_state->function_name)));
+ 		_state->save_json_start = _state->lex->token_start;
+ 	}
+ 	else
+ 	{
+ 		/* must be a scalar */
+ 		_state->save_json_start = NULL;
+ 	}
+ }
+ 
+ static void
+ hash_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 	JsonHashEntry hashentry;
+ 	bool		found;
+ 	char		name[NAMEDATALEN];
+ 
+ 	/*
+ 	 * ignore field names >= NAMEDATALEN - they can't match a record field
+ 	 * ignore nested fields.
+ 	 */
+ 	if (_state->lex->lex_level > 2 || strlen(fname) >= NAMEDATALEN)
+ 		return;
+ 
+ 	memset(name, 0, NAMEDATALEN);
+ 	strncpy(name, fname, NAMEDATALEN);
+ 
+ 	hashentry = hash_search(_state->hash, name, HASH_ENTER, &found);
+ 
+ 	if (found)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("duplicate object field name: \"%s\"", fname)));
+ 
+ 	hashentry->isnull = isnull;
+ 	if (_state->save_json_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->save_json_start;
+ 		char	   *val = palloc((len + 1) * sizeof(char));
+ 
+ 		memcpy(val, _state->save_json_start, len);
+ 		val[len] = '\0';
+ 		hashentry->val = val;
+ 	}
+ 	else
+ 	{
+ 		/* must have had a scalar instead */
+ 		hashentry->val = _state->saved_scalar;
+ 	}
+ }
+ 
+ static void
+ hash_array_start(void *state)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call %s on an array", _state->function_name)));
+ }
+ 
+ static void
+ hash_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call %s on a scalar", _state->function_name)));
+ 
+ 	if (_state->lex->lex_level == 1)
+ 		_state->saved_scalar = token;
+ }
+ 
+ 
+ /*
+  * SQL function json_populate_recordset
+  *
+  * set fields in a set of records from the argument json,
+  * which must be an array of objects.
+  *
+  * similar to json_populate_record, but the tuple-building code
+  * is pushed down into the semantic action handlers so it's done
+  * per object in the array.
+  */
+ Datum
+ json_populate_recordset(PG_FUNCTION_ARGS)
+ {
+ 	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	text	   *json = PG_GETARG_TEXT_P(1);
+ 	bool		use_json_as_text = PG_GETARG_BOOL(2);
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	Oid			tupType;
+ 	int32		tupTypmod;
+ 	HeapTupleHeader rec;
+ 	TupleDesc	tupdesc;
+ 	RecordIOData *my_extra;
+ 	int			ncolumns;
+ 	JsonLexContext *lex;
+ 	JsonSemAction sem;
+ 	PopulateRecordsetState state;
+ 
+ 	if (!type_is_rowtype(argtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("first argument must be a rowtype")));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/*
+ 	 * get the tupdesc from the result set info - it must be a record type
+ 	 * because we already checked that arg1 is a record type.
+ 	 */
+ 	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
+ 
+ 	state = palloc0(sizeof(populateRecordsetState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	/* if the json is null send back an empty set */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_NULL();
+ 
+ 	if (PG_ARGISNULL(0))
+ 		rec = NULL;
+ 	else
+ 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ 
+ 	tupType = tupdesc->tdtypeid;
+ 	tupTypmod = tupdesc->tdtypmod;
+ 	ncolumns = tupdesc->natts;
+ 
+ 	lex = makeJsonLexContext(json, true);
+ 
+ 	/*
+ 	 * We arrange to look up the needed I/O info just once per series of
+ 	 * calls, assuming the record type doesn't change underneath us.
+ 	 */
+ 	my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL ||
+ 		my_extra->ncolumns != ncolumns)
+ 	{
+ 		fcinfo->flinfo->fn_extra =
+ 			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 							   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 		my_extra->record_type = InvalidOid;
+ 		my_extra->record_typmod = 0;
+ 	}
+ 
+ 	if (my_extra->record_type != tupType ||
+ 		my_extra->record_typmod != tupTypmod)
+ 	{
+ 		MemSet(my_extra, 0,
+ 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 			   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra->record_type = tupType;
+ 		my_extra->record_typmod = tupTypmod;
+ 		my_extra->ncolumns = ncolumns;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = populate_recordset_array_start;
+ 	sem->array_element_start = populate_recordset_array_element_start;
+ 	sem->scalar = populate_recordset_scalar;
+ 	sem->object_field_start = populate_recordset_object_field_start;
+ 	sem->object_field_end = populate_recordset_object_field_end;
+ 	sem->object_start = populate_recordset_object_start;
+ 	sem->object_end = populate_recordset_object_end;
+ 
+ 	state->lex = lex;
+ 
+ 	state->my_extra = my_extra;
+ 	state->rec = rec;
+ 	state->use_json_as_text = use_json_as_text;
+ 	state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ 
+ }
+ 
+ static void
+ populate_recordset_object_start(void *state)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 	int			lex_level = _state->lex->lex_level;
+ 	HASHCTL		ctl;
+ 
+ 	if (lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call populate_recordset on an object")));
+ 	else if (lex_level > 1 && !_state->use_json_as_text)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			  errmsg("cannot call populate_recordset with nested objects")));
+ 
+ 	/* set up a new hash for this entry */
+ 	memset(&ctl, 0, sizeof(ctl));
+ 	ctl.keysize = NAMEDATALEN;
+ 	ctl.entrysize = sizeof(jsonHashEntry);
+ 	ctl.hcxt = CurrentMemoryContext;
+ 	_state->json_hash = hash_create("json object hashtable",
+ 									100,
+ 									&ctl,
+ 									HASH_ELEM | HASH_CONTEXT);
+ }
+ 
+ static void
+ populate_recordset_object_end(void *state)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 	HTAB	   *json_hash = _state->json_hash;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	char		fname[NAMEDATALEN];
+ 	int			i;
+ 	RecordIOData *my_extra = _state->my_extra;
+ 	int			ncolumns = my_extra->ncolumns;
+ 	TupleDesc	tupdesc = _state->ret_tdesc;
+ 	JsonHashEntry hashentry;
+ 	HeapTupleHeader rec = _state->rec;
+ 	HeapTuple	rettuple;
+ 
+ 	if (_state->lex->lex_level > 1)
+ 		return;
+ 
+ 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
+ 
+ 	if (_state->rec)
+ 	{
+ 		HeapTupleData tuple;
+ 
+ 		/* Build a temporary HeapTuple control structure */
+ 		tuple.t_len = HeapTupleHeaderGetDatumLength(_state->rec);
+ 		ItemPointerSetInvalid(&(tuple.t_self));
+ 		tuple.t_tableOid = InvalidOid;
+ 		tuple.t_data = _state->rec;
+ 
+ 		/* Break down the tuple into fields */
+ 		heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ 	}
+ 	else
+ 	{
+ 		for (i = 0; i < ncolumns; ++i)
+ 		{
+ 			values[i] = (Datum) 0;
+ 			nulls[i] = true;
+ 		}
+ 	}
+ 
+ 	for (i = 0; i < ncolumns; ++i)
+ 	{
+ 		ColumnIOData *column_info = &my_extra->columns[i];
+ 		Oid			column_type = tupdesc->attrs[i]->atttypid;
+ 		char	   *value;
+ 
+ 		/* Ignore dropped columns in datatype */
+ 		if (tupdesc->attrs[i]->attisdropped)
+ 		{
+ 			nulls[i] = true;
+ 			continue;
+ 		}
+ 
+ 		memset(fname, 0, NAMEDATALEN);
+ 		strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
+ 		hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+ 
+ 		/*
+ 		 * we can't just skip here if the key wasn't found since we might have
+ 		 * a domain to deal with. If we were passed in a non-null record
+ 		 * datum, we assume that the existing values are valid (if they're
+ 		 * not, then it's not our fault), but if we were passed in a null,
+ 		 * then every field which we don't populate needs to be run through
+ 		 * the input function just in case it's a domain type.
+ 		 */
+ 		if (hashentry == NULL && rec)
+ 			continue;
+ 
+ 		/*
+ 		 * Prepare to convert the column value from text
+ 		 */
+ 		if (column_info->column_type != column_type)
+ 		{
+ 			getTypeInputInfo(column_type,
+ 							 &column_info->typiofunc,
+ 							 &column_info->typioparam);
+ 			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+ 						  _state->fn_mcxt);
+ 			column_info->column_type = column_type;
+ 		}
+ 		if (hashentry == NULL || hashentry->isnull)
+ 		{
+ 			/*
+ 			 * need InputFunctionCall to happen even for nulls, so that domain
+ 			 * checks are done
+ 			 */
+ 			values[i] = InputFunctionCall(&column_info->proc, NULL,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = true;
+ 		}
+ 		else
+ 		{
+ 			value = hashentry->val;
+ 
+ 			values[i] = InputFunctionCall(&column_info->proc, value,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = false;
+ 		}
+ 	}
+ 
+ 	rettuple = heap_form_tuple(tupdesc, values, nulls);
+ 
+ 	tuplestore_puttuple(_state->tuple_store, rettuple);
+ 
+ 	hash_destroy(json_hash);
+ }
+ 
+ static void
+ populate_recordset_array_element_start(void *state, bool isnull)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level == 1 &&
+ 		_state->lex->token_type != JSON_TOKEN_OBJECT_START)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			 errmsg("must call populate_recordset on an array of objects")));
+ }
+ 
+ static void
+ populate_recordset_array_start(void *state)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level != 0 && !_state->use_json_as_text)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call populate_recordset with nested arrays")));
+ }
+ 
+ static void
+ populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call populate_recordset on a scalar")));
+ 
+ 	if (_state->lex->lex_level == 2)
+ 		_state->saved_scalar = token;
+ }
+ 
+ static void
+ populate_recordset_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level > 2)
+ 		return;
+ 
+ 	if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
+ 		_state->lex->token_type == JSON_TOKEN_OBJECT_START)
+ 	{
+ 		if (!_state->use_json_as_text)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call populate_recordset on a nested object")));
+ 		_state->save_json_start = _state->lex->token_start;
+ 	}
+ 	else
+ 	{
+ 		_state->save_json_start = NULL;
+ 	}
+ }
+ 
+ static void
+ populate_recordset_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 	JsonHashEntry hashentry;
+ 	bool		found;
+ 	char		name[NAMEDATALEN];
+ 
+ 	/*
+ 	 * ignore field names >= NAMEDATALEN - they can't match a record field
+ 	 * ignore nested fields.
+ 	 */
+ 	if (_state->lex->lex_level > 2 || strlen(fname) >= NAMEDATALEN)
+ 		return;
+ 
+ 	memset(name, 0, NAMEDATALEN);
+ 	strncpy(name, fname, NAMEDATALEN);
+ 
+ 	hashentry = hash_search(_state->json_hash, name, HASH_ENTER, &found);
+ 
+ 	if (found)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("duplicate object field name: \"%s\"", fname)));
+ 
+ 	hashentry->isnull = isnull;
+ 	if (_state->save_json_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->save_json_start;
+ 		char	   *val = palloc((len + 1) * sizeof(char));
+ 
+ 		memcpy(val, _state->save_json_start, len);
+ 		val[len] = '\0';
+ 		hashentry->val = val;
+ 	}
+ 	else
+ 	{
+ 		/* must have had a scalar instead */
+ 		hashentry->val = _state->saved_scalar;
+ 	}
+ }
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
***************
*** 1724,1729 **** DESCR("range difference");
--- 1724,1743 ----
  DATA(insert OID = 3900 (  "*"	   PGNSP PGUID b f f 3831 3831 3831 3900 0 range_intersect - - ));
  DESCR("range intersection");
  
+ /* Use function oids here because json_get and json_get_as_text are overloaded */
+ DATA(insert OID = 5100 (  "->"	   PGNSP PGUID b f f 114 25 114 0 0 5001 - - ));
+ DESCR("get json object field");
+ DATA(insert OID = 5101 (  "->>"    PGNSP PGUID b f f 114 25 25 0 0 5002 - - ));
+ DESCR("get json object field as text");
+ DATA(insert OID = 5102 (  "->"	   PGNSP PGUID b f f 114 23 114 0 0 5003 - - ));
+ DESCR("get json array element");
+ DATA(insert OID = 5103 (  "->>"    PGNSP PGUID b f f 114 23 25 0 0 5004 - - ));
+ DESCR("get json array element as text");
+ DATA(insert OID = 5104 (  "->"     PGNSP PGUID b f f 114 1009 114 0 0 json_get_path_op - - ));
+ DESCR("get value from json with path elements");
+ DATA(insert OID = 5105 (  "->>"    PGNSP PGUID b f f 114 1009 25 0 0 json_get_path_as_text_op - - ));
+ DESCR("get value from json as text with path elements");
+ 
  
  /*
   * function prototypes
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4103,4108 **** DESCR("map row to json");
--- 4103,4139 ----
  DATA(insert OID = 3156 (  row_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "2249 16" _null_ _null_ _null_ _null_ row_to_json_pretty _null_ _null_ _null_ ));
  DESCR("map row to json with optional pretty printing");
  
+ DATA(insert OID = 5001 (  json_get		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "114 25" _null_ _null_ _null_ _null_ json_get_ofield _null_ _null_ _null_ ));
+ DESCR("get json object field");
+ DATA(insert OID = 5002 (  json_get_as_text PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "114 25" _null_ _null_ _null_ _null_ json_get_ofield_as_text _null_ _null_ _null_ ));
+ DESCR("get json object field as text");
+ DATA(insert OID = 5003 (  json_get		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "114 23" _null_ _null_ _null_ _null_ json_get_aelem _null_ _null_ _null_ ));
+ DESCR("get json array element");
+ DATA(insert OID = 5004 (  json_get_as_text PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "114 23" _null_ _null_ _null_ _null_ json_get_aelem_as_text _null_ _null_ _null_ ));
+ DESCR("get json array element as text");
+ DATA(insert OID = 5005 (  json_object_keys PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 25 "114" _null_ _null_ _null_ _null_ json_object_keys _null_ _null_ _null_ ));
+ DESCR("get json object keys");
+ DATA(insert OID = 5006 (  json_array_length PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 23 "114" _null_ _null_ _null_ _null_ json_array_length _null_ _null_ _null_ ));
+ DESCR("length of json array");
+ DATA(insert OID = 5007 (  json_each PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 2249 "114" "{114,25,114}" "{i,o,o}" "{from_json,key,value}" _null_ json_each _null_ _null_ _null_ ));
+ DESCR("key value pairs of a json object");
+ DATA(insert OID = 5008 (  json_get_path	   PGNSP PGUID 12 1 0 25 0 f f f f t f s 2 0 114 "114 1009" "{114,1009}" "{i,v}" "{from_json,path_elems}" _null_ json_get_path _null_ _null_ _null_ ));
+ DESCR("get value from json with path elements");
+ DATA(insert OID = 5014 (  json_get_path_op PGNSP PGUID 12 1 0 0 0  f f f f t f s 2 0 114 "114 1009" _null_ _null_ "{from_json,path_elems}" _null_ json_get_path _null_ _null_ _null_ ));
+ DESCR("get value from json with path elements");
+ DATA(insert OID = 5009 (  json_unnest      PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 114 "114" "{114,114}" "{i,o}" "{from_json,value}" _null_ json_unnest _null_ _null_ _null_ ));
+ DESCR("key value pairs of a json object");
+ DATA(insert OID = 5010 (  json_get_path_as_text	   PGNSP PGUID 12 1 0 25 0 f f f f t f s 2 0 25 "114 1009" "{114,1009}" "{i,v}" "{from_json,path_elems}" _null_ json_get_path_as_text _null_ _null_ _null_ ));
+ DESCR("get value from json as text with path elements");
+ DATA(insert OID = 5015 (  json_get_path_as_text_op PGNSP PGUID 12 1 0 0 0  f f f f t f s 2 0 25 "114 1009" _null_ _null_ "{from_json,path_elems}" _null_ json_get_path_as_text _null_ _null_ _null_ ));
+ DESCR("get value from json as text with path elements");
+ DATA(insert OID = 5011 (  json_each_as_text PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 2249 "114" "{114,25,25}" "{i,o,o}" "{from_json,key,value}" _null_ json_each_as_text _null_ _null_ _null_ ));
+ DESCR("key value pairs of a json object");
+ DATA(insert OID = 5012 (  json_populate_record PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2283 "2283 114 16" _null_ _null_ _null_ _null_ json_populate_record _null_ _null_ _null_ ));
+ DESCR("get record fields from a json object");
+ DATA(insert OID = 5013 (  json_populate_recordset PGNSP PGUID 12 1 100 0 0 f f f f f t s 3 0 2283 "2283 114 16" _null_ _null_ _null_ _null_ json_populate_recordset _null_ _null_ _null_ ));
+ DESCR("get set of records with fields from a json array of objects");
+ 
  /* uuid */
  DATA(insert OID = 2952 (  uuid_in		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));
  DESCR("I/O");
*** a/src/include/utils/json.h
--- b/src/include/utils/json.h
***************
*** 17,22 ****
--- 17,23 ----
  #include "fmgr.h"
  #include "lib/stringinfo.h"
  
+ /* functions in json.c */
  extern Datum json_in(PG_FUNCTION_ARGS);
  extern Datum json_out(PG_FUNCTION_ARGS);
  extern Datum json_recv(PG_FUNCTION_ARGS);
***************
*** 27,30 **** extern Datum row_to_json(PG_FUNCTION_ARGS);
--- 28,46 ----
  extern Datum row_to_json_pretty(PG_FUNCTION_ARGS);
  extern void escape_json(StringInfo buf, const char *str);
  
+ /* functions in jsonfuncs.c */
+ extern Datum json_get_aelem_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_get_aelem(PG_FUNCTION_ARGS);
+ extern Datum json_get_ofield_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_get_ofield(PG_FUNCTION_ARGS);
+ extern Datum json_object_keys(PG_FUNCTION_ARGS);
+ extern Datum json_array_length(PG_FUNCTION_ARGS);
+ extern Datum json_each(PG_FUNCTION_ARGS);
+ extern Datum json_each_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_get_path(PG_FUNCTION_ARGS);
+ extern Datum json_get_path_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_unnest(PG_FUNCTION_ARGS);
+ extern Datum json_populate_record(PG_FUNCTION_ARGS);
+ extern Datum json_populate_recordset(PG_FUNCTION_ARGS);
+ 
  #endif   /* JSON_H */
*** /dev/null
--- b/src/include/utils/jsonapi.h
***************
*** 0 ****
--- 1,110 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/utils/jsonapi.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef JSONAPI_H
+ #define JSONAPI_H
+ 
+ #include "lib/stringinfo.h"
+ 
+ typedef enum
+ {
+ 	JSON_TOKEN_INVALID,
+ 	JSON_TOKEN_STRING,
+ 	JSON_TOKEN_NUMBER,
+ 	JSON_TOKEN_OBJECT_START,
+ 	JSON_TOKEN_OBJECT_END,
+ 	JSON_TOKEN_ARRAY_START,
+ 	JSON_TOKEN_ARRAY_END,
+ 	JSON_TOKEN_COMMA,
+ 	JSON_TOKEN_COLON,
+ 	JSON_TOKEN_TRUE,
+ 	JSON_TOKEN_FALSE,
+ 	JSON_TOKEN_NULL,
+ 	JSON_TOKEN_END,
+ }	JsonTokenType;
+ 
+ 
+ /*
+  * All the fields in this structure should be treated as read-only.
+  *
+  * If strval is not null, then it should contain the de-escaped value
+  * of the lexeme if it's a string. Otherwise most of these field names
+  * should be self-explanatory.
+  *
+  * line_number and line_start are principally for use by the parser's
+  * error reporting routines.
+  * token_terminator and prev_token_terminator point to the character
+  * AFTER the end of the token, i.e. where there would be a nul byte
+  * if we were using nul-terminated strings.
+  */
+ typedef struct JsonLexContext
+ {
+ 	char	   *input;
+ 	int			input_length;
+ 	char	   *token_start;
+ 	char	   *token_terminator;
+ 	char	   *prev_token_terminator;
+ 	JsonTokenType token_type;
+ 	int			lex_level;
+ 	int			line_number;
+ 	char	   *line_start;
+ 	StringInfo	strval;
+ } JsonLexContext;
+ 
+ typedef void (*json_struct_action) (void *state);
+ typedef void (*json_ofield_action) (void *state, char *fname, bool isnull);
+ typedef void (*json_aelem_action) (void *state, bool isnull);
+ typedef void (*json_scalar_action) (void *state, char *token, JsonTokenType tokentype);
+ 
+ 
+ /*
+  * Semantic Action structure for use in parsing json.
+  * Any of these actions can be NULL, in which case nothing is done at that
+  * point, Likewise, semstate can be NULL. Using an all-NULL structure amounts
+  * to doing a pure parse with no side-effects, and is therefore exactly
+  * what the json input routines do.
+  */
+ typedef struct jsonSemAction
+ {
+ 	void	   *semstate;
+ 	json_struct_action object_start;
+ 	json_struct_action object_end;
+ 	json_struct_action array_start;
+ 	json_struct_action array_end;
+ 	json_ofield_action object_field_start;
+ 	json_ofield_action object_field_end;
+ 	json_aelem_action array_element_start;
+ 	json_aelem_action array_element_end;
+ 	json_scalar_action scalar;
+ }	jsonSemAction, *JsonSemAction;
+ 
+ /*
+  * parse_json will parse the string in the lex calling the
+  * action functions in sem at the appropriate points. It is
+  * up to them to keep what state they need	in semstate. If they
+  * need access to the state of the lexer, then its pointer
+  * should be passed to them as a member of whatever semstate
+  * points to. If the action pointers are NULL the parser
+  * does nothing and just continues.
+  */
+ extern void pg_parse_json(JsonLexContext *lex, JsonSemAction sem);
+ 
+ /*
+  * constructor for JsonLexContext, with or without strval element.
+  * If supplied, the strval element will contain a de-escaped version of
+  * the lexeme. However, doing this imposes a performance penalty, so
+  * it should be avoided if the de-escaped lexeme is not required.
+  */
+ extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes);
+ 
+ #endif   /* JSONAPI_H */
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
***************
*** 433,435 **** FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "json
--- 433,873 ----
   {"jsonfield":{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}}
  (1 row)
  
+ -- json extraction functions
+ CREATE TEMP TABLE test_json (
+        json_type text,
+        test_json json
+ );
+ INSERT INTO test_json VALUES
+ ('scalar','"a scalar"'),
+ ('array','["zero", "one","two",null,"four","five"]'),
+ ('object','{"field1":"val1","field2":"val2","field3":null}');
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ ERROR:  cannot call json_get on a scalar
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'array';
+ ERROR:  cannot call json_get(fieldname) on a non-object
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'object';
+  json_get 
+ ----------
+  
+ (1 row)
+ 
+ SELECT json_get(test_json,'field2') 
+ FROM test_json
+ WHERE json_type = 'object';
+  json_get 
+ ----------
+  "val2"
+ (1 row)
+ 
+ SELECT test_json->'field2'
+ FROM test_json
+ WHERE json_type = 'object';
+  ?column? 
+ ----------
+  "val2"
+ (1 row)
+ 
+ SELECT test_json->>'field2' 
+ FROM test_json
+ WHERE json_type = 'object';
+  ?column? 
+ ----------
+  val2
+ (1 row)
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ ERROR:  cannot call json_get on a scalar
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+  json_get 
+ ----------
+  "two"
+ (1 row)
+ 
+ SELECT json_get(test_json,2)
+ FROM test_json
+ WHERE json_type = 'object';
+ ERROR:  cannot call json_get(int) on a non-array
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+  json_get 
+ ----------
+  "two"
+ (1 row)
+ 
+ SELECT test_json->2 
+ FROM test_json
+ WHERE json_type = 'array';
+  ?column? 
+ ----------
+  "two"
+ (1 row)
+ 
+ SELECT test_json->>2
+ FROM test_json
+ WHERE json_type = 'array';
+  ?column? 
+ ----------
+  two
+ (1 row)
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'scalar';
+ ERROR:  cannot call json_object_keys on a scalar
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'array';
+ ERROR:  cannot call json_object_keys on an array
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'object';
+  json_object_keys 
+ ------------------
+  field1
+  field2
+  field3
+ (3 rows)
+ 
+ -- nulls
+ select (test_json->'field3') is null as expect_false
+ from test_json
+ where json_type = 'object';
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select (test_json->>'field3') is null as expect_true
+ from test_json
+ where json_type = 'object';
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ select (test_json->3) is null as expect_false
+ from test_json
+ where json_type = 'array';
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select (test_json->>3) is null as expect_true
+ from test_json
+ where json_type = 'array';
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ -- array length
+ SELECT json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+  json_array_length 
+ -------------------
+                  5
+ (1 row)
+ 
+ SELECT json_array_length('[]');
+  json_array_length 
+ -------------------
+                  0
+ (1 row)
+ 
+ SELECT json_array_length('{"f1":1,"f2":[5,6]}');
+ ERROR:  cannot call json_array_length on an object
+ SELECT json_array_length('4');
+ ERROR:  cannot call json_array_length on a scalar
+ -- each
+ select json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+      json_each     
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 rows)
+ 
+ select * from json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+  key |   value   
+ -----+-----------
+  f1  | [1,2,3]
+  f2  | {"f3":1}
+  f4  | null
+  f5  | 99
+  f6  | "stringy"
+ (5 rows)
+ 
+ select json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,)
+  (f5,null)
+ (4 rows)
+ 
+ select * from json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+  key |  value   
+ -----+----------
+  f1  | [1,2,3]
+  f2  | {"f3":1}
+  f4  | 
+  f5  | 99
+  f6  | stringy
+ (5 rows)
+ 
+ -- get_path, get_path_as_text
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+  json_get_path 
+ ---------------
+  "stringy"
+ (1 row)
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+  json_get_path 
+ ---------------
+  {"f3":1}
+ (1 row)
+ 
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+  json_get_path 
+ ---------------
+  "f3"
+ (1 row)
+ 
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+  json_get_path 
+ ---------------
+  1
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+  json_get_path_as_text 
+ -----------------------
+  stringy
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+  json_get_path_as_text 
+ -----------------------
+  {"f3":1}
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+  json_get_path_as_text 
+ -----------------------
+  f3
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+  json_get_path_as_text 
+ -----------------------
+  1
+ (1 row)
+ 
+ -- get_path nulls
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_false;
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_true;
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_false;
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_true;
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ -- get_path operators
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->array['f4','f6'];
+  ?column?  
+ -----------
+  "stringy"
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->array['f2'];
+  ?column? 
+ ----------
+  {"f3":1}
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->array['f2','0'];
+  ?column? 
+ ----------
+  "f3"
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->array['f2','1'];
+  ?column? 
+ ----------
+  1
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f4','f6'];
+  ?column? 
+ ----------
+  stringy
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f2'];
+  ?column? 
+ ----------
+  {"f3":1}
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f2','0'];
+  ?column? 
+ ----------
+  f3
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f2','1'];
+  ?column? 
+ ----------
+  1
+ (1 row)
+ 
+ --unnest
+ select json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+       json_unnest      
+ -----------------------
+  1
+  true
+  [1,[2,3]]
+  null
+  {"f1":1,"f2":[7,8,9]}
+  false
+ (6 rows)
+ 
+ select * from json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+          value         
+ -----------------------
+  1
+  true
+  [1,[2,3]]
+  null
+  {"f1":1,"f2":[7,8,9]}
+  false
+ (6 rows)
+ 
+ -- populate_record
+ create type jpop as (a text, b int, c timestamp);
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+    a    | b | c 
+ --------+---+---
+  blurfl |   | 
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl | 3 | Mon Dec 31 15:30:56 2012
+ (1 row)
+ 
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}', true) q;
+    a    | b | c 
+ --------+---+---
+  blurfl |   | 
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}', true) q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl | 3 | Mon Dec 31 15:30:56 2012
+ (1 row)
+ 
+ select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a        | b | c 
+ -----------------+---+---
+  [100,200,false] |   | 
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a        | b |            c             
+ -----------------+---+--------------------------
+  [100,200,false] | 3 | Mon Dec 31 15:30:56 2012
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}', true) q;
+ ERROR:  invalid input syntax for type timestamp: "[100,200,false]"
+ -- populate_recordset
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl |   | 
+         | 3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+    a    | b  |            c             
+ --------+----+--------------------------
+  blurfl | 99 | 
+  def    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl |   | 
+         | 3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+    a    | b  |            c             
+ --------+----+--------------------------
+  blurfl | 99 | 
+  def    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+        a       | b  |            c             
+ ---------------+----+--------------------------
+  [100,200,300] | 99 | 
+  {"z":true}    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ ERROR:  invalid input syntax for type timestamp: "[100,200,300]"
+ -- using the default use_json_as_text argument
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl |   | 
+         | 3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+    a    | b  |            c             
+ --------+----+--------------------------
+  blurfl | 99 | 
+  def    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ ERROR:  cannot call populate_recordset on a nested object
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ ERROR:  cannot call populate_recordset on a nested object
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
***************
*** 113,115 **** FROM (SELECT '-Infinity'::float8 AS "float8field") q;
--- 113,288 ----
  -- json input
  SELECT row_to_json(q)
  FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "jsonfield") q;
+ 
+ 
+ -- json extraction functions
+ 
+ CREATE TEMP TABLE test_json (
+        json_type text,
+        test_json json
+ );
+ 
+ INSERT INTO test_json VALUES
+ ('scalar','"a scalar"'),
+ ('array','["zero", "one","two",null,"four","five"]'),
+ ('object','{"field1":"val1","field2":"val2","field3":null}');
+ 
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ 
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT json_get(test_json,'field2') 
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT test_json->'field2'
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT test_json->>'field2' 
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_get(test_json,2)
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT test_json->2 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT test_json->>2
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'scalar';
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ -- nulls
+ 
+ select (test_json->'field3') is null as expect_false
+ from test_json
+ where json_type = 'object';
+ 
+ select (test_json->>'field3') is null as expect_true
+ from test_json
+ where json_type = 'object';
+ 
+ select (test_json->3) is null as expect_false
+ from test_json
+ where json_type = 'array';
+ 
+ select (test_json->>3) is null as expect_true
+ from test_json
+ where json_type = 'array';
+ 
+ 
+ -- array length
+ 
+ SELECT json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+ 
+ SELECT json_array_length('[]');
+ 
+ SELECT json_array_length('{"f1":1,"f2":[5,6]}');
+ 
+ SELECT json_array_length('4');
+ 
+ -- each
+ 
+ select json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+ select * from json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ 
+ select json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+ select * from json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ 
+ -- get_path, get_path_as_text
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ 
+ -- get_path nulls
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_false;
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_true;
+ select json_get_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_false;
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_true;
+ 
+ -- get_path operators
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->array['f4','f6'];
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->array['f2'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->array['f2','0'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->array['f2','1'];
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f4','f6'];
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f2'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f2','0'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json->>array['f2','1'];
+ 
+ --unnest
+ 
+ select json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+ select * from json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+ 
+ -- populate_record
+ create type jpop as (a text, b int, c timestamp);
+ 
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+ 
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}', true) q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}', true) q;
+ 
+ select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}', true) q;
+ 
+ -- populate_recordset
+ 
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ 
+ -- using the default use_json_as_text argument
+ 
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
#5Yeb Havinga
yebhavinga@gmail.com
In reply to: Andrew Dunstan (#4)
Re: JSON NULLs

On 2013-02-08 15:15, Andrew Dunstan wrote:

Revised patch attached. The problem also existed with the get*_as_text
functions (and their operators). Some additional regression tests are
added to test these cases.

Hi,

I did some minor things with the patch today.

1. thanks for the work on the json type, great to see it in Postgres and
also more functions on it!

2.
during compile on

jsonfuncs.c: In function `each_object_field_end':
jsonfuncs.c:1151:13: warning: assignment makes integer from pointer
without a cast

yeb@unix:~/ff$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro
4.5.2-8ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-4.5/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr
--program-suffix=-4.5 --enable-shared --enable-multiarch
--with-multiarch-defaults=x86_64-linux-gnu --enable-linker-build-id
--with-system-zlib --libexecdir=/usr/lib/x86_64-linux-gnu
--without-included-gettext --enable-threads=posix
--with-gxx-include-dir=/usr/include/c++/4.5
--libdir=/usr/lib/x86_64-linux-gnu --enable-nls --with-sysroot=/
--enable-clocale=gnu --enable-libstdcxx-debug
--enable-libstdcxx-time=yes --enable-plugin --enable-gold
--enable-ld=default --with-plugin-ld=ld.gold --enable-objc-gc
--disable-werror --with-arch-32=i686 --with-tune=generic
--enable-checking=release --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)

3. I was wondering how to access the first author from this json snippet:

{
"id": "QZr82w_eSi8C",
"etag": "KZ+JsrkCdqw",
"volumeInfo": {
"title": "Heads Up Software Construction",
"authors": [
"Dan Malone",
"Dave Riles"
],

and played a bit with json_get_path_as_text(document, 'volumeInfo',
'authors') that accepts a list of keys as arguments. Have you thought
about an implementation that would accept a single path argument like
'volumeInfo.authors[0]' ? This might be more powerful and easy to use,
since the user does not need to call another function to get the first
element from the author array, and the function call does not need to be
changed when path lenghts change.

My apologies if this has been discussed before - I've gone through
threads from nov 2012 but did not find a previous discussion about this
topic.

regards,
Yeb Havinga

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

#6Andrew Dunstan
andrew@dunslane.net
In reply to: Yeb Havinga (#5)
Re: JSON NULLs

On 02/10/2013 05:43 AM, Yeb Havinga wrote:

On 2013-02-08 15:15, Andrew Dunstan wrote:

Revised patch attached. The problem also existed with the
get*_as_text functions (and their operators). Some additional
regression tests are added to test these cases.

Hi,

I did some minor things with the patch today.

1. thanks for the work on the json type, great to see it in Postgres
and also more functions on it!

2.
during compile on

jsonfuncs.c: In function `each_object_field_end':
jsonfuncs.c:1151:13: warning: assignment makes integer from pointer
without a cast

Thanks, I have fixed this in my code, and it will be included in the
next patch I post.

3. I was wondering how to access the first author from this json snippet:

{
"id": "QZr82w_eSi8C",
"etag": "KZ+JsrkCdqw",
"volumeInfo": {
"title": "Heads Up Software Construction",
"authors": [
"Dan Malone",
"Dave Riles"
],

and played a bit with json_get_path_as_text(document, 'volumeInfo',
'authors') that accepts a list of keys as arguments. Have you thought
about an implementation that would accept a single path argument like
'volumeInfo.authors[0]' ? This might be more powerful and easy to use,
since the user does not need to call another function to get the first
element from the author array, and the function call does not need to
be changed when path lenghts change.

try:

json_get_path_as_text(document, 'volumeInfo', 'authors', '0')

There are other ways to spell this, too:

json_get_path_as_text(document, variadic
'{volumeInfo,authors,0}'::text[])

or

document->>'{volumeInfo,authors,0}'::text[]

I'm actually wondering if we should use different operator names for the
get_path*op functions so we wouldn't need to type qualify the path
argument. Maybe ?> and ?>> although I'm reluctant to use ? in an
operator given the recent JDBC discussion. Or perhaps #> and #>>.

cheers

andrew

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

#7Yeb Havinga
yebhavinga@gmail.com
In reply to: Andrew Dunstan (#6)
Re: JSON NULLs

On 2013-02-10 16:03, Andrew Dunstan wrote:

On 02/10/2013 05:43 AM, Yeb Havinga wrote:

3. I was wondering how to access the first author from this json
snippet:

{
"id": "QZr82w_eSi8C",
"etag": "KZ+JsrkCdqw",
"volumeInfo": {
"title": "Heads Up Software Construction",
"authors": [
"Dan Malone",
"Dave Riles"
],

try:

json_get_path_as_text(document, 'volumeInfo', 'authors', '0')

There are other ways to spell this, too:

json_get_path_as_text(document, variadic
'{volumeInfo,authors,0}'::text[])

or

document->>'{volumeInfo,authors,0}'::text[]

That works very nice, thanks!

I'm actually wondering if we should use different operator names for
the get_path*op functions so we wouldn't need to type qualify the path
argument. Maybe ?> and ?>> although I'm reluctant to use ? in an
operator given the recent JDBC discussion. Or perhaps #> and #>>.

different operator name: +1.

thanks
Yeb

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

#8Andrew Dunstan
andrew@dunslane.net
In reply to: Yeb Havinga (#7)
1 attachment(s)
Re: JSON NULLs

On 02/11/2013 03:21 AM, Yeb Havinga wrote:

I'm actually wondering if we should use different operator names for
the get_path*op functions so we wouldn't need to type qualify the
path argument. Maybe ?> and ?>> although I'm reluctant to use ? in an
operator given the recent JDBC discussion. Or perhaps #> and #>>.

different operator name: +1.

OK, updated patch attached. I went with #> and #>>.

cheers

andrew

Attachments:

jsonapi9.patchtext/x-patch; name=jsonapi9.patchDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 9632,9641 **** table2-mapping
  
    <table id="functions-json-table">
      <title>JSON Support Functions</title>
!     <tgroup cols="4">
       <thead>
        <row>
         <entry>Function</entry>
         <entry>Description</entry>
         <entry>Example</entry>
         <entry>Example Result</entry>
--- 9632,9642 ----
  
    <table id="functions-json-table">
      <title>JSON Support Functions</title>
!     <tgroup cols="5">
       <thead>
        <row>
         <entry>Function</entry>
+        <entry>Return Type</entry>
         <entry>Description</entry>
         <entry>Example</entry>
         <entry>Example Result</entry>
***************
*** 9649,9654 **** table2-mapping
--- 9650,9656 ----
           </indexterm>
           <literal>array_to_json(anyarray [, pretty_bool])</literal>
         </entry>
+        <entry>json</entry>
         <entry>
           Returns the array as JSON. A PostgreSQL multidimensional array
           becomes a JSON array of arrays. Line feeds will be added between
***************
*** 9664,9669 **** table2-mapping
--- 9666,9672 ----
           </indexterm>
           <literal>row_to_json(record [, pretty_bool])</literal>
         </entry>
+        <entry>json</entry>
         <entry>
           Returns the row as JSON. Line feeds will be added between level
           1 elements if <parameter>pretty_bool</parameter> is true.
***************
*** 9671,9680 **** table2-mapping
         <entry><literal>row_to_json(row(1,'foo'))</literal></entry>
         <entry><literal>{"f1":1,"f2":"foo"}</literal></entry>
        </row>
       </tbody>
      </tgroup>
     </table>
! 
   </sect1>
  
   <sect1 id="functions-sequence">
--- 9674,9963 ----
         <entry><literal>row_to_json(row(1,'foo'))</literal></entry>
         <entry><literal>{"f1":1,"f2":"foo"}</literal></entry>
        </row>
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_array_length</primary>
+          </indexterm>
+          <literal>json_array_length(json)</literal>
+        </entry>
+        <entry>int</entry>
+        <entry>
+          Returns the number of elements in the outermost json array. 
+        </entry>
+        <entry><literal>json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]')</literal></entry>
+        <entry><literal>5</literal></entry>
+       </row>
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_each</primary>
+          </indexterm>
+          <literal>json_each(json)</literal>
+        </entry>
+        <entry>SETOF key text, value json</entry>
+        <entry>
+          Expands the outermost json object into a set of key/value pairs.
+        </entry>
+        <entry><literal>select * from json_each_as_text('{"a":"foo", "b":"bar"}')</literal></entry>
+        <entry>
+ <programlisting>
+  key | value 
+ -----+-------
+  a   | "foo"
+  b   | "bar"
+  </programlisting>       
+        </entry>
+       </row>      
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_each_as_text</primary>
+          </indexterm>
+          <literal>json_each_as_text(from_json json)</literal>
+        </entry>
+        <entry>SETOF key text, value text</entry>
+        <entry>
+          Expands the outermost json object into a set of key/value pairs. The
+          returned value will be of type text.
+        </entry>
+        <entry><literal>select * from json_each_as_text('{"a":"foo", "b":"bar"}')</literal></entry>
+        <entry>
+ <programlisting>
+  key | value 
+ -----+-------
+  a   | foo
+  b   | bar 
+  </programlisting>
+        </entry>
+       </row>       
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get</primary>
+          </indexterm>
+          <literal>json_get(json, index int)</literal>
+        </entry>
+        <entry>json</entry>
+        <entry>
+          Returns nth json object from a json array.  Array indexing is zero based.   
+        </entry>
+        <entry><literal>json_get('[1,2,3]', 2)</literal></entry>
+        <entry><literal>3</literal></entry>
+       </row>           
+       <row>
+        <entry>
+          <literal>json_get(json, key text)</literal>
+        </entry>
+        <entry>json</entry>       
+        <entry>
+           Returns value of json object named by key
+        </entry>
+        <entry><literal>json_get('{"f1":"abc"}', 'f1')</literal></entry>
+        <entry><literal>"abc"</literal></entry>
+       </row>     
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get_as_text</primary>
+          </indexterm>
+          <literal>json_get_as_text(json, index int)</literal>
+        </entry>
+        <entry>text</entry>
+        <entry>
+          Returns nth json object from a json array as SQL scalar (that is, 
+          without any json quoting or escaping).  Array indexing is zero based.
+        </entry>
+        <entry><literal>json_get('{"f1":"abc"}', 'f1')</literal></entry>
+        <entry><literal>abc</literal></entry>
+       </row>   
+       <row>
+        <entry>
+          <literal>json_get(json, key text)</literal>
+        </entry>
+        <entry>text</entry>       
+        <entry>
+           Returns value of json object named by key as SQL scalar (that is, 
+           without any json quoting or escaping)
+        </entry>
+        <entry><literal>json_get('{"f1":[1,2,3],"f2":{"f3":1}}', 'f1')</literal></entry>
+        <entry><literal>[1,2,3]</literal></entry>
+       </row>        
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get_path</primary>
+          </indexterm>
+          <literal>json_get_path(from_json json, VARIADIC path_elems text[])</literal>
+        </entry>
+        <entry>json</entry>
+        <entry>
+          Returns json object pointed to by <parameter>path_elems</parameter>.
+        </entry>
+        <entry><literal>json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4')</literal></entry>
+        <entry><literal>{"f5":99,"f6":"foo"}</literal></entry>
+       </row>   
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_get_path_as_text</primary>
+          </indexterm>
+          <literal>json_get_path_as_text(from_json json, VARIADIC path_elems text[])</literal>
+        </entry>
+        <entry>text</entry>
+        <entry>
+          Returns json object pointed to by <parameter>path_elems</parameter>.
+        </entry>
+        <entry><literal>json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6')</literal></entry>
+        <entry><literal>foo</literal></entry>
+       </row>   
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_object_keys</primary>
+          </indexterm>
+          <literal>json_object_keys(json)</literal>
+        </entry>
+        <entry>SETOF text</entry>
+        <entry>
+           Returns set of keys in the json object.  Only the "outer" object will be displayed.
+        </entry>
+        <entry><literal>json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}')</literal></entry>
+        <entry>
+ <programlisting>
+  json_object_keys 
+ ------------------
+  f1
+  f2
+ </programlisting>
+        </entry>
+       </row>         
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_populate_record</primary>
+          </indexterm>
+          <literal>json_populate_record(base anyelement, from_json json, [, use_json_as_text bool=false]</literal>
+        </entry>
+        <entry>anyelement</entry>
+        <entry>
+          Expands the object in from_json to a row whose columns match 
+          the record type defined by base. Conversion will be best 
+          effort; columns in base with no corresponding key in from_json 
+          will be left null.  A column may only be specified once.         
+        </entry>
+        <entry><literal>json_populate_record(null::x, '{"a":1,"b":2}')</literal></entry>
+        <entry>
+ <programlisting>       
+  a | b 
+ ---+---
+  1 | 2
+ </programlisting>
+        </entry>
+       </row>      
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_populate_recordset</primary>
+          </indexterm>
+          <literal>json_populate_recordset(base anyelement, from_json json, [, use_json_as_text bool=false]</literal>
+        </entry>
+        <entry>SETOF anyelement</entry>
+        <entry>
+          Expands the outermost set of objects in from_json to a set 
+          whose columns match the record type defined by base.
+          Conversion will be best effort; columns in base with no
+          corresponding key in from_json will be left null.  A column 
+          may only be specified once.
+        </entry>
+        <entry><literal>json_populate_recordset(null::x, '[{"a":1,"b":2},{"a":3,"b":4}]')</literal></entry>
+        <entry>
+ <programlisting>       
+  a | b 
+ ---+---
+  1 | 2
+  3 | 4
+  </programlisting>
+        </entry>
+       </row>   
+       <row>
+        <entry>
+          <indexterm>
+           <primary>json_unnest</primary>
+          </indexterm>
+          <literal>json_unnest(json)</literal>
+        </entry>
+        <entry>SETOF json</entry>
+        <entry>
+          Expands a json array to a set of json elements.  However, 
+          unlike standard unnest only the outermost array is 
+          expanded.
+        </entry>
+        <entry><literal>json_unnest('[1,true, [2,false]]')</literal></entry>
+        <entry>
+ <programlisting>
+    value   
+ -----------
+  1
+  true
+  [2,false]
+ </programlisting>
+        </entry>
+       </row>   
       </tbody>
      </tgroup>
     </table>
!    <table id="functions-json-op-table">
!      <title>JSON Operators</title>
!      <tgroup cols="4">
!       <thead>
!        <row>
!         <entry>Operator</entry>
!         <entry>Right Operand Type</entry>
!         <entry>Description</entry>
!         <entry>Example</entry>
!        </row>
!       </thead>
!       <tbody>
!        <row>
!         <entry><literal>-&gt;</literal></entry>
!         <entry>int</entry>
!         <entry>Get JSON array element</entry>
!         <entry><literal>'[1,2,3]'::json-&gt;2</literal></entry>
!        </row>
!        <row>
!         <entry><literal>-&gt;</literal></entry>
!         <entry>text</entry>
!         <entry>Get JSON object field</entry>
!         <entry><literal>'{"a":1,"b":2}'::json-&gt;'b'</literal></entry>
!        </row>
!         <row>
!         <entry><literal>-&gt;&gt;</literal></entry>
!         <entry>int</entry>
!         <entry>Get JSON array element as text</entry>
!         <entry><literal>'[1,2,3]'::json-&gt;&gt;2</literal></entry>
!        </row>
!        <row>
!         <entry><literal>-&gt;&gt;</literal></entry>
!         <entry>text</entry>
!         <entry>Get JSON object field as text</entry>
!         <entry><literal>'{"a":1,"b":2}'::json-&gt;&gt;'b'</literal></entry>
!        </row>
!        <row>
!         <entry><literal>#&gt;</literal></entry>
!         <entry>array of text</entry>
!         <entry>Get JSON object at specified path</entry>
!         <entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#&gt;'{a,2}'</literal></entry>
!        </row>
!        <row>
!         <entry><literal>#&gt;&gt;</literal></entry>
!         <entry>array of text</entry>
!         <entry>Get JSON object at specified path as text</entry>
!         <entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#&gt;&gt;'{a,2}'</literal></entry>
!        </row>
!       </tbody>
!      </tgroup>
!    </table>   
   </sect1>
  
   <sect1 id="functions-sequence">
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 773,775 **** COMMENT ON FUNCTION ts_debug(text) IS
--- 773,783 ----
  CREATE OR REPLACE FUNCTION
    pg_start_backup(label text, fast boolean DEFAULT false)
    RETURNS text STRICT VOLATILE LANGUAGE internal AS 'pg_start_backup';
+ 
+ CREATE OR REPLACE FUNCTION 
+   json_populate_record(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
+   RETURNS anyelement LANGUAGE internal STABLE AS 'json_populate_record';
+ 
+ CREATE OR REPLACE FUNCTION 
+   json_populate_recordset(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
+   RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100  AS 'json_populate_recordset';
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 19,26 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o like.o lockfuncs.o \
! 	misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
--- 19,26 ----
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
! 	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
***************
*** 24,92 ****
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/json.h"
  #include "utils/typcache.h"
  
! typedef enum					/* types of JSON values */
! {
! 	JSON_VALUE_INVALID,			/* non-value tokens are reported as this */
! 	JSON_VALUE_STRING,
! 	JSON_VALUE_NUMBER,
! 	JSON_VALUE_OBJECT,
! 	JSON_VALUE_ARRAY,
! 	JSON_VALUE_TRUE,
! 	JSON_VALUE_FALSE,
! 	JSON_VALUE_NULL
! } JsonValueType;
! 
! typedef struct					/* state of JSON lexer */
! {
! 	char	   *input;			/* whole string being parsed */
! 	char	   *token_start;	/* start of current token within input */
! 	char	   *token_terminator; /* end of previous or current token */
! 	JsonValueType token_type;	/* type of current token, once it's known */
! } JsonLexContext;
! 
! typedef enum					/* states of JSON parser */
  {
  	JSON_PARSE_VALUE,			/* expecting a value */
  	JSON_PARSE_ARRAY_START,		/* saw '[', expecting value or ']' */
  	JSON_PARSE_ARRAY_NEXT,		/* saw array element, expecting ',' or ']' */
  	JSON_PARSE_OBJECT_START,	/* saw '{', expecting label or '}' */
  	JSON_PARSE_OBJECT_LABEL,	/* saw object label, expecting ':' */
  	JSON_PARSE_OBJECT_NEXT,		/* saw object value, expecting ',' or '}' */
! 	JSON_PARSE_OBJECT_COMMA		/* saw object ',', expecting next label */
! } JsonParseState;
! 
! typedef struct JsonParseStack	/* the parser state has to be stackable */
! {
! 	JsonParseState state;
! 	/* currently only need the state enum, but maybe someday more stuff */
! } JsonParseStack;
! 
! typedef enum					/* required operations on state stack */
! {
! 	JSON_STACKOP_NONE,			/* no-op */
! 	JSON_STACKOP_PUSH,			/* push new JSON_PARSE_VALUE stack item */
! 	JSON_STACKOP_PUSH_WITH_PUSHBACK, /* push, then rescan current token */
! 	JSON_STACKOP_POP			/* pop, or expect end of input if no stack */
! } JsonStackOp;
! 
! static void json_validate_cstring(char *input);
! static void json_lex(JsonLexContext *lex);
! static void json_lex_string(JsonLexContext *lex);
! static void json_lex_number(JsonLexContext *lex, char *s);
! static void report_parse_error(JsonParseStack *stack, JsonLexContext *lex);
  static void report_invalid_token(JsonLexContext *lex);
! static int report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
  static void composite_to_json(Datum composite, StringInfo result,
! 							  bool use_line_feeds);
  static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
  				  TYPCATEGORY tcategory, Oid typoutputfunc,
  				  bool use_line_feeds);
  static void array_to_json_internal(Datum array, StringInfo result,
! 								   bool use_line_feeds);
  
  /* fake type category for JSON so we can distinguish it in datum_to_json */
  #define TYPCATEGORY_JSON 'j'
--- 24,141 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/json.h"
+ #include "utils/jsonapi.h"
  #include "utils/typcache.h"
  
! /*
!  * The context of the parser is maintained by the recursive descent
!  * mechanism, but is passed explicitly to the error reporting routine
!  * for better diagnostics.
!  */
! typedef enum					/* contexts of JSON parser */
  {
  	JSON_PARSE_VALUE,			/* expecting a value */
+ 	JSON_PARSE_STRING,			/* expecting a string (for a field name) */
  	JSON_PARSE_ARRAY_START,		/* saw '[', expecting value or ']' */
  	JSON_PARSE_ARRAY_NEXT,		/* saw array element, expecting ',' or ']' */
  	JSON_PARSE_OBJECT_START,	/* saw '{', expecting label or '}' */
  	JSON_PARSE_OBJECT_LABEL,	/* saw object label, expecting ':' */
  	JSON_PARSE_OBJECT_NEXT,		/* saw object value, expecting ',' or '}' */
! 	JSON_PARSE_OBJECT_COMMA,	/* saw object ',', expecting next label */
! 	JSON_PARSE_END				/* saw the end of a document, expect nothing */
! }	JsonParseContext;
! 
! static inline void json_lex(JsonLexContext *lex);
! static inline void json_lex_string(JsonLexContext *lex);
! static inline void json_lex_number(JsonLexContext *lex, char *s);
! static inline void parse_scalar(JsonLexContext *lex, JsonSemAction sem);
! static void parse_object_field(JsonLexContext *lex, JsonSemAction sem);
! static void parse_object(JsonLexContext *lex, JsonSemAction sem);
! static void parse_array_element(JsonLexContext *lex, JsonSemAction sem);
! static void parse_array(JsonLexContext *lex, JsonSemAction sem);
! static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex);
  static void report_invalid_token(JsonLexContext *lex);
! static int	report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
  static void composite_to_json(Datum composite, StringInfo result,
! 				  bool use_line_feeds);
  static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
  				  TYPCATEGORY tcategory, Oid typoutputfunc,
  				  bool use_line_feeds);
  static void array_to_json_internal(Datum array, StringInfo result,
! 					   bool use_line_feeds);
! 
! /* the null action object used for pure validation */
! static jsonSemAction nullSemAction =
! {
! 	NULL, NULL, NULL, NULL, NULL,
! 	NULL, NULL, NULL, NULL, NULL
! };
! static JsonSemAction NullSemAction = &nullSemAction;
! 
! /* Recursive Descent parser support routines */
! 
! /*
!  * lex_peek
!  *
!  * what is the current look_ahead token?
! */
! static inline JsonTokenType
! lex_peek(JsonLexContext *lex)
! {
! 	return lex->token_type;
! }
! 
! /*
!  * lex_accept
!  *
!  * accept the look_ahead token and move the lexer to the next token if the
!  * look_ahead token matches the token parameter. In that case, and if required,
!  * also hand back the de-escaped lexeme.
!  *
!  * returns true if the token matched, false otherwise.
!  */
! static inline bool
! lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme)
! {
! 	if (lex->token_type == token)
! 	{
! 		if (lexeme != NULL)
! 		{
! 			if (lex->token_type == JSON_TOKEN_STRING)
! 			{
! 				if (lex->strval != NULL)
! 					*lexeme = pstrdup(lex->strval->data);
! 			}
! 			else
! 			{
! 				int			len = (lex->token_terminator - lex->token_start);
! 				char	   *tokstr = palloc(len + 1);
! 
! 				memcpy(tokstr, lex->token_start, len);
! 				tokstr[len] = '\0';
! 				*lexeme = tokstr;
! 			}
! 		}
! 		json_lex(lex);
! 		return true;
! 	}
! 	return false;
! }
! 
! /*
!  * lex_accept
!  *
!  * move the lexer to the next token if the current look_ahead token matches
!  * the parameter token. Otherwise, report an error.
!  */
! static inline void
! lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
! {
! 	if (!lex_accept(lex, token, NULL))
! 		report_parse_error(ctx, lex);;
! }
  
  /* fake type category for JSON so we can distinguish it in datum_to_json */
  #define TYPCATEGORY_JSON 'j'
***************
*** 100,118 **** static void array_to_json_internal(Datum array, StringInfo result,
  	 (c) == '_' || \
  	 IS_HIGHBIT_SET(c))
  
- 
  /*
   * Input.
   */
  Datum
  json_in(PG_FUNCTION_ARGS)
  {
! 	char	   *text = PG_GETARG_CSTRING(0);
  
! 	json_validate_cstring(text);
  
  	/* Internal representation is the same as text, for now */
! 	PG_RETURN_TEXT_P(cstring_to_text(text));
  }
  
  /*
--- 149,170 ----
  	 (c) == '_' || \
  	 IS_HIGHBIT_SET(c))
  
  /*
   * Input.
   */
  Datum
  json_in(PG_FUNCTION_ARGS)
  {
! 	char	   *json = PG_GETARG_CSTRING(0);
! 	text	   *result = cstring_to_text(json);
! 	JsonLexContext *lex;
  
! 	/* validate it */
! 	lex = makeJsonLexContext(result, false);
! 	pg_parse_json(lex, NullSemAction);
  
  	/* Internal representation is the same as text, for now */
! 	PG_RETURN_TEXT_P(result);
  }
  
  /*
***************
*** 151,443 **** json_recv(PG_FUNCTION_ARGS)
  	text	   *result;
  	char	   *str;
  	int			nbytes;
  
  	str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
  
! 	/*
! 	 * We need a null-terminated string to pass to json_validate_cstring().
! 	 * Rather than make a separate copy, make the temporary result one byte
! 	 * bigger than it needs to be.
! 	 */
! 	result = palloc(nbytes + 1 + VARHDRSZ);
  	SET_VARSIZE(result, nbytes + VARHDRSZ);
  	memcpy(VARDATA(result), str, nbytes);
- 	str = VARDATA(result);
- 	str[nbytes] = '\0';
  
  	/* Validate it. */
! 	json_validate_cstring(str);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /*
!  * Check whether supplied input is valid JSON.
   */
  static void
! json_validate_cstring(char *input)
  {
! 	JsonLexContext lex;
! 	JsonParseStack *stack,
! 			   *stacktop;
! 	int			stacksize;
! 
! 	/* Set up lexing context. */
! 	lex.input = input;
! 	lex.token_terminator = lex.input;
! 
! 	/* Set up parse stack. */
! 	stacksize = 32;
! 	stacktop = (JsonParseStack *) palloc(sizeof(JsonParseStack) * stacksize);
! 	stack = stacktop;
! 	stack->state = JSON_PARSE_VALUE;
! 
! 	/* Main parsing loop. */
! 	for (;;)
  	{
! 		JsonStackOp op;
  
! 		/* Fetch next token. */
! 		json_lex(&lex);
  
! 		/* Check for unexpected end of input. */
! 		if (lex.token_start == NULL)
! 			report_parse_error(stack, &lex);
  
! redo:
! 		/* Figure out what to do with this token. */
! 		op = JSON_STACKOP_NONE;
! 		switch (stack->state)
! 		{
! 			case JSON_PARSE_VALUE:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == '[')
! 					stack->state = JSON_PARSE_ARRAY_START;
! 				else if (lex.token_start[0] == '{')
! 					stack->state = JSON_PARSE_OBJECT_START;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_ARRAY_START:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					stack->state = JSON_PARSE_ARRAY_NEXT;
! 				else if (lex.token_start[0] == ']')
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == '[' ||
! 						 lex.token_start[0] == '{')
! 				{
! 					stack->state = JSON_PARSE_ARRAY_NEXT;
! 					op = JSON_STACKOP_PUSH_WITH_PUSHBACK;
! 				}
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_ARRAY_NEXT:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					report_parse_error(stack, &lex);
! 				else if (lex.token_start[0] == ']')
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == ',')
! 					op = JSON_STACKOP_PUSH;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_START:
! 				if (lex.token_type == JSON_VALUE_STRING)
! 					stack->state = JSON_PARSE_OBJECT_LABEL;
! 				else if (lex.token_type == JSON_VALUE_INVALID &&
! 						 lex.token_start[0] == '}')
! 					op = JSON_STACKOP_POP;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_LABEL:
! 				if (lex.token_type == JSON_VALUE_INVALID &&
! 					lex.token_start[0] == ':')
! 				{
! 					stack->state = JSON_PARSE_OBJECT_NEXT;
! 					op = JSON_STACKOP_PUSH;
! 				}
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_NEXT:
! 				if (lex.token_type != JSON_VALUE_INVALID)
! 					report_parse_error(stack, &lex);
! 				else if (lex.token_start[0] == '}')
! 					op = JSON_STACKOP_POP;
! 				else if (lex.token_start[0] == ',')
! 					stack->state = JSON_PARSE_OBJECT_COMMA;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			case JSON_PARSE_OBJECT_COMMA:
! 				if (lex.token_type == JSON_VALUE_STRING)
! 					stack->state = JSON_PARSE_OBJECT_LABEL;
! 				else
! 					report_parse_error(stack, &lex);
! 				break;
! 			default:
! 				elog(ERROR, "unexpected json parse state: %d",
! 					 (int) stack->state);
! 		}
  
! 		/* Push or pop the state stack, if needed. */
! 		switch (op)
! 		{
! 			case JSON_STACKOP_PUSH:
! 			case JSON_STACKOP_PUSH_WITH_PUSHBACK:
! 				stack++;
! 				if (stack >= &stacktop[stacksize])
! 				{
! 					/* Need to enlarge the stack. */
! 					int			stackoffset = stack - stacktop;
! 
! 					stacksize += 32;
! 					stacktop = (JsonParseStack *)
! 						repalloc(stacktop,
! 								 sizeof(JsonParseStack) * stacksize);
! 					stack = stacktop + stackoffset;
! 				}
! 				stack->state = JSON_PARSE_VALUE;
! 				if (op == JSON_STACKOP_PUSH_WITH_PUSHBACK)
! 					goto redo;
! 				break;
! 			case JSON_STACKOP_POP:
! 				if (stack == stacktop)
! 				{
! 					/* Expect end of input. */
! 					json_lex(&lex);
! 					if (lex.token_start != NULL)
! 						report_parse_error(NULL, &lex);
! 					return;
! 				}
! 				stack--;
! 				break;
! 			case JSON_STACKOP_NONE:
! 				/* nothing to do */
! 				break;
! 		}
  	}
  }
  
  /*
   * Lex one token from the input stream.
   */
! static void
  json_lex(JsonLexContext *lex)
  {
  	char	   *s;
  
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
! 	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
! 		s++;
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (strchr("{}[],:", s[0]) != NULL)
  	{
! 		/* strchr() is willing to match a zero byte, so test for that. */
! 		if (s[0] == '\0')
! 		{
! 			/* End of string. */
! 			lex->token_start = NULL;
! 			lex->token_terminator = s;
! 		}
! 		else
! 		{
! 			/* Single-character token, some kind of punctuation mark. */
! 			lex->token_terminator = s + 1;
! 		}
! 		lex->token_type = JSON_VALUE_INVALID;
! 	}
! 	else if (*s == '"')
! 	{
! 		/* String. */
! 		json_lex_string(lex);
! 		lex->token_type = JSON_VALUE_STRING;
! 	}
! 	else if (*s == '-')
! 	{
! 		/* Negative number. */
! 		json_lex_number(lex, s + 1);
! 		lex->token_type = JSON_VALUE_NUMBER;
! 	}
! 	else if (*s >= '0' && *s <= '9')
! 	{
! 		/* Positive number. */
! 		json_lex_number(lex, s);
! 		lex->token_type = JSON_VALUE_NUMBER;
  	}
  	else
! 	{
! 		char	   *p;
  
! 		/*
! 		 * We're not dealing with a string, number, legal punctuation mark, or
! 		 * end of string.  The only legal tokens we might find here are true,
! 		 * false, and null, but for error reporting purposes we scan until we
! 		 * see a non-alphanumeric character.  That way, we can report the
! 		 * whole word as an unexpected token, rather than just some
! 		 * unintuitive prefix thereof.
! 		 */
! 		for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
! 			/* skip */ ;
  
! 		if (p == s)
! 		{
! 			/*
! 			 * We got some sort of unexpected punctuation or an otherwise
! 			 * unexpected character, so just complain about that one
! 			 * character.  (It can't be multibyte because the above loop
! 			 * will advance over any multibyte characters.)
! 			 */
! 			lex->token_terminator = s + 1;
! 			report_invalid_token(lex);
! 		}
  
! 		/*
! 		 * We've got a real alphanumeric token here.  If it happens to be
! 		 * true, false, or null, all is well.  If not, error out.
! 		 */
! 		lex->token_terminator = p;
! 		if (p - s == 4)
! 		{
! 			if (memcmp(s, "true", 4) == 0)
! 				lex->token_type = JSON_VALUE_TRUE;
! 			else if (memcmp(s, "null", 4) == 0)
! 				lex->token_type = JSON_VALUE_NULL;
! 			else
! 				report_invalid_token(lex);
! 		}
! 		else if (p - s == 5 && memcmp(s, "false", 5) == 0)
! 			lex->token_type = JSON_VALUE_FALSE;
! 		else
! 			report_invalid_token(lex);
! 	}
  }
  
  /*
   * The next token in the input stream is known to be a string; lex it.
   */
! static void
  json_lex_string(JsonLexContext *lex)
  {
  	char	   *s;
  
! 	for (s = lex->token_start + 1; *s != '"'; s++)
  	{
! 		/* Per RFC4627, these characters MUST be escaped. */
! 		if ((unsigned char) *s < 32)
  		{
! 			/* A NUL byte marks the (premature) end of the string. */
! 			if (*s == '\0')
! 			{
! 				lex->token_terminator = s;
! 				report_invalid_token(lex);
! 			}
  			/* Since *s isn't printable, exclude it from the context string */
  			lex->token_terminator = s;
  			ereport(ERROR,
--- 203,660 ----
  	text	   *result;
  	char	   *str;
  	int			nbytes;
+ 	JsonLexContext *lex;
  
  	str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
  
! 	result = palloc(nbytes + VARHDRSZ);
  	SET_VARSIZE(result, nbytes + VARHDRSZ);
  	memcpy(VARDATA(result), str, nbytes);
  
  	/* Validate it. */
! 	lex = makeJsonLexContext(result, false);
! 	pg_parse_json(lex, NullSemAction);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /*
!  * makeJsonLexContext
!  *
!  * lex constructor, with or without StringInfo object
!  * for de-escaped lexemes.
!  *
!  * Without is better as it makes the processing faster, so only make one
!  * if really required.
!  */
! JsonLexContext *
! makeJsonLexContext(text *json, bool need_escapes)
! {
! 	JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
! 
! 	lex->input = lex->token_terminator = lex->line_start = VARDATA(json);
! 	lex->line_number = 1;
! 	lex->input_length = VARSIZE(json) - VARHDRSZ;
! 	if (need_escapes)
! 		lex->strval = makeStringInfo();
! 	return lex;
! }
! 
! /*
!  * pg_parse_json
!  *
!  * Publicly visible entry point for the JSON parser.
!  *
!  * lex is a lexing context, set up for the json to be processed by calling
!  * makeJsonLexContext(). sem is a strucure of function pointers to semantic
!  * action routines to be called at appropriate spots during parsing, and a
!  * pointer to a state object to be passed to those routines.
   */
+ void
+ pg_parse_json(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	JsonTokenType tok;
+ 
+ 	/* get the initial token */
+ 	json_lex(lex);
+ 
+ 	tok = lex_peek(lex);
+ 
+ 	/* parse by recursive descent */
+ 	switch (tok)
+ 	{
+ 		case JSON_TOKEN_OBJECT_START:
+ 			parse_object(lex, sem);
+ 			break;
+ 		case JSON_TOKEN_ARRAY_START:
+ 			parse_array(lex, sem);
+ 			break;
+ 		default:
+ 			parse_scalar(lex, sem);		/* json can be a bare scalar */
+ 	}
+ 
+ 	lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
+ 
+ }
+ 
+ /*
+  *	Recursive Descent parse routines. There is one for each structural
+  *	element in a json document:
+  *	  - scalar (string, number, true, false, null)
+  *	  - array  ( [ ] )
+  *	  - array element
+  *	  - object ( { } )
+  *	  - object field
+  */
+ static inline void
+ parse_scalar(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	char	   *val = NULL;
+ 	json_scalar_action sfunc = sem->scalar;
+ 	char	  **valaddr;
+ 	JsonTokenType tok = lex_peek(lex);
+ 
+ 	valaddr = sfunc == NULL ? NULL : &val;
+ 
+ 	/* a scalar must be a string, a number, true, false, or null */
+ 	switch (tok)
+ 	{
+ 		case JSON_TOKEN_TRUE:
+ 			lex_accept(lex, JSON_TOKEN_TRUE, valaddr);
+ 			break;
+ 		case JSON_TOKEN_FALSE:
+ 			lex_accept(lex, JSON_TOKEN_FALSE, valaddr);
+ 			break;
+ 		case JSON_TOKEN_NULL:
+ 			lex_accept(lex, JSON_TOKEN_NULL, valaddr);
+ 			break;
+ 		case JSON_TOKEN_NUMBER:
+ 			lex_accept(lex, JSON_TOKEN_NUMBER, valaddr);
+ 			break;
+ 		case JSON_TOKEN_STRING:
+ 			lex_accept(lex, JSON_TOKEN_STRING, valaddr);
+ 			break;
+ 		default:
+ 			report_parse_error(JSON_PARSE_VALUE, lex);
+ 	}
+ 
+ 	if (sfunc != NULL)
+ 		(*sfunc) (sem->semstate, val, tok);
+ }
+ 
  static void
! parse_object_field(JsonLexContext *lex, JsonSemAction sem)
  {
! 	/*
! 	 * an object field is "fieldname" : value where value can be a scalar,
! 	 * object or array
! 	 */
! 
! 	char	   *fname = NULL;	/* keep compiler quiet */
! 	json_ofield_action ostart = sem->object_field_start;
! 	json_ofield_action oend = sem->object_field_end;
! 	bool		isnull;
! 	char	  **fnameaddr = NULL;
! 	JsonTokenType tok;
! 
! 	if (ostart != NULL || oend != NULL)
! 		fnameaddr = &fname;
! 
! 	if (!lex_accept(lex, JSON_TOKEN_STRING, fnameaddr))
! 		report_parse_error(JSON_PARSE_STRING, lex);
! 
! 	lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
! 
! 	tok = lex_peek(lex);
! 	isnull = tok == JSON_TOKEN_NULL;
! 
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate, fname, isnull);
! 
! 	switch (tok)
  	{
! 		case JSON_TOKEN_OBJECT_START:
! 			parse_object(lex, sem);
! 			break;
! 		case JSON_TOKEN_ARRAY_START:
! 			parse_array(lex, sem);
! 			break;
! 		default:
! 			parse_scalar(lex, sem);
! 	}
  
! 	if (oend != NULL)
! 		(*oend) (sem->semstate, fname, isnull);
  
! 	if (fname != NULL)
! 		pfree(fname);
! }
  
! static void
! parse_object(JsonLexContext *lex, JsonSemAction sem)
! {
! 	/*
! 	 * an object is a possibly empty sequence of object fields, separated by
! 	 * commas and surrounde by curly braces.
! 	 */
! 	json_struct_action ostart = sem->object_start;
! 	json_struct_action oend = sem->object_end;
! 	JsonTokenType tok;
  
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate);
! 
! 	/*
! 	 * Data inside an object at at a higher nesting level than the object
! 	 * itself. Note that we increment this after we call the semantic routine
! 	 * for the object start and restore it before we call the routine for the
! 	 * object end.
! 	 */
! 	lex->lex_level++;
! 
! 	/* we know this will succeeed, just clearing the token */
! 	lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
! 
! 	tok = lex_peek(lex);
! 	switch (tok)
! 	{
! 		case JSON_TOKEN_STRING:
! 			parse_object_field(lex, sem);
! 			while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
! 				parse_object_field(lex, sem);
! 			break;
! 		case JSON_TOKEN_OBJECT_END:
! 			break;
! 		default:
! 			/* case of an invalid initial token inside the object */
! 			report_parse_error(JSON_PARSE_OBJECT_START, lex);
! 	}
! 
! 	lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END);
! 
! 	lex->lex_level--;
! 
! 	if (oend != NULL)
! 		(*oend) (sem->semstate);
! }
! 
! static void
! parse_array_element(JsonLexContext *lex, JsonSemAction sem)
! {
! 	json_aelem_action astart = sem->array_element_start;
! 	json_aelem_action aend = sem->array_element_end;
! 	JsonTokenType tok = lex_peek(lex);
! 
! 	bool		isnull;
! 
! 	isnull = tok == JSON_TOKEN_NULL;
! 
! 	if (astart != NULL)
! 		(*astart) (sem->semstate, isnull);
! 
! 	/* an array element is any object, array or scalar */
! 	switch (tok)
! 	{
! 		case JSON_TOKEN_OBJECT_START:
! 			parse_object(lex, sem);
! 			break;
! 		case JSON_TOKEN_ARRAY_START:
! 			parse_array(lex, sem);
! 			break;
! 		default:
! 			parse_scalar(lex, sem);
  	}
+ 
+ 	if (aend != NULL)
+ 		(*aend) (sem->semstate, isnull);
+ }
+ 
+ static void
+ parse_array(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	/*
+ 	 * an array is a possibly empty sequence of array elements, separated by
+ 	 * commas and surrounded by square brackets.
+ 	 */
+ 	json_struct_action astart = sem->array_start;
+ 	json_struct_action aend = sem->array_end;
+ 
+ 	if (astart != NULL)
+ 		(*astart) (sem->semstate);
+ 
+ 	/*
+ 	 * Data inside an array at at a higher nesting level than the array
+ 	 * itself. Note that we increment this after we call the semantic routine
+ 	 * for the array start and restore it before we call the routine for the
+ 	 * array end.
+ 	 */
+ 	lex->lex_level++;
+ 
+ 	lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START);
+ 	if (lex_peek(lex) != JSON_TOKEN_ARRAY_END)
+ 	{
+ 
+ 		parse_array_element(lex, sem);
+ 
+ 		while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
+ 			parse_array_element(lex, sem);
+ 	}
+ 
+ 	lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END);
+ 
+ 	lex->lex_level--;
+ 
+ 	if (aend != NULL)
+ 		(*aend) (sem->semstate);
  }
  
  /*
   * Lex one token from the input stream.
   */
! static inline void
  json_lex(JsonLexContext *lex)
  {
  	char	   *s;
+ 	int			len;
  
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
! 	len = s - lex->input;
! 	while (len < lex->input_length &&
! 		   (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
! 	{
! 		if (*s == '\n')
! 			++lex->line_number;
! 		++s;
! 		++len;
! 	}
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (len >= lex->input_length)
  	{
! 		lex->token_start = NULL;
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s;
! 		lex->token_type = JSON_TOKEN_END;
  	}
  	else
! 		switch (*s)
! 		{
! 				/* Single-character token, some kind of punctuation mark. */
! 			case '{':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_OBJECT_START;
! 				break;
! 			case '}':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_OBJECT_END;
! 				break;
! 			case '[':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_ARRAY_START;
! 				break;
! 			case ']':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_ARRAY_END;
! 				break;
! 			case ',':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_COMMA;
! 				break;
! 			case ':':
! 				lex->prev_token_terminator = lex->token_terminator;
! 				lex->token_terminator = s + 1;
! 				lex->token_type = JSON_TOKEN_COLON;
! 				break;
  
! 			case '"':
! 				/* string */
! 				json_lex_string(lex);
! 				lex->token_type = JSON_TOKEN_STRING;
! 				break;
! 			case '-':
! 				/* Negative number. */
! 				json_lex_number(lex, s + 1);
! 				lex->token_type = JSON_TOKEN_NUMBER;
! 				break;
! 			case '0':
! 			case '1':
! 			case '2':
! 			case '3':
! 			case '4':
! 			case '5':
! 			case '6':
! 			case '7':
! 			case '8':
! 			case '9':
! 				/* Positive number. */
! 				json_lex_number(lex, s);
! 				lex->token_type = JSON_TOKEN_NUMBER;
! 				break;
! 			default:
! 				{
! 					char	   *p;
! 
! 					/*
! 					 * We're not dealing with a string, number, legal
! 					 * punctuation mark, or end of string.	The only legal
! 					 * tokens we might find here are true, false, and null,
! 					 * but for error reporting purposes we scan until we see a
! 					 * non-alphanumeric character.	That way, we can report
! 					 * the whole word as an unexpected token, rather than just
! 					 * some unintuitive prefix thereof.
! 					 */
! 					for (p = s; JSON_ALPHANUMERIC_CHAR(*p) && p - s < lex->input_length - len; p++)
! 						 /* skip */ ;
! 
! 					/*
! 					 * We got some sort of unexpected punctuation or an
! 					 * otherwise unexpected character, so just complain about
! 					 * that one character.
! 					 */
! 					if (p == s)
! 					{
! 						lex->prev_token_terminator = lex->token_terminator;
! 						lex->token_terminator = s + 1;
! 						report_invalid_token(lex);
! 					}
  
! 					/*
! 					 * We've got a real alphanumeric token here.  If it
! 					 * happens to be true, false, or null, all is well.  If
! 					 * not, error out.
! 					 */
! 					lex->prev_token_terminator = lex->token_terminator;
! 					lex->token_terminator = p;
! 					if (p - s == 4)
! 					{
! 						if (memcmp(s, "true", 4) == 0)
! 							lex->token_type = JSON_TOKEN_TRUE;
! 						else if (memcmp(s, "null", 4) == 0)
! 							lex->token_type = JSON_TOKEN_NULL;
! 						else
! 							report_invalid_token(lex);
! 					}
! 					else if (p - s == 5 && memcmp(s, "false", 5) == 0)
! 						lex->token_type = JSON_TOKEN_FALSE;
! 					else
! 						report_invalid_token(lex);
  
! 				}
! 		}						/* end of switch */
  }
  
  /*
   * The next token in the input stream is known to be a string; lex it.
   */
! static inline void
  json_lex_string(JsonLexContext *lex)
  {
  	char	   *s;
+ 	int			len;
  
! 	if (lex->strval != NULL)
! 		resetStringInfo(lex->strval);
! 
! 	len = lex->token_start - lex->input;
! 	len++;
! 	for (s = lex->token_start + 1; *s != '"'; s++, len++)
  	{
! 		/* Premature end of the string. */
! 		if (len >= lex->input_length)
  		{
! 			lex->token_terminator = s;
! 			report_invalid_token(lex);
! 		}
! 		else if ((unsigned char) *s < 32)
! 		{
! 			/* Per RFC4627, these characters MUST be escaped. */
  			/* Since *s isn't printable, exclude it from the context string */
  			lex->token_terminator = s;
  			ereport(ERROR,
***************
*** 451,457 **** json_lex_string(JsonLexContext *lex)
  		{
  			/* OK, we have an escape character. */
  			s++;
! 			if (*s == '\0')
  			{
  				lex->token_terminator = s;
  				report_invalid_token(lex);
--- 668,675 ----
  		{
  			/* OK, we have an escape character. */
  			s++;
! 			len++;
! 			if (len >= lex->input_length)
  			{
  				lex->token_terminator = s;
  				report_invalid_token(lex);
***************
*** 464,470 **** json_lex_string(JsonLexContext *lex)
  				for (i = 1; i <= 4; i++)
  				{
  					s++;
! 					if (*s == '\0')
  					{
  						lex->token_terminator = s;
  						report_invalid_token(lex);
--- 682,689 ----
  				for (i = 1; i <= 4; i++)
  				{
  					s++;
! 					len++;
! 					if (len >= lex->input_length)
  					{
  						lex->token_terminator = s;
  						report_invalid_token(lex);
***************
*** 485,494 **** json_lex_string(JsonLexContext *lex)
  								 report_json_context(lex)));
  					}
  				}
  			}
  			else if (strchr("\"\\/bfnrt", *s) == NULL)
  			{
! 				/* Not a valid string escape, so error out. */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
--- 704,769 ----
  								 report_json_context(lex)));
  					}
  				}
+ 				if (lex->strval != NULL)
+ 				{
+ 					char		utf8str[5];
+ 					int			utf8len;
+ 					char	   *converted;
+ 
+ 					unicode_to_utf8(ch, (unsigned char *) utf8str);
+ 					utf8len = pg_utf_mblen((unsigned char *) utf8str);
+ 					utf8str[utf8len] = '\0';
+ 					converted = pg_any_to_server(utf8str, 1, PG_UTF8);
+ 					appendStringInfoString(lex->strval, converted);
+ 					if (converted != utf8str)
+ 						pfree(converted);
+ 
+ 				}
+ 			}
+ 			else if (lex->strval != NULL)
+ 			{
+ 				switch (*s)
+ 				{
+ 					case '"':
+ 					case '\\':
+ 					case '/':
+ 						appendStringInfoChar(lex->strval, *s);
+ 						break;
+ 					case 'b':
+ 						appendStringInfoChar(lex->strval, '\b');
+ 						break;
+ 					case 'f':
+ 						appendStringInfoChar(lex->strval, '\f');
+ 						break;
+ 					case 'n':
+ 						appendStringInfoChar(lex->strval, '\n');
+ 						break;
+ 					case 'r':
+ 						appendStringInfoChar(lex->strval, '\r');
+ 						break;
+ 					case 't':
+ 						appendStringInfoChar(lex->strval, '\t');
+ 						break;
+ 					default:
+ 						/* Not a valid string escape, so error out. */
+ 						lex->token_terminator = s + pg_mblen(s);
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 								 errmsg("invalid input syntax for type json"),
+ 							errdetail("Escape sequence \"\\%s\" is invalid.",
+ 									  extract_mb_char(s)),
+ 								 report_json_context(lex)));
+ 				}
  			}
  			else if (strchr("\"\\/bfnrt", *s) == NULL)
  			{
! 				/*
! 				 * Simpler processing if we're not bothered about de-escaping
! 				 *
! 				 * It's very tempting to remove the strchr() call here and
! 				 * replace it with a switch statement, but testing so far has
! 				 * shown it's not a performance win.
! 				 */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 497,506 **** json_lex_string(JsonLexContext *lex)
--- 772,788 ----
  								   extract_mb_char(s)),
  						 report_json_context(lex)));
  			}
+ 
+ 		}
+ 		else if (lex->strval != NULL)
+ 		{
+ 			appendStringInfoChar(lex->strval, *s);
  		}
+ 
  	}
  
  	/* Hooray, we found the end of the string! */
+ 	lex->prev_token_terminator = lex->token_terminator;
  	lex->token_terminator = s + 1;
  }
  
***************
*** 530,596 **** json_lex_string(JsonLexContext *lex)
   *
   *-------------------------------------------------------------------------
   */
! static void
  json_lex_number(JsonLexContext *lex, char *s)
  {
  	bool		error = false;
  	char	   *p;
  
  	/* Part (1): leading sign indicator. */
  	/* Caller already did this for us; so do nothing. */
  
  	/* Part (2): parse main digit string. */
  	if (*s == '0')
  		s++;
  	else if (*s >= '1' && *s <= '9')
  	{
  		do
  		{
  			s++;
! 		} while (*s >= '0' && *s <= '9');
  	}
  	else
  		error = true;
  
  	/* Part (3): parse optional decimal portion. */
! 	if (*s == '.')
  	{
  		s++;
! 		if (*s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 			} while (*s >= '0' && *s <= '9');
  		}
  	}
  
  	/* Part (4): parse optional exponent. */
! 	if (*s == 'e' || *s == 'E')
  	{
  		s++;
! 		if (*s == '+' || *s == '-')
  			s++;
! 		if (*s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 			} while (*s >= '0' && *s <= '9');
  		}
  	}
  
  	/*
! 	 * Check for trailing garbage.  As in json_lex(), any alphanumeric stuff
  	 * here should be considered part of the token for error-reporting
  	 * purposes.
  	 */
! 	for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
  		error = true;
  	lex->token_terminator = p;
  	if (error)
  		report_invalid_token(lex);
--- 812,892 ----
   *
   *-------------------------------------------------------------------------
   */
! static inline void
  json_lex_number(JsonLexContext *lex, char *s)
  {
  	bool		error = false;
  	char	   *p;
+ 	int			len;
  
+ 	len = s - lex->input;
  	/* Part (1): leading sign indicator. */
  	/* Caller already did this for us; so do nothing. */
  
  	/* Part (2): parse main digit string. */
  	if (*s == '0')
+ 	{
  		s++;
+ 		len++;
+ 	}
  	else if (*s >= '1' && *s <= '9')
  	{
  		do
  		{
  			s++;
! 			len++;
! 		} while (*s >= '0' && *s <= '9' && len < lex->input_length);
  	}
  	else
  		error = true;
  
  	/* Part (3): parse optional decimal portion. */
! 	if (len < lex->input_length && *s == '.')
  	{
  		s++;
! 		len++;
! 		if (len == lex->input_length || *s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 				len++;
! 			} while (*s >= '0' && *s <= '9' && len < lex->input_length);
  		}
  	}
  
  	/* Part (4): parse optional exponent. */
! 	if (len < lex->input_length && (*s == 'e' || *s == 'E'))
  	{
  		s++;
! 		len++;
! 		if (len < lex->input_length && (*s == '+' || *s == '-'))
! 		{
  			s++;
! 			len++;
! 		}
! 		if (len == lex->input_length || *s < '0' || *s > '9')
  			error = true;
  		else
  		{
  			do
  			{
  				s++;
! 				len++;
! 			} while (len < lex->input_length && *s >= '0' && *s <= '9');
  		}
  	}
  
  	/*
! 	 * Check for trailing garbage.	As in json_lex(), any alphanumeric stuff
  	 * here should be considered part of the token for error-reporting
  	 * purposes.
  	 */
! 	for (p = s; JSON_ALPHANUMERIC_CHAR(*p) && len < lex->input_length; p++, len++)
  		error = true;
+ 	lex->prev_token_terminator = lex->token_terminator;
  	lex->token_terminator = p;
  	if (error)
  		report_invalid_token(lex);
***************
*** 602,614 **** json_lex_number(JsonLexContext *lex, char *s)
   * lex->token_start and lex->token_terminator must identify the current token.
   */
  static void
! report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  {
  	char	   *token;
  	int			toklen;
  
  	/* Handle case where the input ended prematurely. */
! 	if (lex->token_start == NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
--- 898,910 ----
   * lex->token_start and lex->token_terminator must identify the current token.
   */
  static void
! report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
  {
  	char	   *token;
  	int			toklen;
  
  	/* Handle case where the input ended prematurely. */
! 	if (lex->token_start == NULL || lex->token_type == JSON_TOKEN_END)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
***************
*** 622,628 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  	token[toklen] = '\0';
  
  	/* Complain, with the appropriate detail message. */
! 	if (stack == NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
--- 918,924 ----
  	token[toklen] = '\0';
  
  	/* Complain, with the appropriate detail message. */
! 	if (ctx == JSON_PARSE_END)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  				 errmsg("invalid input syntax for type json"),
***************
*** 631,637 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  				 report_json_context(lex)));
  	else
  	{
! 		switch (stack->state)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
--- 927,933 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 937,950 ----
  								   token),
  						 report_json_context(lex)));
  				break;
+ 			case JSON_PARSE_STRING:
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 						 errmsg("invalid input syntax for type json"),
+ 						 errdetail("Expected string, but found \"%s\".",
+ 								   token),
+ 						 report_json_context(lex)));
+ 				break;
  			case JSON_PARSE_ARRAY_START:
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 653,668 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Expected \",\" or \"]\", but found \"%s\".",
! 								   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_START:
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Expected string or \"}\", but found \"%s\".",
! 								   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_LABEL:
--- 957,972 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 					  errdetail("Expected \",\" or \"]\", but found \"%s\".",
! 								token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_START:
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 					 errdetail("Expected string or \"}\", but found \"%s\".",
! 							   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_LABEL:
***************
*** 677,684 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Expected \",\" or \"}\", but found \"%s\".",
! 								   token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_COMMA:
--- 981,988 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
  						 errmsg("invalid input syntax for type json"),
! 					  errdetail("Expected \",\" or \"}\", but found \"%s\".",
! 								token),
  						 report_json_context(lex)));
  				break;
  			case JSON_PARSE_OBJECT_COMMA:
***************
*** 690,697 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
  						 report_json_context(lex)));
  				break;
  			default:
! 				elog(ERROR, "unexpected json parse state: %d",
! 					 (int) stack->state);
  		}
  	}
  }
--- 994,1000 ----
  						 report_json_context(lex)));
  				break;
  			default:
! 				elog(ERROR, "unexpected json parse state: %d", ctx);
  		}
  	}
  }
***************
*** 786,792 **** report_json_context(JsonLexContext *lex)
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (*context_end != '\0' && *context_end != '\n' && *context_end != '\r') ? "..." : "";
  
  	return errcontext("JSON data, line %d: %s%s%s",
  					  line_number, prefix, ctxt, suffix);
--- 1089,1095 ----
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (lex->token_type != JSON_TOKEN_END && context_end - lex->input < lex->input_length && *context_end != '\n' && *context_end != '\r') ? "..." : "";
  
  	return errcontext("JSON data, line %d: %s%s%s",
  					  line_number, prefix, ctxt, suffix);
*** /dev/null
--- b/src/backend/utils/adt/jsonfuncs.c
***************
*** 0 ****
--- 1,2063 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/jsonfuncs.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include <limits.h>
+ 
+ #include "fmgr.h"
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "access/htup_details.h"
+ #include "catalog/pg_type.h"
+ #include "lib/stringinfo.h"
+ #include "mb/pg_wchar.h"
+ #include "utils/array.h"
+ #include "utils/builtins.h"
+ #include "utils/hsearch.h"
+ #include "utils/json.h"
+ #include "utils/jsonapi.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ #include "utils/typcache.h"
+ 
+ /* semantic action functions for json_object_keys */
+ static void okeys_object_field_start(void *state, char *fname, bool isnull);
+ static void okeys_array_start(void *state);
+ static void okeys_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* semantic action functions for json_get* functions */
+ static void get_object_start(void *state);
+ static void get_object_field_start(void *state, char *fname, bool isnull);
+ static void get_object_field_end(void *state, char *fname, bool isnull);
+ static void get_array_start(void *state);
+ static void get_array_element_start(void *state, bool isnull);
+ static void get_array_element_end(void *state, bool isnull);
+ static void get_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* common worker function for json_get* functions */
+ static text *get_worker(text *json, char *field, int elem_index, char **path,
+ 		   int npath, bool normalize_results);
+ 
+ /* semantic action functions for json_array_length */
+ static void alen_object_start(void *state);
+ static void alen_scalar(void *state, char *token, JsonTokenType tokentype);
+ static void alen_array_element_start(void *state, bool isnull);
+ 
+ /* semantic action functions for json_each */
+ static void each_object_field_start(void *state, char *fname, bool isnull);
+ static void each_object_field_end(void *state, char *fname, bool isnull);
+ static void each_array_start(void *state);
+ static void each_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* semantic action functions for json_unnest */
+ static void unnest_object_start(void *state);
+ static void unnest_array_element_start(void *state, bool isnull);
+ static void unnest_array_element_end(void *state, bool isnull);
+ static void unnest_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* turn a json object into a hash table */
+ static HTAB *get_json_object_as_hash(text *json, char *funcname, bool use_json_as_text);
+ 
+ /* semantic action functions for get_json_object_as_hash */
+ static void hash_object_field_start(void *state, char *fname, bool isnull);
+ static void hash_object_field_end(void *state, char *fname, bool isnull);
+ static void hash_array_start(void *state);
+ static void hash_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ /* semantic action functions for populate_recordset */
+ static void populate_recordset_object_field_start(void *state, char *fname, bool isnull);
+ static void populate_recordset_object_field_end(void *state, char *fname, bool isnull);
+ static void populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype);
+ static void populate_recordset_object_start(void *state);
+ static void populate_recordset_object_end(void *state);
+ static void populate_recordset_array_start(void *state);
+ static void populate_recordset_array_element_start(void *state, bool isnull);
+ 
+ /* search type classification for json_get* functions */
+ typedef enum
+ {
+ 	JSON_SEARCH_OBJECT = 1,
+ 	JSON_SEARCH_ARRAY,
+ 	JSON_SEARCH_PATH
+ }	JsonSearch;
+ 
+ /* state for json_object_keys */
+ typedef struct okeysState
+ {
+ 	JsonLexContext *lex;
+ 	char	  **result;
+ 	int			result_size;
+ 	int			result_count;
+ 	int			sent_count;
+ }	okeysState, *OkeysState;
+ 
+ /* state for json_get* functions */
+ typedef struct getState
+ {
+ 	JsonLexContext *lex;
+ 	JsonSearch	search_type;
+ 	int			search_index;
+ 	int			array_index;
+ 	char	   *search_term;
+ 	char	   *result_start;
+ 	text	   *tresult;
+ 	bool		result_is_null;
+ 	bool		normalize_results;
+ 	bool		next_scalar;
+ 	char	  **path;
+ 	int			npath;
+ 	char	  **current_path;
+ 	bool	   *pathok;
+ 	int		   *array_level_index;
+ 	int		   *path_level_index;
+ }	getState, *GetState;
+ 
+ /* state for json_array_length */
+ typedef struct alenState
+ {
+ 	JsonLexContext *lex;
+ 	int			count;
+ }	alenState, *AlenState;
+ 
+ /* state for json_each */
+ typedef struct eachState
+ {
+ 	JsonLexContext *lex;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	MemoryContext tmp_cxt;
+ 	char	   *result_start;
+ 	bool		normalize_results;
+ 	bool		next_scalar;
+ 	char	   *normalized_scalar;
+ }	eachState, *EachState;
+ 
+ /* state for json_unnest */
+ typedef struct unnestState
+ {
+ 	JsonLexContext *lex;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	MemoryContext tmp_cxt;
+ 	char	   *result_start;
+ }	unnestState, *UnnestState;
+ 
+ /* state for get_json_object_as_hash */
+ typedef struct jhashState
+ {
+ 	JsonLexContext *lex;
+ 	HTAB	   *hash;
+ 	char	   *saved_scalar;
+ 	char	   *save_json_start;
+ 	bool		use_json_as_text;
+ 	char	   *function_name;
+ }	jhashState, *JHashState;
+ 
+ /* used to build the hashtable */
+ typedef struct jsonHashEntry
+ {
+ 	char		fname[NAMEDATALEN];
+ 	char	   *val;
+ 	char	   *json;
+ 	bool		isnull;
+ }	jsonHashEntry, *JsonHashEntry;
+ 
+ /* these two are stolen from hstore / record_out, used in populate_record* */
+ typedef struct ColumnIOData
+ {
+ 	Oid			column_type;
+ 	Oid			typiofunc;
+ 	Oid			typioparam;
+ 	FmgrInfo	proc;
+ } ColumnIOData;
+ 
+ typedef struct RecordIOData
+ {
+ 	Oid			record_type;
+ 	int32		record_typmod;
+ 	int			ncolumns;
+ 	ColumnIOData columns[1];	/* VARIABLE LENGTH ARRAY */
+ } RecordIOData;
+ 
+ /* state for populate_recordset */
+ typedef struct populateRecordsetState
+ {
+ 	JsonLexContext *lex;
+ 	HTAB	   *json_hash;
+ 	char	   *saved_scalar;
+ 	char	   *save_json_start;
+ 	bool		use_json_as_text;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	HeapTupleHeader rec;
+ 	RecordIOData *my_extra;
+ 	MemoryContext fn_mcxt;		/* used to stash IO funcs */
+ }	populateRecordsetState, *PopulateRecordsetState;
+ 
+ /*
+  * SQL function json_object-keys
+  *
+  * Returns the set of keys for the object argument.
+  *
+  * This SRF operates in value-per-call mode. It processes the
+  * object during the first call, and the keys are simply stashed
+  * in an array, whise size is expanded as necessary. This is probably
+  * safe enough for a list of keys of a single object, since they are
+  * limited in size to NAMEDATALEN and the number of keys is unlikely to
+  * be so huge that it has major memory implications.
+  */
+ 
+ Datum
+ json_object_keys(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 	OkeysState	state;
+ 	int			i;
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		text	   *json = PG_GETARG_TEXT_P(0);
+ 		JsonLexContext *lex = makeJsonLexContext(json, true);
+ 		JsonSemAction sem;
+ 
+ 		MemoryContext oldcontext;
+ 
+ 		funcctx = SRF_FIRSTCALL_INIT();
+ 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ 
+ 		state = palloc(sizeof(okeysState));
+ 		sem = palloc0(sizeof(jsonSemAction));
+ 
+ 		state->lex = lex;
+ 		state->result_size = 256;
+ 		state->result_count = 0;
+ 		state->sent_count = 0;
+ 		state->result = palloc(256 * sizeof(char *));
+ 
+ 		sem->semstate = (void *) state;
+ 		sem->array_start = okeys_array_start;
+ 		sem->scalar = okeys_scalar;
+ 		sem->object_field_start = okeys_object_field_start;
+ 		/* remainder are all NULL, courtesy of palloc0 above */
+ 
+ 		pg_parse_json(lex, sem);
+ 		/* keys are now in state->result */
+ 
+ 		pfree(lex->strval->data);
+ 		pfree(lex->strval);
+ 		pfree(lex);
+ 		pfree(sem);
+ 
+ 		MemoryContextSwitchTo(oldcontext);
+ 		funcctx->user_fctx = (void *) state;
+ 
+ 	}
+ 
+ 	funcctx = SRF_PERCALL_SETUP();
+ 	state = (OkeysState) funcctx->user_fctx;
+ 
+ 	if (state->sent_count < state->result_count)
+ 	{
+ 		char	   *nxt = state->result[state->sent_count++];
+ 
+ 		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt));
+ 	}
+ 
+ 	/* cleanup to reduce or eliminate memory leaks */
+ 	for (i = 0; i < state->result_count; i++)
+ 		pfree(state->result[i]);
+ 	pfree(state->result);
+ 	pfree(state);
+ 
+ 	SRF_RETURN_DONE(funcctx);
+ }
+ 
+ static void
+ okeys_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	/* only collecting keys for the top level object */
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 
+ 	/* enlarge result array if necessary */
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 
+ 	/* save a copy of the field name */
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	/* top level must be a json object */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_object_keys on an array")));
+ }
+ 
+ static void
+ okeys_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	/* top level must be a json object */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_object_keys on a scalar")));
+ }
+ 
+ /*
+  * json_get* functions
+  * these all use a common worker, just with some slightly
+  * different setup options.
+  */
+ 
+ 
+ /*
+  * SQL function json_get(json text) -> json
+  *
+  * return json for named field
+  *
+  * also used for json -> text operator
+  */
+ Datum
+ json_get_ofield(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, fnamestr, -1, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_get_as_text(json text) -> text
+  *
+  * return text for named field. If the field is a
+  * string the de-escaped value of the string is delivered.
+  *
+  * also used for json ->> text operator
+  */
+ Datum
+ json_get_ofield_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, fnamestr, -1, NULL, -1, true);
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_get(json, int) -> json
+  *
+  * return json for numbered field
+  *
+  * also used for json -> int operator
+  */
+ Datum
+ json_get_aelem(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, NULL, element, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ /*
+  * SQL function json_get_as_text(json, int) -> text
+  *
+  * return text for numbered field . If the field is a
+  * string the de-escaped value of the string is delivered.
+  *
+  * also used for json ->> int operator
+  */
+ Datum
+ json_get_aelem_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	text	   *result;
+ 
+ 	result = get_worker(json, NULL, element, NULL, -1, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ /*
+  * SQL function json_get_path(json, variadic text[]) -> json
+  *
+  * return json for object pointed to by path contained in second
+  * parameter. If the json structure refered to by a path element is
+  * an array, the path element is treated as a (zero based) index. If
+  * it's an object it is treated as a field name. Since SQL arrays are
+  * homogeneous, integer arguments for array indexes must be passed as text.
+  *
+  * There is also a non-variadic function json_get_path_op
+  * that maps to this function and is used in the construction of the
+  * json -> text[] operator.
+  */
+ Datum
+ json_get_path(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	text	   *result;
+ 	Datum	   *pathtext;
+ 	bool	   *pathnulls;
+ 	int			npath;
+ 	char	  **pathstr;
+ 	int			i;
+ 
+ 	if (array_contains_nulls(path))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call %s with null path elements",
+ 						"json_get_path_as_text")));
+ 
+ 
+ 	deconstruct_array(path, TEXTOID, -1, false, 'i',
+ 					  &pathtext, &pathnulls, &npath);
+ 
+ 	pathstr = palloc(npath * sizeof(char *));
+ 
+ 	for (i = 0; i < npath; i++)
+ 	{
+ 		pathstr[i] = TextDatumGetCString(pathtext[i]);
+ 		if (*pathstr[i] == '\0')
+ 			ereport(
+ 					ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot call %s with empty path elements",
+ 							"json_get_path_as_text")));
+ 	}
+ 
+ 	result = get_worker(json, NULL, -1, pathstr, npath, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_get_path_as_text(json, variadic text[]) -> json
+  *
+  * return text for object pointed to by path contained in second
+  * parameter. If the json structure refered to by a path element is
+  * an array, the path element is treated as a (zero based) index. If
+  * it's an object it is treated as a field name. Since SQL arrays are
+  * homogeneous, integer arguments for array indexes must be passed as text.
+  *
+  * If the field is a string the de-escaped value of the string is delivered.
+  *
+  * There is also a non-variadic function json_get_path_as_text_op
+  * that maps to this function and is used in the construction of the
+  * json ->> text[] operator.
+  */
+ Datum
+ json_get_path_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	text	   *result;
+ 	Datum	   *pathtext;
+ 	bool	   *pathnulls;
+ 	int			npath;
+ 	char	  **pathstr;
+ 	int			i;
+ 
+ 	if (array_contains_nulls(path))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call %s with null path elements",
+ 						"json_get_path_as_text")));
+ 
+ 
+ 	deconstruct_array(path, TEXTOID, -1, false, 'i',
+ 					  &pathtext, &pathnulls, &npath);
+ 
+ 	pathstr = palloc(npath * sizeof(char *));
+ 
+ 	for (i = 0; i < npath; i++)
+ 	{
+ 		pathstr[i] = TextDatumGetCString(pathtext[i]);
+ 		if (*pathstr[i] == '\0')
+ 			ereport(
+ 					ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot call %s with empty path elements",
+ 							"json_get_path_as_text")));
+ 	}
+ 
+ 	result = get_worker(json, NULL, -1, pathstr, npath, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ /*
+  * get_worker
+  *
+  * common worker for json_get* functions
+  */
+ static text *
+ get_worker(text *json,
+ 		   char *field,
+ 		   int elem_index,
+ 		   char **path,
+ 		   int npath,
+ 		   bool normalize_results)
+ {
+ 	GetState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 
+ 	/* only allowed to use one of these */
+ 	Assert(elem_index < 0 || (path == NULL && field == NULL));
+ 	Assert(path == NULL || field == NULL);
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex = lex;
+ 	/* is is "_as_text" variant? */
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		/* single text argument */
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else if (path != NULL)
+ 	{
+ 		/* path array argument */
+ 		int			i;
+ 		long int	ind;
+ 		char	   *endptr;
+ 
+ 		state->search_type = JSON_SEARCH_PATH;
+ 		state->path = path;
+ 		state->npath = npath;
+ 		state->current_path = palloc(sizeof(char *) * npath);
+ 		state->pathok = palloc(sizeof(bool) * npath);
+ 		state->pathok[0] = true;
+ 		state->array_level_index = palloc(sizeof(int) * npath);
+ 		state->path_level_index = palloc(sizeof(int) * npath);
+ 
+ 		/*
+ 		 * we have no idea at this stage what structure the document is so
+ 		 * just convert anything in the path that we can to an integer and set
+ 		 * all the other integers to -1 which will never match.
+ 		 */
+ 		for (i = 0; i < npath; i++)
+ 		{
+ 			ind = strtol(path[i], &endptr, 10);
+ 			if (*endptr == '\0' && ind <= INT_MAX && ind >= 0)
+ 				state->path_level_index[i] = (int) ind;
+ 			else
+ 				state->path_level_index[i] = -1;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* single integer argument */
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 
+ 	/*
+ 	 * Not all	variants need all the semantic routines. only set the ones
+ 	 * that ar actually needed for maximum efficiency.
+ 	 */
+ 	sem->object_start = get_object_start;
+ 	sem->array_start = get_array_start;
+ 	sem->scalar = get_scalar;
+ 	if (field != NULL || path != NULL)
+ 	{
+ 		sem->object_field_start = get_object_field_start;
+ 		sem->object_field_end = get_object_field_end;
+ 	}
+ 	if (field == NULL)
+ 	{
+ 		sem->array_element_start = get_array_element_start;
+ 		sem->array_element_end = get_array_element_end;
+ 	}
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	return state->tresult;
+ }
+ 
+ static void
+ get_object_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0 && _state->search_type == JSON_SEARCH_ARRAY)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_get(int) on a non-array")));
+ }
+ 
+ static void
+ get_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_next = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 		strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		/* single field search and we have a match at the right nesting level */
+ 		get_next = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[_state->lex->lex_level - 1] &&
+ 			 strcmp(fname, _state->path[lex_level - 1]) == 0)
+ 	{
+ 		/* path search, path so far is ok,	and we have a match */
+ 
+ 		/* if not at end of path just mark path ok */
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = true;
+ 
+ 		/* end of path, so we want this value */
+ 		if (lex_level == _state->npath)
+ 			get_next = true;
+ 	}
+ 
+ 	if (get_next)
+ 	{
+ 		/*
+ 		 * If tresult is already set it means we've already made this match.
+ 		 * So complain about it.
+ 		 */
+ 		if (_state->tresult != NULL || _state->result_start != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("field name is not unique in json object")));
+ 
+ 		if (_state->normalize_results &&
+ 			_state->lex->token_type == JSON_TOKEN_STRING)
+ 		{
+ 			/* for as_text variants, tell get_scalar to set it for us */
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			/* for non-as_text variants, just note the json starting point */
+ 			_state->result_start = _state->lex->token_start;
+ 		}
+ 	}
+ }
+ 
+ static void
+ get_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_last = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 
+ 	/* same tests as in get_object_field_start, mutatis mutandis */
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 		strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		get_last = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[lex_level - 1] &&
+ 			 strcmp(fname, _state->path[lex_level - 1]) == 0)
+ 	{
+ 		/* done with this field so reset pathok */
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = false;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_last = true;
+ 	}
+ 
+ 	/* for as_test variants our work is already done */
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		/*
+ 		 * make a text object from the string from the prevously noted json
+ 		 * start up to the end of the previous token (the lexer is by now
+ 		 * ahead of us on whatevere came after what we're interested in).
+ 		 */
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		if (isnull  && _state->normalize_results)
+ 			_state->tresult = (text *) NULL;
+ 		else
+ 			_state->tresult = cstring_to_text_with_len(_state->result_start, len);
+ 	}
+ 
+ 	/*
+ 	 * don't need to reset _state->result_start b/c we're only returning one
+ 	 * datum, the conditions should not occur more than once, and this lets us
+ 	 * check cheaply that they don't (see object_field_start() )
+ 	 */
+ }
+ 
+ static void
+ get_array_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	/* json structure check */
+ 	if (lex_level == 0 && _state->search_type == JSON_SEARCH_OBJECT)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_get(fieldname) on a non-object")));
+ 	/* initialize array count for this nesting level */
+ 	if (_state->search_type == JSON_SEARCH_PATH &&
+ 		lex_level <= _state->npath)
+ 		_state->array_level_index[lex_level] = -1;
+ }
+ 
+ static void
+ get_array_element_start(void *state, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_next = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY)
+ 	{
+ 		/* single integer search */
+ 		_state->array_index++;
+ 		if (_state->array_index == _state->search_index)
+ 			get_next = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[lex_level - 1])
+ 	{
+ 		/*
+ 		 * path search, path so far is ok
+ 		 *
+ 		 * increment the array counter. no point doing this if we already know
+ 		 * the path is bad.
+ 		 *
+ 		 * then check if we have a match.
+ 		 */
+ 
+ 		if (++_state->array_level_index[lex_level - 1] ==
+ 			_state->path_level_index[lex_level - 1])
+ 		{
+ 			if (lex_level == _state->npath)
+ 			{
+ 				/* match and at end of path, so get value */
+ 				get_next = true;
+ 			}
+ 			else
+ 			{
+ 				/* not at end of path just mark path ok */
+ 				_state->pathok[lex_level] = true;
+ 			}
+ 		}
+ 
+ 	}
+ 
+ 	/* same logic as for objects */
+ 	if (get_next)
+ 	{
+ 		if (_state->normalize_results &&
+ 			_state->lex->token_type == JSON_TOKEN_STRING)
+ 		{
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			_state->result_start = _state->lex->token_start;
+ 		}
+ 	}
+ }
+ 
+ static void
+ get_array_element_end(void *state, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_last = false;
+ 	int			lex_level = _state->lex->lex_level;
+ 
+ 	/* same logic as in get_object_end, modified for arrays */
+ 
+ 	if (lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY &&
+ 		_state->array_index == _state->search_index)
+ 	{
+ 		get_last = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 lex_level <= _state->npath &&
+ 			 _state->pathok[lex_level - 1] &&
+ 			 _state->array_level_index[lex_level - 1] ==
+ 			 _state->path_level_index[lex_level - 1])
+ 	{
+ 		/* done with this element so reset pathok */
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = false;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_last = true;
+ 	}
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int	 len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		if (isnull  && _state->normalize_results)
+ 			_state->tresult = (text *) NULL;
+ 		else
+ 			_state->tresult = cstring_to_text_with_len(_state->result_start, len);
+ 	}
+ }
+ 
+ static void
+ get_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	if (_state->lex->lex_level == 0 && _state->search_type != JSON_SEARCH_PATH)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_get on a scalar")));
+ 	if (_state->next_scalar)
+ 	{
+ 		/* a de-escaped text value is wanted, so supply it */
+ 		_state->tresult = cstring_to_text(token);
+ 		/* make sure the next call to get_scalar doesn't overwrite it */
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
+ 
+ /*
+  * SQL function json_array_length(json) -> int
+  */
+ Datum
+ json_array_length(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 
+ 	AlenState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, false);
+ 	JsonSemAction sem;
+ 
+ 	state = palloc0(sizeof(alenState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	/* palloc0 does this for us */
+ #if 0
+ 	state->count = 0;
+ #endif
+ 	state->lex = lex;
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = alen_object_start;
+ 	sem->scalar = alen_scalar;
+ 	sem->array_element_start = alen_array_element_start;
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	PG_RETURN_INT32(state->count);
+ }
+ 
+ /*
+  * These next two check ensure that the json is an array (since it can't be
+  * a scala or an object).
+  */
+ 
+ static void
+ alen_object_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_array_length on an object")));
+ }
+ 
+ static void
+ alen_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_array_length on a scalar")));
+ }
+ 
+ static void
+ alen_array_element_start(void *state, bool isnull)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	/* just count up all the level 1 elements */
+ 	if (_state->lex->lex_level == 1)
+ 		_state->count++;
+ }
+ 
+ /*
+  * SQL function json_each
+  *
+  * decompose a json object into key value pairs.
+  *
+  * Unlike json_object_keys() this SRF operates in materialize mode,
+  * stashing its results into a Tuplestore object as it goes.
+  * The constriction of tuples is done using a temporary memory context
+  * that is cleared out after each tuple is built.
+  */
+ Datum
+ json_each(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	TupleDesc	tupdesc;
+ 	EachState	state;
+ 
+ 	state = palloc0(sizeof(eachState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = each_array_start;
+ 	sem->scalar = each_scalar;
+ 	sem->object_field_start = each_object_field_start;
+ 	sem->object_field_end = each_object_field_end;
+ 
+ 	state->normalize_results = false;
+ 	state->next_scalar = false;
+ 
+ 	state->lex = lex;
+ 	state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 										   "json_each temporary cxt",
+ 										   ALLOCSET_DEFAULT_MINSIZE,
+ 										   ALLOCSET_DEFAULT_INITSIZE,
+ 										   ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ /*
+  * SQL function json_each_as_text
+  *
+  * decompose a json object into key value pairs with
+  * de-escaped scalar string values.
+  *
+  * See also comments for json_each
+  */
+ Datum
+ json_each_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	TupleDesc	tupdesc;
+ 	EachState	state;
+ 
+ 	state = palloc0(sizeof(eachState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = each_array_start;
+ 	sem->scalar = each_scalar;
+ 	sem->object_field_start = each_object_field_start;
+ 	sem->object_field_end = each_object_field_end;
+ 
+ 	/* next line is what's different from json_each */
+ 	state->normalize_results = true;
+ 	state->next_scalar = false;
+ 
+ 	state->lex = lex;
+ 	state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 										   "json_each temporary cxt",
+ 										   ALLOCSET_DEFAULT_MINSIZE,
+ 										   ALLOCSET_DEFAULT_INITSIZE,
+ 										   ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ static void
+ each_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	/* save a pointer to where the value starts */
+ 	if (_state->lex->lex_level == 1)
+ 	{
+ 		/*
+ 		 * next_scalar will be reset in the object_field_end handler, and
+ 		 * since we know the value is a scalar there is no danger of it being
+ 		 * on while recursing down the tree.
+ 		 */
+ 		if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING)
+ 			_state->next_scalar = true;
+ 		else
+ 			_state->result_start = _state->lex->token_start;
+ 	}
+ }
+ 
+ static void
+ each_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	EachState	_state = (EachState) state;
+ 	MemoryContext old_cxt;
+ 	int			len;
+ 	text	   *val;
+ 	HeapTuple	tuple;
+ 	Datum		values[2];
+ 	bool        nulls[2] = {false, false};
+ 
+ 	/* skip over nested objects */
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 
+ 	/* use the tmp context so we can clean up after each tuple is done */
+ 	old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
+ 
+ 	values[0] = CStringGetTextDatum(fname);
+ 
+ 	if (isnull && _state->normalize_results)
+ 	{
+ 		nulls[1] = true;
+ 		values[1] = (Datum) NULL;
+ 	}
+ 	else if (_state->next_scalar)
+ 	{
+ 		values[1] = CStringGetTextDatum(_state->normalized_scalar);
+ 		_state->next_scalar = false;
+ 	}
+ 	else
+ 	{
+ 		len = _state->lex->prev_token_terminator - _state->result_start;
+ 		val = cstring_to_text_with_len(_state->result_start, len);
+ 		values[1] = PointerGetDatum(val);
+ 	}
+ 
+ 
+ 	tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
+ 
+ 	tuplestore_puttuple(_state->tuple_store, tuple);
+ 
+ 	/* clean up and switch back */
+ 	MemoryContextSwitchTo(old_cxt);
+ 	MemoryContextReset(_state->tmp_cxt);
+ }
+ 
+ static void
+ each_array_start(void *state)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on an array")));
+ }
+ 
+ static void
+ each_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on a scalar")));
+ 
+ 	/* supply de-escaped value if required */
+ 	if (_state->next_scalar)
+ 		_state->normalized_scalar = token;
+ }
+ 
+ /*
+  * SQL function json_unnest
+  *
+  * get the elements from a json array
+  *
+  * a lot of this processing is similar to the json_each* functions
+  */
+ Datum
+ json_unnest(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 
+ 	/* unnest doesn't need any escaped strings, so use false here */
+ 	JsonLexContext *lex = makeJsonLexContext(json, false);
+ 	JsonSemAction sem;
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	TupleDesc	tupdesc;
+ 	UnnestState state;
+ 
+ 	state = palloc0(sizeof(unnestState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/* it's a simple type, so don't use get_call_result_type() */
+ 	tupdesc = rsi->expectedDesc;
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = unnest_object_start;
+ 	sem->scalar = unnest_scalar;
+ 	sem->array_element_start = unnest_array_element_start;
+ 	sem->array_element_end = unnest_array_element_end;
+ 
+ 	state->lex = lex;
+ 	state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 										   "json_unnest temporary cxt",
+ 										   ALLOCSET_DEFAULT_MINSIZE,
+ 										   ALLOCSET_DEFAULT_INITSIZE,
+ 										   ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ static void
+ unnest_array_element_start(void *state, bool isnull)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	/* save a pointer to where the value starts */
+ 	if (_state->lex->lex_level == 1)
+ 		_state->result_start = _state->lex->token_start;
+ }
+ 
+ static void
+ unnest_array_element_end(void *state, bool isnull)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 	MemoryContext old_cxt;
+ 	int			len;
+ 	text	   *val;
+ 	HeapTuple	tuple;
+ 	Datum		values[1];
+ 	static bool nulls[1] = {false};
+ 
+ 	/* skip over nested objects */
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 
+ 	/* use the tmp context so we can clean up after each tuple is done */
+ 	old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
+ 
+ 	len = _state->lex->prev_token_terminator - _state->result_start;
+ 	val = cstring_to_text_with_len(_state->result_start, len);
+ 
+ 	values[0] = PointerGetDatum(val);
+ 
+ 	tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
+ 
+ 	tuplestore_puttuple(_state->tuple_store, tuple);
+ 
+ 	/* clean up and switch back */
+ 	MemoryContextSwitchTo(old_cxt);
+ 	MemoryContextReset(_state->tmp_cxt);
+ }
+ 
+ static void
+ unnest_object_start(void *state)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on an object")));
+ }
+ 
+ static void
+ unnest_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	/* json structure check */
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on a scalar")));
+ 
+ 	/*
+ 	 * json_unnest always returns json, so there's no need to think about
+ 	 * de-escaped values here.
+ 	 */
+ }
+ 
+ /*
+  * SQL function json_populate_record
+  *
+  * set fields in a record from the argument json
+  *
+  * Code adapted shamelessly from hstore's populate_record
+  * which is in turn partly adapted from record_out.
+  *
+  * The json is decomposed into a hash table, in which each
+  * field in the record is then looked up by name.
+  */
+ Datum
+ json_populate_record(PG_FUNCTION_ARGS)
+ {
+ 	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	text	   *json = PG_GETARG_TEXT_P(1);
+ 	bool		use_json_as_text = PG_GETARG_BOOL(2);
+ 	HTAB	   *json_hash;
+ 	HeapTupleHeader rec;
+ 	Oid			tupType;
+ 	int32		tupTypmod;
+ 	TupleDesc	tupdesc;
+ 	HeapTupleData tuple;
+ 	HeapTuple	rettuple;
+ 	RecordIOData *my_extra;
+ 	int			ncolumns;
+ 	int			i;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	char		fname[NAMEDATALEN];
+ 	JsonHashEntry hashentry;
+ 
+ 
+ 	if (!type_is_rowtype(argtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("first argument must be a rowtype")));
+ 
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		if (PG_ARGISNULL(1))
+ 			PG_RETURN_NULL();
+ 
+ 		rec = NULL;
+ 
+ 		/*
+ 		 * have no tuple to look at, so the only source of type info is the
+ 		 * argtype. The lookup_rowtype_tupdesc call below will error out if we
+ 		 * don't have a known composite type oid here.
+ 		 */
+ 		tupType = argtype;
+ 		tupTypmod = -1;
+ 	}
+ 	else
+ 	{
+ 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ 
+ 		if (PG_ARGISNULL(1))
+ 			PG_RETURN_POINTER(rec);
+ 
+ 		/* Extract type info from the tuple itself */
+ 		tupType = HeapTupleHeaderGetTypeId(rec);
+ 		tupTypmod = HeapTupleHeaderGetTypMod(rec);
+ 	}
+ 
+ 	json_hash = get_json_object_as_hash(json, "json_populate_record", use_json_as_text);
+ 
+ 	/*
+ 	 * if the input json is empty, we can only skip the rest if we were passed
+ 	 * in a non-null record, since otherwise there may be issues with domain
+ 	 * nulls.
+ 	 */
+ 	if (hash_get_num_entries(json_hash) == 0 && rec)
+ 		PG_RETURN_POINTER(rec);
+ 
+ 
+ 	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ 	ncolumns = tupdesc->natts;
+ 
+ 	if (rec)
+ 	{
+ 		/* Build a temporary HeapTuple control structure */
+ 		tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ 		ItemPointerSetInvalid(&(tuple.t_self));
+ 		tuple.t_tableOid = InvalidOid;
+ 		tuple.t_data = rec;
+ 	}
+ 
+ 	/*
+ 	 * We arrange to look up the needed I/O info just once per series of
+ 	 * calls, assuming the record type doesn't change underneath us.
+ 	 */
+ 	my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL ||
+ 		my_extra->ncolumns != ncolumns)
+ 	{
+ 		fcinfo->flinfo->fn_extra =
+ 			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 							   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 		my_extra->record_type = InvalidOid;
+ 		my_extra->record_typmod = 0;
+ 	}
+ 
+ 	if (my_extra->record_type != tupType ||
+ 		my_extra->record_typmod != tupTypmod)
+ 	{
+ 		MemSet(my_extra, 0,
+ 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 			   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra->record_type = tupType;
+ 		my_extra->record_typmod = tupTypmod;
+ 		my_extra->ncolumns = ncolumns;
+ 	}
+ 
+ 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
+ 
+ 	if (rec)
+ 	{
+ 		/* Break down the tuple into fields */
+ 		heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ 	}
+ 	else
+ 	{
+ 		for (i = 0; i < ncolumns; ++i)
+ 		{
+ 			values[i] = (Datum) 0;
+ 			nulls[i] = true;
+ 		}
+ 	}
+ 
+ 	for (i = 0; i < ncolumns; ++i)
+ 	{
+ 		ColumnIOData *column_info = &my_extra->columns[i];
+ 		Oid			column_type = tupdesc->attrs[i]->atttypid;
+ 		char	   *value;
+ 
+ 		/* Ignore dropped columns in datatype */
+ 		if (tupdesc->attrs[i]->attisdropped)
+ 		{
+ 			nulls[i] = true;
+ 			continue;
+ 		}
+ 
+ 		memset(fname, 0, NAMEDATALEN);
+ 		strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
+ 		hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+ 
+ 		/*
+ 		 * we can't just skip here if the key wasn't found since we might have
+ 		 * a domain to deal with. If we were passed in a non-null record
+ 		 * datum, we assume that the existing values are valid (if they're
+ 		 * not, then it's not our fault), but if we were passed in a null,
+ 		 * then every field which we don't populate needs to be run through
+ 		 * the input function just in case it's a domain type.
+ 		 */
+ 		if (hashentry == NULL && rec)
+ 			continue;
+ 
+ 		/*
+ 		 * Prepare to convert the column value from text
+ 		 */
+ 		if (column_info->column_type != column_type)
+ 		{
+ 			getTypeInputInfo(column_type,
+ 							 &column_info->typiofunc,
+ 							 &column_info->typioparam);
+ 			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+ 						  fcinfo->flinfo->fn_mcxt);
+ 			column_info->column_type = column_type;
+ 		}
+ 		if (hashentry == NULL || hashentry->isnull)
+ 		{
+ 			/*
+ 			 * need InputFunctionCall to happen even for nulls, so that domain
+ 			 * checks are done
+ 			 */
+ 			values[i] = InputFunctionCall(&column_info->proc, NULL,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = true;
+ 		}
+ 		else
+ 		{
+ 			value = hashentry->val;
+ 
+ 			values[i] = InputFunctionCall(&column_info->proc, value,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = false;
+ 		}
+ 	}
+ 
+ 	rettuple = heap_form_tuple(tupdesc, values, nulls);
+ 
+ 	ReleaseTupleDesc(tupdesc);
+ 
+ 	PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
+ }
+ 
+ /*
+  * get_json_object_as_hash
+  *
+  * decompose a json object into a hash table.
+  *
+  * Currently doesn't allow anything but a flat object. Should this
+  * change?
+  *
+  * funcname argument allows caller to pass in its name for use in
+  * error messages.
+  */
+ static HTAB *
+ get_json_object_as_hash(text *json, char *funcname, bool use_json_as_text)
+ {
+ 	HASHCTL		ctl;
+ 	HTAB	   *tab;
+ 	JHashState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 
+ 	memset(&ctl, 0, sizeof(ctl));
+ 	ctl.keysize = NAMEDATALEN;
+ 	ctl.entrysize = sizeof(jsonHashEntry);
+ 	ctl.hcxt = CurrentMemoryContext;
+ 	tab = hash_create("json object hashtable",
+ 					  100,
+ 					  &ctl,
+ 					  HASH_ELEM | HASH_CONTEXT);
+ 
+ 	state = palloc0(sizeof(jhashState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->function_name = funcname;
+ 	state->hash = tab;
+ 	state->lex = lex;
+ 	state->use_json_as_text = use_json_as_text;
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = hash_array_start;
+ 	sem->scalar = hash_scalar;
+ 	sem->object_field_start = hash_object_field_start;
+ 	sem->object_field_end = hash_object_field_end;
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	return tab;
+ }
+ 
+ static void
+ hash_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->lex_level > 1)
+ 		return;
+ 
+ 	if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
+ 		_state->lex->token_type == JSON_TOKEN_OBJECT_START)
+ 	{
+ 		if (!_state->use_json_as_text)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot call %s on a nested object",
+ 							_state->function_name)));
+ 		_state->save_json_start = _state->lex->token_start;
+ 	}
+ 	else
+ 	{
+ 		/* must be a scalar */
+ 		_state->save_json_start = NULL;
+ 	}
+ }
+ 
+ static void
+ hash_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 	JsonHashEntry hashentry;
+ 	bool		found;
+ 	char		name[NAMEDATALEN];
+ 
+ 	/*
+ 	 * ignore field names >= NAMEDATALEN - they can't match a record field
+ 	 * ignore nested fields.
+ 	 */
+ 	if (_state->lex->lex_level > 2 || strlen(fname) >= NAMEDATALEN)
+ 		return;
+ 
+ 	memset(name, 0, NAMEDATALEN);
+ 	strncpy(name, fname, NAMEDATALEN);
+ 
+ 	hashentry = hash_search(_state->hash, name, HASH_ENTER, &found);
+ 
+ 	if (found)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("duplicate object field name: \"%s\"", fname)));
+ 
+ 	hashentry->isnull = isnull;
+ 	if (_state->save_json_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->save_json_start;
+ 		char	   *val = palloc((len + 1) * sizeof(char));
+ 
+ 		memcpy(val, _state->save_json_start, len);
+ 		val[len] = '\0';
+ 		hashentry->val = val;
+ 	}
+ 	else
+ 	{
+ 		/* must have had a scalar instead */
+ 		hashentry->val = _state->saved_scalar;
+ 	}
+ }
+ 
+ static void
+ hash_array_start(void *state)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call %s on an array", _state->function_name)));
+ }
+ 
+ static void
+ hash_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call %s on a scalar", _state->function_name)));
+ 
+ 	if (_state->lex->lex_level == 1)
+ 		_state->saved_scalar = token;
+ }
+ 
+ 
+ /*
+  * SQL function json_populate_recordset
+  *
+  * set fields in a set of records from the argument json,
+  * which must be an array of objects.
+  *
+  * similar to json_populate_record, but the tuple-building code
+  * is pushed down into the semantic action handlers so it's done
+  * per object in the array.
+  */
+ Datum
+ json_populate_recordset(PG_FUNCTION_ARGS)
+ {
+ 	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	text	   *json = PG_GETARG_TEXT_P(1);
+ 	bool		use_json_as_text = PG_GETARG_BOOL(2);
+ 	ReturnSetInfo *rsi;
+ 	MemoryContext old_cxt;
+ 	Oid			tupType;
+ 	int32		tupTypmod;
+ 	HeapTupleHeader rec;
+ 	TupleDesc	tupdesc;
+ 	RecordIOData *my_extra;
+ 	int			ncolumns;
+ 	JsonLexContext *lex;
+ 	JsonSemAction sem;
+ 	PopulateRecordsetState state;
+ 
+ 	if (!type_is_rowtype(argtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("first argument must be a rowtype")));
+ 
+ 	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
+ 		rsi->expectedDesc == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/*
+ 	 * get the tupdesc from the result set info - it must be a record type
+ 	 * because we already checked that arg1 is a record type.
+ 	 */
+ 	(void) get_call_result_type(fcinfo, NULL, &tupdesc);
+ 
+ 	state = palloc0(sizeof(populateRecordsetState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 
+ 	/* make these in a sufficiently long-lived memory context */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ 	BlessTupleDesc(state->ret_tdesc);
+ 	state->tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	/* if the json is null send back an empty set */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_NULL();
+ 
+ 	if (PG_ARGISNULL(0))
+ 		rec = NULL;
+ 	else
+ 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ 
+ 	tupType = tupdesc->tdtypeid;
+ 	tupTypmod = tupdesc->tdtypmod;
+ 	ncolumns = tupdesc->natts;
+ 
+ 	lex = makeJsonLexContext(json, true);
+ 
+ 	/*
+ 	 * We arrange to look up the needed I/O info just once per series of
+ 	 * calls, assuming the record type doesn't change underneath us.
+ 	 */
+ 	my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL ||
+ 		my_extra->ncolumns != ncolumns)
+ 	{
+ 		fcinfo->flinfo->fn_extra =
+ 			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 							   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+ 		my_extra->record_type = InvalidOid;
+ 		my_extra->record_typmod = 0;
+ 	}
+ 
+ 	if (my_extra->record_type != tupType ||
+ 		my_extra->record_typmod != tupTypmod)
+ 	{
+ 		MemSet(my_extra, 0,
+ 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+ 			   + ncolumns * sizeof(ColumnIOData));
+ 		my_extra->record_type = tupType;
+ 		my_extra->record_typmod = tupTypmod;
+ 		my_extra->ncolumns = ncolumns;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->array_start = populate_recordset_array_start;
+ 	sem->array_element_start = populate_recordset_array_element_start;
+ 	sem->scalar = populate_recordset_scalar;
+ 	sem->object_field_start = populate_recordset_object_field_start;
+ 	sem->object_field_end = populate_recordset_object_field_end;
+ 	sem->object_start = populate_recordset_object_start;
+ 	sem->object_end = populate_recordset_object_end;
+ 
+ 	state->lex = lex;
+ 
+ 	state->my_extra = my_extra;
+ 	state->rec = rec;
+ 	state->use_json_as_text = use_json_as_text;
+ 	state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
+ 
+ 	pg_parse_json(lex, sem);
+ 
+ 	rsi->setResult = state->tuple_store;
+ 	rsi->setDesc = state->ret_tdesc;
+ 
+ 	PG_RETURN_NULL();
+ 
+ }
+ 
+ static void
+ populate_recordset_object_start(void *state)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 	int			lex_level = _state->lex->lex_level;
+ 	HASHCTL		ctl;
+ 
+ 	if (lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call populate_recordset on an object")));
+ 	else if (lex_level > 1 && !_state->use_json_as_text)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			  errmsg("cannot call populate_recordset with nested objects")));
+ 
+ 	/* set up a new hash for this entry */
+ 	memset(&ctl, 0, sizeof(ctl));
+ 	ctl.keysize = NAMEDATALEN;
+ 	ctl.entrysize = sizeof(jsonHashEntry);
+ 	ctl.hcxt = CurrentMemoryContext;
+ 	_state->json_hash = hash_create("json object hashtable",
+ 									100,
+ 									&ctl,
+ 									HASH_ELEM | HASH_CONTEXT);
+ }
+ 
+ static void
+ populate_recordset_object_end(void *state)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 	HTAB	   *json_hash = _state->json_hash;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	char		fname[NAMEDATALEN];
+ 	int			i;
+ 	RecordIOData *my_extra = _state->my_extra;
+ 	int			ncolumns = my_extra->ncolumns;
+ 	TupleDesc	tupdesc = _state->ret_tdesc;
+ 	JsonHashEntry hashentry;
+ 	HeapTupleHeader rec = _state->rec;
+ 	HeapTuple	rettuple;
+ 
+ 	if (_state->lex->lex_level > 1)
+ 		return;
+ 
+ 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
+ 
+ 	if (_state->rec)
+ 	{
+ 		HeapTupleData tuple;
+ 
+ 		/* Build a temporary HeapTuple control structure */
+ 		tuple.t_len = HeapTupleHeaderGetDatumLength(_state->rec);
+ 		ItemPointerSetInvalid(&(tuple.t_self));
+ 		tuple.t_tableOid = InvalidOid;
+ 		tuple.t_data = _state->rec;
+ 
+ 		/* Break down the tuple into fields */
+ 		heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ 	}
+ 	else
+ 	{
+ 		for (i = 0; i < ncolumns; ++i)
+ 		{
+ 			values[i] = (Datum) 0;
+ 			nulls[i] = true;
+ 		}
+ 	}
+ 
+ 	for (i = 0; i < ncolumns; ++i)
+ 	{
+ 		ColumnIOData *column_info = &my_extra->columns[i];
+ 		Oid			column_type = tupdesc->attrs[i]->atttypid;
+ 		char	   *value;
+ 
+ 		/* Ignore dropped columns in datatype */
+ 		if (tupdesc->attrs[i]->attisdropped)
+ 		{
+ 			nulls[i] = true;
+ 			continue;
+ 		}
+ 
+ 		memset(fname, 0, NAMEDATALEN);
+ 		strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
+ 		hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+ 
+ 		/*
+ 		 * we can't just skip here if the key wasn't found since we might have
+ 		 * a domain to deal with. If we were passed in a non-null record
+ 		 * datum, we assume that the existing values are valid (if they're
+ 		 * not, then it's not our fault), but if we were passed in a null,
+ 		 * then every field which we don't populate needs to be run through
+ 		 * the input function just in case it's a domain type.
+ 		 */
+ 		if (hashentry == NULL && rec)
+ 			continue;
+ 
+ 		/*
+ 		 * Prepare to convert the column value from text
+ 		 */
+ 		if (column_info->column_type != column_type)
+ 		{
+ 			getTypeInputInfo(column_type,
+ 							 &column_info->typiofunc,
+ 							 &column_info->typioparam);
+ 			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+ 						  _state->fn_mcxt);
+ 			column_info->column_type = column_type;
+ 		}
+ 		if (hashentry == NULL || hashentry->isnull)
+ 		{
+ 			/*
+ 			 * need InputFunctionCall to happen even for nulls, so that domain
+ 			 * checks are done
+ 			 */
+ 			values[i] = InputFunctionCall(&column_info->proc, NULL,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = true;
+ 		}
+ 		else
+ 		{
+ 			value = hashentry->val;
+ 
+ 			values[i] = InputFunctionCall(&column_info->proc, value,
+ 										  column_info->typioparam,
+ 										  tupdesc->attrs[i]->atttypmod);
+ 			nulls[i] = false;
+ 		}
+ 	}
+ 
+ 	rettuple = heap_form_tuple(tupdesc, values, nulls);
+ 
+ 	tuplestore_puttuple(_state->tuple_store, rettuple);
+ 
+ 	hash_destroy(json_hash);
+ }
+ 
+ static void
+ populate_recordset_array_element_start(void *state, bool isnull)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level == 1 &&
+ 		_state->lex->token_type != JSON_TOKEN_OBJECT_START)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			 errmsg("must call populate_recordset on an array of objects")));
+ }
+ 
+ static void
+ populate_recordset_array_start(void *state)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level != 0 && !_state->use_json_as_text)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call populate_recordset with nested arrays")));
+ }
+ 
+ static void
+ populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call populate_recordset on a scalar")));
+ 
+ 	if (_state->lex->lex_level == 2)
+ 		_state->saved_scalar = token;
+ }
+ 
+ static void
+ populate_recordset_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 
+ 	if (_state->lex->lex_level > 2)
+ 		return;
+ 
+ 	if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
+ 		_state->lex->token_type == JSON_TOKEN_OBJECT_START)
+ 	{
+ 		if (!_state->use_json_as_text)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			   errmsg("cannot call populate_recordset on a nested object")));
+ 		_state->save_json_start = _state->lex->token_start;
+ 	}
+ 	else
+ 	{
+ 		_state->save_json_start = NULL;
+ 	}
+ }
+ 
+ static void
+ populate_recordset_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	PopulateRecordsetState _state = (PopulateRecordsetState) state;
+ 	JsonHashEntry hashentry;
+ 	bool		found;
+ 	char		name[NAMEDATALEN];
+ 
+ 	/*
+ 	 * ignore field names >= NAMEDATALEN - they can't match a record field
+ 	 * ignore nested fields.
+ 	 */
+ 	if (_state->lex->lex_level > 2 || strlen(fname) >= NAMEDATALEN)
+ 		return;
+ 
+ 	memset(name, 0, NAMEDATALEN);
+ 	strncpy(name, fname, NAMEDATALEN);
+ 
+ 	hashentry = hash_search(_state->json_hash, name, HASH_ENTER, &found);
+ 
+ 	if (found)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("duplicate object field name: \"%s\"", fname)));
+ 
+ 	hashentry->isnull = isnull;
+ 	if (_state->save_json_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->save_json_start;
+ 		char	   *val = palloc((len + 1) * sizeof(char));
+ 
+ 		memcpy(val, _state->save_json_start, len);
+ 		val[len] = '\0';
+ 		hashentry->val = val;
+ 	}
+ 	else
+ 	{
+ 		/* must have had a scalar instead */
+ 		hashentry->val = _state->saved_scalar;
+ 	}
+ }
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
***************
*** 1724,1729 **** DESCR("range difference");
--- 1724,1743 ----
  DATA(insert OID = 3900 (  "*"	   PGNSP PGUID b f f 3831 3831 3831 3900 0 range_intersect - - ));
  DESCR("range intersection");
  
+ /* Use function oids here because json_get and json_get_as_text are overloaded */
+ DATA(insert OID = 5100 (  "->"	   PGNSP PGUID b f f 114 25 114 0 0 5001 - - ));
+ DESCR("get json object field");
+ DATA(insert OID = 5101 (  "->>"    PGNSP PGUID b f f 114 25 25 0 0 5002 - - ));
+ DESCR("get json object field as text");
+ DATA(insert OID = 5102 (  "->"	   PGNSP PGUID b f f 114 23 114 0 0 5003 - - ));
+ DESCR("get json array element");
+ DATA(insert OID = 5103 (  "->>"    PGNSP PGUID b f f 114 23 25 0 0 5004 - - ));
+ DESCR("get json array element as text");
+ DATA(insert OID = 5104 (  "#>"     PGNSP PGUID b f f 114 1009 114 0 0 json_get_path_op - - ));
+ DESCR("get value from json with path elements");
+ DATA(insert OID = 5105 (  "#>>"    PGNSP PGUID b f f 114 1009 25 0 0 json_get_path_as_text_op - - ));
+ DESCR("get value from json as text with path elements");
+ 
  
  /*
   * function prototypes
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4103,4108 **** DESCR("map row to json");
--- 4103,4139 ----
  DATA(insert OID = 3156 (  row_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "2249 16" _null_ _null_ _null_ _null_ row_to_json_pretty _null_ _null_ _null_ ));
  DESCR("map row to json with optional pretty printing");
  
+ DATA(insert OID = 5001 (  json_get		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "114 25" _null_ _null_ _null_ _null_ json_get_ofield _null_ _null_ _null_ ));
+ DESCR("get json object field");
+ DATA(insert OID = 5002 (  json_get_as_text PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "114 25" _null_ _null_ _null_ _null_ json_get_ofield_as_text _null_ _null_ _null_ ));
+ DESCR("get json object field as text");
+ DATA(insert OID = 5003 (  json_get		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "114 23" _null_ _null_ _null_ _null_ json_get_aelem _null_ _null_ _null_ ));
+ DESCR("get json array element");
+ DATA(insert OID = 5004 (  json_get_as_text PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "114 23" _null_ _null_ _null_ _null_ json_get_aelem_as_text _null_ _null_ _null_ ));
+ DESCR("get json array element as text");
+ DATA(insert OID = 5005 (  json_object_keys PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 25 "114" _null_ _null_ _null_ _null_ json_object_keys _null_ _null_ _null_ ));
+ DESCR("get json object keys");
+ DATA(insert OID = 5006 (  json_array_length PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 23 "114" _null_ _null_ _null_ _null_ json_array_length _null_ _null_ _null_ ));
+ DESCR("length of json array");
+ DATA(insert OID = 5007 (  json_each PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 2249 "114" "{114,25,114}" "{i,o,o}" "{from_json,key,value}" _null_ json_each _null_ _null_ _null_ ));
+ DESCR("key value pairs of a json object");
+ DATA(insert OID = 5008 (  json_get_path	   PGNSP PGUID 12 1 0 25 0 f f f f t f s 2 0 114 "114 1009" "{114,1009}" "{i,v}" "{from_json,path_elems}" _null_ json_get_path _null_ _null_ _null_ ));
+ DESCR("get value from json with path elements");
+ DATA(insert OID = 5014 (  json_get_path_op PGNSP PGUID 12 1 0 0 0  f f f f t f s 2 0 114 "114 1009" _null_ _null_ "{from_json,path_elems}" _null_ json_get_path _null_ _null_ _null_ ));
+ DESCR("get value from json with path elements");
+ DATA(insert OID = 5009 (  json_unnest      PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 114 "114" "{114,114}" "{i,o}" "{from_json,value}" _null_ json_unnest _null_ _null_ _null_ ));
+ DESCR("key value pairs of a json object");
+ DATA(insert OID = 5010 (  json_get_path_as_text	   PGNSP PGUID 12 1 0 25 0 f f f f t f s 2 0 25 "114 1009" "{114,1009}" "{i,v}" "{from_json,path_elems}" _null_ json_get_path_as_text _null_ _null_ _null_ ));
+ DESCR("get value from json as text with path elements");
+ DATA(insert OID = 5015 (  json_get_path_as_text_op PGNSP PGUID 12 1 0 0 0  f f f f t f s 2 0 25 "114 1009" _null_ _null_ "{from_json,path_elems}" _null_ json_get_path_as_text _null_ _null_ _null_ ));
+ DESCR("get value from json as text with path elements");
+ DATA(insert OID = 5011 (  json_each_as_text PGNSP PGUID 12 1 100 0 0 f f f f t t s 1 0 2249 "114" "{114,25,25}" "{i,o,o}" "{from_json,key,value}" _null_ json_each_as_text _null_ _null_ _null_ ));
+ DESCR("key value pairs of a json object");
+ DATA(insert OID = 5012 (  json_populate_record PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2283 "2283 114 16" _null_ _null_ _null_ _null_ json_populate_record _null_ _null_ _null_ ));
+ DESCR("get record fields from a json object");
+ DATA(insert OID = 5013 (  json_populate_recordset PGNSP PGUID 12 1 100 0 0 f f f f f t s 3 0 2283 "2283 114 16" _null_ _null_ _null_ _null_ json_populate_recordset _null_ _null_ _null_ ));
+ DESCR("get set of records with fields from a json array of objects");
+ 
  /* uuid */
  DATA(insert OID = 2952 (  uuid_in		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));
  DESCR("I/O");
*** a/src/include/utils/json.h
--- b/src/include/utils/json.h
***************
*** 17,22 ****
--- 17,23 ----
  #include "fmgr.h"
  #include "lib/stringinfo.h"
  
+ /* functions in json.c */
  extern Datum json_in(PG_FUNCTION_ARGS);
  extern Datum json_out(PG_FUNCTION_ARGS);
  extern Datum json_recv(PG_FUNCTION_ARGS);
***************
*** 27,30 **** extern Datum row_to_json(PG_FUNCTION_ARGS);
--- 28,46 ----
  extern Datum row_to_json_pretty(PG_FUNCTION_ARGS);
  extern void escape_json(StringInfo buf, const char *str);
  
+ /* functions in jsonfuncs.c */
+ extern Datum json_get_aelem_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_get_aelem(PG_FUNCTION_ARGS);
+ extern Datum json_get_ofield_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_get_ofield(PG_FUNCTION_ARGS);
+ extern Datum json_object_keys(PG_FUNCTION_ARGS);
+ extern Datum json_array_length(PG_FUNCTION_ARGS);
+ extern Datum json_each(PG_FUNCTION_ARGS);
+ extern Datum json_each_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_get_path(PG_FUNCTION_ARGS);
+ extern Datum json_get_path_as_text(PG_FUNCTION_ARGS);
+ extern Datum json_unnest(PG_FUNCTION_ARGS);
+ extern Datum json_populate_record(PG_FUNCTION_ARGS);
+ extern Datum json_populate_recordset(PG_FUNCTION_ARGS);
+ 
  #endif   /* JSON_H */
*** /dev/null
--- b/src/include/utils/jsonapi.h
***************
*** 0 ****
--- 1,110 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/utils/jsonapi.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef JSONAPI_H
+ #define JSONAPI_H
+ 
+ #include "lib/stringinfo.h"
+ 
+ typedef enum
+ {
+ 	JSON_TOKEN_INVALID,
+ 	JSON_TOKEN_STRING,
+ 	JSON_TOKEN_NUMBER,
+ 	JSON_TOKEN_OBJECT_START,
+ 	JSON_TOKEN_OBJECT_END,
+ 	JSON_TOKEN_ARRAY_START,
+ 	JSON_TOKEN_ARRAY_END,
+ 	JSON_TOKEN_COMMA,
+ 	JSON_TOKEN_COLON,
+ 	JSON_TOKEN_TRUE,
+ 	JSON_TOKEN_FALSE,
+ 	JSON_TOKEN_NULL,
+ 	JSON_TOKEN_END,
+ }	JsonTokenType;
+ 
+ 
+ /*
+  * All the fields in this structure should be treated as read-only.
+  *
+  * If strval is not null, then it should contain the de-escaped value
+  * of the lexeme if it's a string. Otherwise most of these field names
+  * should be self-explanatory.
+  *
+  * line_number and line_start are principally for use by the parser's
+  * error reporting routines.
+  * token_terminator and prev_token_terminator point to the character
+  * AFTER the end of the token, i.e. where there would be a nul byte
+  * if we were using nul-terminated strings.
+  */
+ typedef struct JsonLexContext
+ {
+ 	char	   *input;
+ 	int			input_length;
+ 	char	   *token_start;
+ 	char	   *token_terminator;
+ 	char	   *prev_token_terminator;
+ 	JsonTokenType token_type;
+ 	int			lex_level;
+ 	int			line_number;
+ 	char	   *line_start;
+ 	StringInfo	strval;
+ } JsonLexContext;
+ 
+ typedef void (*json_struct_action) (void *state);
+ typedef void (*json_ofield_action) (void *state, char *fname, bool isnull);
+ typedef void (*json_aelem_action) (void *state, bool isnull);
+ typedef void (*json_scalar_action) (void *state, char *token, JsonTokenType tokentype);
+ 
+ 
+ /*
+  * Semantic Action structure for use in parsing json.
+  * Any of these actions can be NULL, in which case nothing is done at that
+  * point, Likewise, semstate can be NULL. Using an all-NULL structure amounts
+  * to doing a pure parse with no side-effects, and is therefore exactly
+  * what the json input routines do.
+  */
+ typedef struct jsonSemAction
+ {
+ 	void	   *semstate;
+ 	json_struct_action object_start;
+ 	json_struct_action object_end;
+ 	json_struct_action array_start;
+ 	json_struct_action array_end;
+ 	json_ofield_action object_field_start;
+ 	json_ofield_action object_field_end;
+ 	json_aelem_action array_element_start;
+ 	json_aelem_action array_element_end;
+ 	json_scalar_action scalar;
+ }	jsonSemAction, *JsonSemAction;
+ 
+ /*
+  * parse_json will parse the string in the lex calling the
+  * action functions in sem at the appropriate points. It is
+  * up to them to keep what state they need	in semstate. If they
+  * need access to the state of the lexer, then its pointer
+  * should be passed to them as a member of whatever semstate
+  * points to. If the action pointers are NULL the parser
+  * does nothing and just continues.
+  */
+ extern void pg_parse_json(JsonLexContext *lex, JsonSemAction sem);
+ 
+ /*
+  * constructor for JsonLexContext, with or without strval element.
+  * If supplied, the strval element will contain a de-escaped version of
+  * the lexeme. However, doing this imposes a performance penalty, so
+  * it should be avoided if the de-escaped lexeme is not required.
+  */
+ extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes);
+ 
+ #endif   /* JSONAPI_H */
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
***************
*** 433,435 **** FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "json
--- 433,922 ----
   {"jsonfield":{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}}
  (1 row)
  
+ -- json extraction functions
+ CREATE TEMP TABLE test_json (
+        json_type text,
+        test_json json
+ );
+ INSERT INTO test_json VALUES
+ ('scalar','"a scalar"'),
+ ('array','["zero", "one","two",null,"four","five"]'),
+ ('object','{"field1":"val1","field2":"val2","field3":null}');
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ ERROR:  cannot call json_get on a scalar
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'array';
+ ERROR:  cannot call json_get(fieldname) on a non-object
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'object';
+  json_get 
+ ----------
+  
+ (1 row)
+ 
+ SELECT json_get(test_json,'field2') 
+ FROM test_json
+ WHERE json_type = 'object';
+  json_get 
+ ----------
+  "val2"
+ (1 row)
+ 
+ SELECT test_json->'field2'
+ FROM test_json
+ WHERE json_type = 'object';
+  ?column? 
+ ----------
+  "val2"
+ (1 row)
+ 
+ SELECT test_json->>'field2' 
+ FROM test_json
+ WHERE json_type = 'object';
+  ?column? 
+ ----------
+  val2
+ (1 row)
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ ERROR:  cannot call json_get on a scalar
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+  json_get 
+ ----------
+  "two"
+ (1 row)
+ 
+ SELECT json_get(test_json,2)
+ FROM test_json
+ WHERE json_type = 'object';
+ ERROR:  cannot call json_get(int) on a non-array
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+  json_get 
+ ----------
+  "two"
+ (1 row)
+ 
+ SELECT test_json->2 
+ FROM test_json
+ WHERE json_type = 'array';
+  ?column? 
+ ----------
+  "two"
+ (1 row)
+ 
+ SELECT test_json->>2
+ FROM test_json
+ WHERE json_type = 'array';
+  ?column? 
+ ----------
+  two
+ (1 row)
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'scalar';
+ ERROR:  cannot call json_object_keys on a scalar
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'array';
+ ERROR:  cannot call json_object_keys on an array
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'object';
+  json_object_keys 
+ ------------------
+  field1
+  field2
+  field3
+ (3 rows)
+ 
+ -- nulls
+ select (test_json->'field3') is null as expect_false
+ from test_json
+ where json_type = 'object';
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select (test_json->>'field3') is null as expect_true
+ from test_json
+ where json_type = 'object';
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ select (test_json->3) is null as expect_false
+ from test_json
+ where json_type = 'array';
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select (test_json->>3) is null as expect_true
+ from test_json
+ where json_type = 'array';
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ -- array length
+ SELECT json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+  json_array_length 
+ -------------------
+                  5
+ (1 row)
+ 
+ SELECT json_array_length('[]');
+  json_array_length 
+ -------------------
+                  0
+ (1 row)
+ 
+ SELECT json_array_length('{"f1":1,"f2":[5,6]}');
+ ERROR:  cannot call json_array_length on an object
+ SELECT json_array_length('4');
+ ERROR:  cannot call json_array_length on a scalar
+ -- each
+ select json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+      json_each     
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 rows)
+ 
+ select * from json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+  key |   value   
+ -----+-----------
+  f1  | [1,2,3]
+  f2  | {"f3":1}
+  f4  | null
+  f5  | 99
+  f6  | "stringy"
+ (5 rows)
+ 
+ select json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,)
+  (f5,null)
+ (4 rows)
+ 
+ select * from json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+  key |  value   
+ -----+----------
+  f1  | [1,2,3]
+  f2  | {"f3":1}
+  f4  | 
+  f5  | 99
+  f6  | stringy
+ (5 rows)
+ 
+ -- get_path, get_path_as_text
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+  json_get_path 
+ ---------------
+  "stringy"
+ (1 row)
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+  json_get_path 
+ ---------------
+  {"f3":1}
+ (1 row)
+ 
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+  json_get_path 
+ ---------------
+  "f3"
+ (1 row)
+ 
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+  json_get_path 
+ ---------------
+  1
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+  json_get_path_as_text 
+ -----------------------
+  stringy
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+  json_get_path_as_text 
+ -----------------------
+  {"f3":1}
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+  json_get_path_as_text 
+ -----------------------
+  f3
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+  json_get_path_as_text 
+ -----------------------
+  1
+ (1 row)
+ 
+ -- get_path nulls
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_false;
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_true;
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_false;
+  expect_false 
+ --------------
+  f
+ (1 row)
+ 
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_true;
+  expect_true 
+ -------------
+  t
+ (1 row)
+ 
+ -- get_path operators
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f4','f6'];
+  ?column?  
+ -----------
+  "stringy"
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2'];
+  ?column? 
+ ----------
+  {"f3":1}
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','0'];
+  ?column? 
+ ----------
+  "f3"
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','1'];
+  ?column? 
+ ----------
+  1
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f4','f6'];
+  ?column? 
+ ----------
+  stringy
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2'];
+  ?column? 
+ ----------
+  {"f3":1}
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','0'];
+  ?column? 
+ ----------
+  f3
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','1'];
+  ?column? 
+ ----------
+  1
+ (1 row)
+ 
+ -- same using array literals
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f4,f6}';
+  ?column?  
+ -----------
+  "stringy"
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f2}';
+  ?column? 
+ ----------
+  {"f3":1}
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f2,0}';
+  ?column? 
+ ----------
+  "f3"
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f2,1}';
+  ?column? 
+ ----------
+  1
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f4,f6}';
+  ?column? 
+ ----------
+  stringy
+ (1 row)
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2}';
+  ?column? 
+ ----------
+  {"f3":1}
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,0}';
+  ?column? 
+ ----------
+  f3
+ (1 row)
+ 
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,1}';
+  ?column? 
+ ----------
+  1
+ (1 row)
+ 
+ --unnest
+ select json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+       json_unnest      
+ -----------------------
+  1
+  true
+  [1,[2,3]]
+  null
+  {"f1":1,"f2":[7,8,9]}
+  false
+ (6 rows)
+ 
+ select * from json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+          value         
+ -----------------------
+  1
+  true
+  [1,[2,3]]
+  null
+  {"f1":1,"f2":[7,8,9]}
+  false
+ (6 rows)
+ 
+ -- populate_record
+ create type jpop as (a text, b int, c timestamp);
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+    a    | b | c 
+ --------+---+---
+  blurfl |   | 
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl | 3 | Mon Dec 31 15:30:56 2012
+ (1 row)
+ 
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}', true) q;
+    a    | b | c 
+ --------+---+---
+  blurfl |   | 
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}', true) q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl | 3 | Mon Dec 31 15:30:56 2012
+ (1 row)
+ 
+ select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a        | b | c 
+ -----------------+---+---
+  [100,200,false] |   | 
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+         a        | b |            c             
+ -----------------+---+--------------------------
+  [100,200,false] | 3 | Mon Dec 31 15:30:56 2012
+ (1 row)
+ 
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}', true) q;
+ ERROR:  invalid input syntax for type timestamp: "[100,200,false]"
+ -- populate_recordset
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl |   | 
+         | 3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+    a    | b  |            c             
+ --------+----+--------------------------
+  blurfl | 99 | 
+  def    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl |   | 
+         | 3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+    a    | b  |            c             
+ --------+----+--------------------------
+  blurfl | 99 | 
+  def    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+        a       | b  |            c             
+ ---------------+----+--------------------------
+  [100,200,300] | 99 | 
+  {"z":true}    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ ERROR:  invalid input syntax for type timestamp: "[100,200,300]"
+ -- using the default use_json_as_text argument
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+    a    | b |            c             
+ --------+---+--------------------------
+  blurfl |   | 
+         | 3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+    a    | b  |            c             
+ --------+----+--------------------------
+  blurfl | 99 | 
+  def    |  3 | Fri Jan 20 10:42:53 2012
+ (2 rows)
+ 
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ ERROR:  cannot call populate_recordset on a nested object
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ ERROR:  cannot call populate_recordset on a nested object
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
***************
*** 113,115 **** FROM (SELECT '-Infinity'::float8 AS "float8field") q;
--- 113,298 ----
  -- json input
  SELECT row_to_json(q)
  FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "jsonfield") q;
+ 
+ 
+ -- json extraction functions
+ 
+ CREATE TEMP TABLE test_json (
+        json_type text,
+        test_json json
+ );
+ 
+ INSERT INTO test_json VALUES
+ ('scalar','"a scalar"'),
+ ('array','["zero", "one","two",null,"four","five"]'),
+ ('object','{"field1":"val1","field2":"val2","field3":null}');
+ 
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ 
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_get(test_json,'x') 
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT json_get(test_json,'field2') 
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT test_json->'field2'
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT test_json->>'field2' 
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'scalar';
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_get(test_json,2)
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ SELECT json_get(test_json,2) 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT test_json->2 
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT test_json->>2
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'scalar';
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'array';
+ 
+ SELECT json_object_keys(test_json)
+ FROM test_json
+ WHERE json_type = 'object';
+ 
+ -- nulls
+ 
+ select (test_json->'field3') is null as expect_false
+ from test_json
+ where json_type = 'object';
+ 
+ select (test_json->>'field3') is null as expect_true
+ from test_json
+ where json_type = 'object';
+ 
+ select (test_json->3) is null as expect_false
+ from test_json
+ where json_type = 'array';
+ 
+ select (test_json->>3) is null as expect_true
+ from test_json
+ where json_type = 'array';
+ 
+ 
+ -- array length
+ 
+ SELECT json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+ 
+ SELECT json_array_length('[]');
+ 
+ SELECT json_array_length('{"f1":1,"f2":[5,6]}');
+ 
+ SELECT json_array_length('4');
+ 
+ -- each
+ 
+ select json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+ select * from json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ 
+ select json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+ select * from json_each_as_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+ 
+ -- get_path, get_path_as_text
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ select json_get_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ select json_get_path_as_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ 
+ -- get_path nulls
+ 
+ select json_get_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_false;
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_true;
+ select json_get_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_false;
+ select json_get_path_as_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_true;
+ 
+ -- get_path operators
+ 
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f4','f6'];
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','0'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','1'];
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f4','f6'];
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','0'];
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','1'];
+ 
+ -- same using array literals
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f4,f6}';
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f2}';
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f2,0}';
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>'{f2,1}';
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f4,f6}';
+ select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2}';
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,0}';
+ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,1}';
+ 
+ --unnest
+ 
+ select json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
+ select * from json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+ 
+ -- populate_record
+ create type jpop as (a text, b int, c timestamp);
+ 
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+ 
+ select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}', true) q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}', true) q;
+ 
+ select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}', true) q;
+ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}', true) q;
+ 
+ -- populate_recordset
+ 
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q;
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
+ 
+ -- using the default use_json_as_text argument
+ 
+ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;